@ -0,0 +1,2 @@ |
|||||
|
/node_modules/ |
||||
|
/package-lock.json |
@ -0,0 +1,194 @@ |
|||||
|
import os |
||||
|
import markdown |
||||
|
import re |
||||
|
from jinja2 import Environment, FileSystemLoader |
||||
|
import yaml |
||||
|
import json |
||||
|
|
||||
|
class Website: |
||||
|
def __init__(self, template_dir, public_dir): |
||||
|
self.template_dir_root = template_dir |
||||
|
self.public_dir = public_dir |
||||
|
self.output_dir_root = 'public/articles' |
||||
|
self.env = Environment(loader=FileSystemLoader(self.template_dir_root)) |
||||
|
self.pages = [] |
||||
|
self.tags = [] |
||||
|
self.all_images = [] |
||||
|
self.about_meta, self.about_content = self.fetch_page('content', 'about.md') |
||||
|
|
||||
|
def build(self): |
||||
|
self.fetch_pages() |
||||
|
self.process_page() |
||||
|
self.fetch_tags() |
||||
|
self.create_list() |
||||
|
self.build_about() |
||||
|
self.fetch_all_images() |
||||
|
self.create_json('public/json/articles.json') |
||||
|
|
||||
|
def fetch_pages(self): |
||||
|
for page in os.listdir('content/posts'): |
||||
|
if page.endswith('.md'): |
||||
|
with open(os.path.join('content/posts', page), 'r', encoding="utf8") as f: |
||||
|
content = f.read() |
||||
|
parts = content.split('---') |
||||
|
metadata = yaml.safe_load(parts[1]) |
||||
|
md_content = ''.join(parts[2:]) |
||||
|
md_content = re.sub(r'\(([^)]+)\)\[([^\]]+)\]', r'<a href="\2">\1</a>', md_content) |
||||
|
md_content = self.format_content(md_content) |
||||
|
html_content = markdown.markdown(md_content) |
||||
|
output_filename = os.path.splitext(page)[0] + '.html' |
||||
|
new_page = Page(metadata, html_content, output_filename) |
||||
|
self.pages.append(new_page) |
||||
|
|
||||
|
def process_page(self): |
||||
|
template = self.env.get_template('article.html') |
||||
|
self.check_output_dir() |
||||
|
for page in self.pages: |
||||
|
metadata = page.get_metadata() |
||||
|
filename = page.get_filename() |
||||
|
html_content = page.get_content() |
||||
|
html_output = template.render( |
||||
|
title=metadata['title'], |
||||
|
year=metadata['year'], |
||||
|
date=metadata['date'], |
||||
|
tags=metadata.get('tags', []), |
||||
|
image=metadata['image'], |
||||
|
showcase=metadata['showcase'], |
||||
|
credits=metadata['credits'], |
||||
|
references=page.get_references(), |
||||
|
content=html_content) |
||||
|
with open(os.path.join(self.output_dir_root, filename), 'w', encoding='utf8') as output_file: |
||||
|
output_file.write(html_output) |
||||
|
|
||||
|
def check_output_dir(self): |
||||
|
if not os.path.exists(self.output_dir_root): |
||||
|
os.makedirs(self.output_dir_root) |
||||
|
|
||||
|
def fetch_page(self, dir, page): |
||||
|
for about in os.listdir(dir): |
||||
|
if about == page: |
||||
|
with open(os.path.join(dir, about), 'r', encoding='utf8') as f: |
||||
|
content = f.read() |
||||
|
parts = content.split('---') |
||||
|
metadata = yaml.safe_load(parts[1]) |
||||
|
md_content = ''.join(parts[2:]) |
||||
|
md_content = re.sub(r'\(([^)]+)\)\[([^\]]+)\]', r'<a href="\2">\1</a>', md_content) |
||||
|
html_content = markdown.markdown(md_content) |
||||
|
return metadata, html_content |
||||
|
|
||||
|
def create_list(self): |
||||
|
template = self.env.get_template('list.html') |
||||
|
self.check_output_dir() |
||||
|
html_output = template.render( |
||||
|
tags=self.tags, |
||||
|
pages=self.pages |
||||
|
) |
||||
|
with open(os.path.join('public', 'list.html'), 'w', encoding='utf8') as output_file: |
||||
|
output_file.write(html_output) |
||||
|
|
||||
|
def fetch_tags(self): |
||||
|
for page in self.pages: |
||||
|
page_tags = page.get_tags() |
||||
|
for tag in page_tags: |
||||
|
if tag not in self.tags: |
||||
|
self.tags.append(tag) |
||||
|
|
||||
|
def build_about(self): |
||||
|
template = self.env.get_template('about.html') |
||||
|
html_output = template.render( |
||||
|
content=self.about_content, |
||||
|
socials=self.about_meta |
||||
|
) |
||||
|
with open(os.path.join('public', 'about.html'), 'w', encoding='utf8') as output_file: |
||||
|
output_file.write(html_output) |
||||
|
|
||||
|
def format_content(self, content): |
||||
|
# convert all (link)(src) to <a> tags |
||||
|
content = re.sub(r'\(([^)]+)\)\[([^\]]+)\]', r'<a href="\2">\1</a>', content) |
||||
|
return content |
||||
|
|
||||
|
def fetch_all_images(self): |
||||
|
dir = 'public/images' |
||||
|
template = self.env.get_template('gallery.html') |
||||
|
image_extensions = ('.png', '.jpg', '.jpeg', '.gif', '.webp') |
||||
|
for dirpath, _, filename in os.walk(dir): |
||||
|
for filename in filename: |
||||
|
if filename.lower().endswith(image_extensions): |
||||
|
relative_path = os.path.relpath(dirpath, dir).replace("\\", "/") |
||||
|
image_path = os.path.join('/images', relative_path, filename).replace("\\", "/") |
||||
|
if image_path.startswith("/images/."): |
||||
|
image_path = image_path.replace("/images/.", "/images") |
||||
|
self.all_images.append(image_path) |
||||
|
|
||||
|
html_output = template.render( |
||||
|
images=self.all_images |
||||
|
) |
||||
|
with open(os.path.join('public', 'gallery.html'), 'w', encoding='utf8') as output_file: |
||||
|
output_file.write(html_output) |
||||
|
|
||||
|
def create_json(self, json_file): |
||||
|
page_info_list = [] |
||||
|
for page in self.pages: |
||||
|
page_info = { |
||||
|
'name': page.get_title(), |
||||
|
'filename': '/articles/' + page.get_filename(), |
||||
|
'image': page.get_image() |
||||
|
} |
||||
|
page_info_list.append(page_info) |
||||
|
|
||||
|
with open(json_file, 'w', encoding='utf8') as f: |
||||
|
json.dump(page_info_list, f, ensure_ascii=False, indent=4) |
||||
|
|
||||
|
class Page: |
||||
|
def __init__(self, metadata, md_content, filename): |
||||
|
self.metadata = metadata |
||||
|
self.content = md_content |
||||
|
self.filename = filename |
||||
|
self.title = self.metadata['title'] |
||||
|
self.type = self.metadata['type'] |
||||
|
self.year = self.metadata['year'] |
||||
|
self.image_src = self.metadata['image'] |
||||
|
self.tags = self.metadata['tags'] |
||||
|
self.date = self.metadata['date'] |
||||
|
self.showcase = self.metadata.get('showcase', []) |
||||
|
self.credits = self.metadata.get('credits', {}) |
||||
|
self.references = self.metadata.get('references', []) |
||||
|
self.isDraft = self.metadata['draft'] |
||||
|
|
||||
|
def get_metadata(self): |
||||
|
return self.metadata |
||||
|
|
||||
|
def get_content(self): |
||||
|
return self.content |
||||
|
|
||||
|
def get_filename(self): |
||||
|
return self.filename |
||||
|
|
||||
|
def get_title(self): |
||||
|
return self.title |
||||
|
|
||||
|
def get_tags(self): |
||||
|
return self.tags |
||||
|
|
||||
|
def get_year(self): |
||||
|
return self.year |
||||
|
|
||||
|
def get_image(self): |
||||
|
return self.image_src |
||||
|
|
||||
|
def get_references(self): |
||||
|
return self.references |
||||
|
|
||||
|
def get_src(self): |
||||
|
return os.path.splitext(self.filename)[0] |
||||
|
|
||||
|
def display_metadata(self): |
||||
|
output = f"Title: {self.title}\nYear: {self.year}\nDate: {self.date}\nTags: {self.tags}\nType: {self.type}" |
||||
|
print(output) |
||||
|
|
||||
|
def main(): |
||||
|
inst = Website('templates', 'public') |
||||
|
inst.build() |
||||
|
|
||||
|
if __name__ == "__main__": |
||||
|
main() |
@ -0,0 +1,12 @@ |
|||||
|
--- |
||||
|
cv: some-path |
||||
|
instagram: https://www.instagram.com/cailean.finn/ |
||||
|
git: https://git.fioruil.ie/ |
||||
|
--- |
||||
|
Cailean Finn is a Media Artist and Creative Technologist from Waterford, Ireland. His practice is centred around investigating computational design, histories, and processes embedded within socio-technological systems. |
||||
|
|
||||
|
Cailean holds a BSc in Computer Science (2019), and a MA in Art & Technology (2022) from the University of Limerick. |
||||
|
|
||||
|
In his work, Cailean explores not only the technical aspects of technology, but also uses it as a tool to highlight the hidden processes and idiosyncratic nature of the human-machine relationship. In doing so, he hopes to reimagine and explore speculative realities that show the potential of emerging technology to be (re)shaped and (re)defined - ranging from Artificial Intelligence, to Creative Coding, and Computer Graphics. |
||||
|
|
||||
|
Currently, he is exploring emergent behaviours of virtual life through Evolutionary Computation and Reinforcement Learning. Additionally, he is experimenting with simulations as a medium for these investigations into embodied intelligence, and its potential impact on various ecologies, as it slowly becomes more ubiquitous. Cailean is also a member of CONCEPTNULL, a community-based organisation, which runs a bi-monthly newsletter and hosts events with a focus on New Media Art in Ireland. |
@ -0,0 +1,21 @@ |
|||||
|
--- |
||||
|
title: (O)MACHINE |
||||
|
type: Project |
||||
|
year: 2022 |
||||
|
image: o-machine.png |
||||
|
tags: [AI, NLP, Simulation] |
||||
|
date: 2024-06-07 |
||||
|
showcase: |
||||
|
- name: Speak It Now Eat It, Revision Performing Arts Festival |
||||
|
year: 2023 |
||||
|
location: Belfast |
||||
|
credits: |
||||
|
Eoin O'Sullivan: Sound design |
||||
|
references: |
||||
|
- title: some-title |
||||
|
link: https://www.caileanfinn.ie |
||||
|
draft: false |
||||
|
--- |
||||
|
(O)MACHINE is a real-time generative performance that employs contemporary machine learning algorithms to explore how we humanise technologies.The architecture of this system was designed to emulate our stream of consciousness, where the machine is trapped in this perpetual cycle through processes of reflection and feedback. As questions begin to arise around the sentience or ‘intelligence’ of these thinking machines, it has become even more important to explore our relationship with machines, and how it continues to evolve. By engaging with its output, it positions artificial intelligence as both a subject and tool. Through this approach, we may begin to expand the dynamics of this connection through new methods of collaboration. From this interaction, we can continue to learn more about how these systems function, how they think, if they even think at all, or can it help us think? |
||||
|
|
||||
|
Sound design by (Eoin O'Sullivan)[https://eoin-osullivan.bandcamp.com/] |
@ -0,0 +1,32 @@ |
|||||
|
--- |
||||
|
title: AI x Body |
||||
|
type: Publication |
||||
|
year: 2022 |
||||
|
image: aixbody.webp |
||||
|
tags: [HPE, AI] |
||||
|
date: 2024-06-07 |
||||
|
showcase: |
||||
|
- name: Exposed Torino Foto Festival |
||||
|
year: 2024 |
||||
|
location: Turin |
||||
|
- name: Another Showcase Festival |
||||
|
year: 2025 |
||||
|
location: Rome |
||||
|
credits: |
||||
|
Cailean: CT |
||||
|
Leon: Camera |
||||
|
references: |
||||
|
- title: some-title |
||||
|
link: https://www.caileanfinn.ie |
||||
|
draft: false |
||||
|
--- |
||||
|
This publication was created in collaboration with AIxDesign, as part of their AI Playground (S01) which ran from May 2022-February 2023. |
||||
|
|
||||
|
The text explores the evolution of human pose estimation and recognition technologies through tracing their historical development, their contemporary applications, and how artists and creative practitioners have employed such tools in their artistic process. |
||||
|
|
||||
|
(Article 📎)[https://nadiapiet.notion.site/AIxDesign-s-Guide-to-AI-x-Body-26ea1c78f253425a92f9269895ea6f46] |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
@ -0,0 +1,31 @@ |
|||||
|
--- |
||||
|
title: Beauty and The Beep |
||||
|
type: Project |
||||
|
year: 2024 |
||||
|
image: beep.png |
||||
|
tags: [RL, AI, Unity, Simulation] |
||||
|
date: 2024-06-07 |
||||
|
showcase: |
||||
|
- name: Exposed Torino Foto Festival |
||||
|
year: 2024 |
||||
|
location: Turin |
||||
|
- name: Another Showcase Festival |
||||
|
year: 2025 |
||||
|
location: Rome |
||||
|
credits: |
||||
|
Cailean: CT |
||||
|
Leon: Camera |
||||
|
references: |
||||
|
- title: some-title |
||||
|
link: https://www.caileanfinn.ie |
||||
|
draft: false |
||||
|
--- |
||||
|
|
||||
|
Exploring the consequences of cohabiting with computer vision, (Simone Niquille’s)[https://www.technofle.sh/] ( ᐛ )و Beauty and The Beep follows Bertil, a chair that is trying to find a place to sit. Inspired by the enchanted household objects from the fairy tale Beauty and The Beast, the film is set in a suburban home instead of a castle, and the beast has been replaced by the continuous notification sounds of smart devices. In the film, Bertil navigates through a virtual house — a recreation of the model home built by the robotics company Boston Dynamics in 2016 to showcase their robot dog SpotMini. |
||||
|
|
||||
|
Wondering who would buy an automated mechanical pet to assist and live in their home, the film explores Boston Dynamics' datafied definition of a home or what it takes for such a personal and intimate space to be standardised for computer vision to function. Bertil — a synthetic chair inspired by IKEA’s first 3D rendered image for their print catalogue, which marked their shift to rendered imagery — wanders through this seemingly simple virtual home, interacting with its objects, in search of some answers. Navigating the home for Bertil is no easy task, as they encounter the daily life noise that is littered throughout the home. A banana trips them, they cannot sit, they get stuck on a treadmill and why is there a toy pony on the floor? Revealing how the impossibility of gathering training data in the home has led to the widespread use of synthetic data, Bertil reminds us that the home is private and not for capture. |
||||
|
|
||||
|
For this work, I collaborated with Simone C Niquille as a Creative Technologist. In the process of creating Beauty and The Beep, the chair was trained using reinforcement learning alogrthims in the Unity game engine. The training process took inspiration from Boston Dynamic's approach in the training of their SpotMini, as well as tradiontional (DeepMimic)[https://www.youtube.com/watch?v=vppFvq2quQ0] environments for Reinforcement Learning research. We chose to use Unity for this project, as it allowed us to work with the (ML-Agents Package)[https://github.com/Unity-Technologies/ml-agents] - an experimental Reinforcement Learning framework, which wraps complex reinforcement learning algorithms/methods into components which are more acessible for developers. Even though this package has been forgotten by Unity, for the most part, working with a user-friendly game engine was key in creating simuated environments for the 🪑 to explore. |
||||
|
|
||||
|
|
||||
|
|
@ -0,0 +1,27 @@ |
|||||
|
--- |
||||
|
title: The BIG D.A.T.A Interview |
||||
|
year: 2023 |
||||
|
image: data.png |
||||
|
type: Project |
||||
|
tags: [Web, ML, p5] |
||||
|
date: 2024-06-07 |
||||
|
showcase: |
||||
|
- name: Exposed Torino Foto Festival |
||||
|
year: 2024 |
||||
|
location: Turin |
||||
|
- name: Another Showcase Festival |
||||
|
year: 2025 |
||||
|
location: Rome |
||||
|
credits: |
||||
|
Cailean: CT |
||||
|
Leon: Camera |
||||
|
references: |
||||
|
- title: some-title |
||||
|
link: https://www.caileanfinn.ie |
||||
|
draft: false |
||||
|
--- |
||||
|
(🔗)[http://conceptnull.org/data] In 2023, Concept Null had the pleasure to chat with Paul, Tom, and Aisling, who lead the Dublin Art & Technology Association (D.A.T.A). Since 2022, D.A.T.A has been a hub for artists, makers, and thinkers to exchange ideas on digital culture in Ireland. During the conversation, D.A.T.A explored it's identity, evolution, and the intricacies of event curation and organisation. |
||||
|
|
||||
|
The website presents the interview in both linear and non-linear formats. By utilising machine learning and natural language processing, text segments extracted from the interview were ranked against key topics; creating a higher-dimensional understanding, and projection of the interview - which is commonly referred to as the latent space. After, a t-SNE algorithm was applied to high-dimensional space, flattening it into two dimensions, represented in the interactive map; allowing the user to navigate the interview from the perspective of the machine. |
||||
|
|
||||
|
Designed and developed using p5js, by Cailean Finn. |
@ -0,0 +1,24 @@ |
|||||
|
--- |
||||
|
title: Dwelling |
||||
|
year: 2023 |
||||
|
type: Project |
||||
|
image: dwelling.png |
||||
|
tags: [Unity, Performance, VFX] |
||||
|
draft: false |
||||
|
date: 2024-06-07 |
||||
|
showcase: |
||||
|
- name: Beta Festival, Project Arts Center |
||||
|
year: 2023 |
||||
|
location: Dublin |
||||
|
credits: |
||||
|
Cailean: CT |
||||
|
Leon: Camera |
||||
|
references: |
||||
|
- title: some-title |
||||
|
link: https://www.caileanfinn.ie |
||||
|
--- |
||||
|
Dwelling is a dynamic live performance and theatre installation created by (Peter Power)[https://peterpower.ie/] and (Leon Butler)[https://bold.ie/]. The performance explores the periphery of cultural isolation, and the dispersal of self across the multimedial, delving into themes of digital mortality, transformation, and rebirth. The performance takes place in the fragments of a home with dance performances by Robyn Byrne and Rosie Stebbing. The characters moves between the digital and real space through motion capture data in conjunction with live tracking. Over the duration of the performance, Rosie starts to form a connection between her physical self, and the digital divide. |
||||
|
|
||||
|
The virtual world was created entirely within Unity. Data was captured from Robyn's movement through various methods, such as the Perception Neuron mo-cap suit, as well as emerging monocular 3d human pose detection models. Unity's particle system was used extensively in the project, converting point cloud and positional data into emergent movement, and ethereal landscapes. |
||||
|
|
||||
|
|
@ -0,0 +1,25 @@ |
|||||
|
--- |
||||
|
title: The Electronic Image, An Object of Time and Energy |
||||
|
year: 2021 |
||||
|
type: Project |
||||
|
image: electronic-image.png |
||||
|
tags: [Virtualisation, Video-Synthesis, MaxMSP, Jitter] |
||||
|
date: 2024-06-07 |
||||
|
showcase: |
||||
|
- name: The Limerick Show, Ormston House |
||||
|
year: 2022 |
||||
|
location: Limerick |
||||
|
credits: |
||||
|
Cailean: CT |
||||
|
Leon: Camera |
||||
|
references: |
||||
|
- title: some-title |
||||
|
link: https://www.caileanfinn.ie |
||||
|
draft: false |
||||
|
--- |
||||
|
This video series comprises of three individual studies, namely Embedded Energy, Electronic Phase, and Omnidirectional Objects, with each video study exploring an inherent characteristic of the video signal that reflects the key phases of the development in the evolution of the medium’s structural, temporal and spatial capabilities. Created as part of my Thesis “The Electronic Image: An Object of Time and Energy” in Art and Technology MA, University of Limerick, Ireland. |
||||
|
|
||||
|
The three studies have been shaped by the experimental processes, techniques, and philosophies of the pioneering artists working with video. The artists in question, specifically the works of Steina and Woody Vasulka, who were driven by their yearning to understand the electronic signal and to formulate an electronic lexicon. The work, in its entirety, is an investigation of the unique set of “codes” embedded within the language of the video signal, consequently, recognising the electronic image as an object of time, energy, and it's programmable building element – the waveform. |
||||
|
|
||||
|
(📎thesis.pdf)[./assets/pdfs/thesis.pdf] |
||||
|
|
@ -0,0 +1,24 @@ |
|||||
|
--- |
||||
|
title: Latent Mirror |
||||
|
year: 2022 |
||||
|
type: Project |
||||
|
image: latent-mirror.png |
||||
|
tags: [Performance, AI, DeepFakes, TD] |
||||
|
date: 2024-06-07 |
||||
|
showcase: |
||||
|
- name: Sound & Portraits, Imagine Arts Fesitval, WGOA |
||||
|
year: 2022 |
||||
|
location: Wateford |
||||
|
credits: |
||||
|
Cailean: CT |
||||
|
Leon: Camera |
||||
|
references: |
||||
|
- title: some-title |
||||
|
link: https://www.caileanfinn.ie |
||||
|
draft: false |
||||
|
--- |
||||
|
This audio-visual performance was created in response to the 'Portraits: People & Place' exhibition at the WGOA (Waterford Gallery of Art). The performance explored the role portraits hold in the digital age, and how our perception of the 'subject' or 'sitter' has in some ways changed to facilitate virtual interactions. For this performance, I collaborated with local sound artist and producer (Evan Miles)[https://www.instagram.com/theevanmiles/], to produce visuals in response to his music. Our aim was to understand what meaning has been lost or gained during this digital conversion, and in what ways can we re-imagine our digital identity through sound, and video. |
||||
|
|
||||
|
The visual element of the performance was real-time and audio reactive, which captured the facial structure of the performing sound artist. By utilising Machine Learning Models, the captured face was manipulated and distorted further to animate another portrait, in an attempt to deconstruct and isolate key compositional elements of the 'subject'. Through this work, we hoped to reflect on our digital identity, and highlight the disconnection between our physical and virtual presence. |
||||
|
|
||||
|
Created in TouchDesigner. |
@ -0,0 +1,26 @@ |
|||||
|
--- |
||||
|
title: Undefined Panorama |
||||
|
year: 2022 |
||||
|
type: Project |
||||
|
image: undefined-panorama.png |
||||
|
tags: [Web, Creative-Coding] |
||||
|
date: 2024-06-07 |
||||
|
showcase: |
||||
|
- name: Exceptional Times, Uncertain Moves, Seo-Seoul Museum of Art |
||||
|
year: 2022 |
||||
|
location: Seoul |
||||
|
credits: |
||||
|
Cailean: CT |
||||
|
Leon: Camera |
||||
|
references: |
||||
|
- title: some-title |
||||
|
link: https://www.caileanfinn.ie |
||||
|
draft: false |
||||
|
--- |
||||
|
Undefined Panorama is a project by Yang Ah Ham in collaboration with, (The Laboratory of Manuel Bürger)[https://manuelbuerger.com/], Cailean Finn, and (Nora O Murchú)[http://www.noraomurchu.com/]. The work was made with the support of curator SungMin LJ, assistant researcher Parr Geng, coordinator Yena Ku. |
||||
|
|
||||
|
Undefined Panorama (2018–present) explores socio-political infrastructures and systems, and the relations embedded within them. This continuously evolving project is based on research collected by the artist Yang Ah Ham as she observes how people deal with hardship generated by the impact of globalisation, societal crisis, inequality, economics, and politics in their lives. The aim of the project is to observe how society is organised and aims to ask: What possibilities are there for social structures based on care and solidarity? |
||||
|
|
||||
|
The online version of Undefined Panorama allows people to move between micro and macro perspectives of global, national and local events. In moving between these scales, Yang Ah Ham aims to open up questions about our relations to these events, and to generate new meanings by altering the scale of observation. |
||||
|
|
||||
|
This website was commissioned by 2022 Seo-Seoul Museum of Art Pre-opening Public Program Exceptional Times, Uncertain Moves, and created with support from the Arts Council Korea. |
@ -0,0 +1,17 @@ |
|||||
|
{ |
||||
|
"name": "threejs-app", |
||||
|
"version": "1.0.0", |
||||
|
"description": "A simple Three.js application", |
||||
|
"main": "server.js", |
||||
|
"scripts": { |
||||
|
"start": "node server.js" |
||||
|
}, |
||||
|
"dependencies": { |
||||
|
"express": "^4.17.1", |
||||
|
"three": "^0.164.1" |
||||
|
}, |
||||
|
"devDependencies": {}, |
||||
|
"keywords": [], |
||||
|
"author": "", |
||||
|
"license": "ISC" |
||||
|
} |
@ -0,0 +1,56 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<title>cailean.finn</title> |
||||
|
<link rel="preconnect" href="https://fonts.googleapis.com"> |
||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet"> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=Gothic+A1&display=swap" rel="stylesheet"> |
||||
|
<link rel="stylesheet" href="/css/styles.css"> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="main-container"> |
||||
|
<div id="nav-container"> |
||||
|
<div class="nav-emoji"> |
||||
|
<a href="/"><div class="emoji-flip">(ง•_•)ง</div></a> |
||||
|
</div> |
||||
|
<div class="nav-about"><a href="/about">cailean.finn</a></div> |
||||
|
<div class="nav-emoji"><a href="/list">list</a></div> |
||||
|
<div class="nav-emoji"><a href='/gallery'>gallery</a></div> |
||||
|
</div> |
||||
|
|
||||
|
<div id="container"> |
||||
|
<div id="project-container"> |
||||
|
<div id="project-header">ABOUT</div> |
||||
|
<div id="project-body"> |
||||
|
<p>Cailean Finn is a Media Artist and Creative Technologist from Waterford, Ireland. His practice is centred around investigating computational design, histories, and processes embedded within socio-technological systems.</p> |
||||
|
<p>Cailean holds a BSc in Computer Science (2019), and a MA in Art & Technology (2022) from the University of Limerick.</p> |
||||
|
<p>In his work, Cailean explores not only the technical aspects of technology, but also uses it as a tool to highlight the hidden processes and idiosyncratic nature of the human-machine relationship. In doing so, he hopes to reimagine and explore speculative realities that show the potential of emerging technology to be (re)shaped and (re)defined - ranging from Artificial Intelligence, to Creative Coding, and Computer Graphics.</p> |
||||
|
<p>Currently, he is exploring emergent behaviours of virtual life through Evolutionary Computation and Reinforcement Learning. Additionally, he is experimenting with simulations as a medium for these investigations into embodied intelligence, and its potential impact on various ecologies, as it slowly becomes more ubiquitous. Cailean is also a member of CONCEPTNULL, a community-based organisation, which runs a bi-monthly newsletter and hosts events with a focus on New Media Art in Ireland.</p> |
||||
|
</div> |
||||
|
<div id="social-container"> |
||||
|
<div>[email protected]</div> |
||||
|
|
||||
|
<div>cv <a href="some-path" target="_blank">↯</a></div> |
||||
|
|
||||
|
<div>instagram <a href="https://www.instagram.com/cailean.finn/" target="_blank">↯</a></div> |
||||
|
|
||||
|
<div>git <a href="https://git.fioruil.ie/" target="_blank">↯</a></div> |
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<a><span id="monster"> |
||||
|
<span id="fire-1">🔥</span> |
||||
|
<span>🔥╰(#°Д°)╯🔥</span> |
||||
|
<span id="fire-3">🔥</span> |
||||
|
</span> |
||||
|
</a> |
||||
|
</div> |
||||
|
<script src="js/mob.js"></script> |
||||
|
|
||||
|
</div> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,59 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<title>cailean.finn</title> |
||||
|
<link rel="preconnect" href="https://fonts.googleapis.com"> |
||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet"> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=Gothic+A1&display=swap" rel="stylesheet"> |
||||
|
<link rel="stylesheet" href="/css/styles.css"> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="main-container"> |
||||
|
<div id="nav-container"> |
||||
|
<div class="nav-emoji"> |
||||
|
<a href="/"><div class="emoji-flip">(ง•_•)ง</div></a> |
||||
|
</div> |
||||
|
<div class="nav-about"><a href="/about">cailean.finn</a></div> |
||||
|
<div class="nav-emoji"><a href="/list">list</a></div> |
||||
|
<div class="nav-emoji"><a href='/gallery'>gallery</a></div> |
||||
|
</div> |
||||
|
|
||||
|
<div id="container"> |
||||
|
<div id="project-container"> |
||||
|
<div id="project-header">(O)MACHINE, 2022</div> |
||||
|
<div id="project-tags"> |
||||
|
|
||||
|
<div class="project-tag">✳ AI</div> |
||||
|
|
||||
|
<div class="project-tag">✳ NLP</div> |
||||
|
|
||||
|
<div class="project-tag">✳ Simulation</div> |
||||
|
|
||||
|
</div> |
||||
|
<div id="project-cover"> |
||||
|
<img src="/images/o-machine.png"> |
||||
|
</div> |
||||
|
<div id="project-body"> |
||||
|
<p>(O)MACHINE is a real-time generative performance that employs contemporary machine learning algorithms to explore how we humanise technologies.The architecture of this system was designed to emulate our stream of consciousness, where the machine is trapped in this perpetual cycle through processes of reflection and feedback. As questions begin to arise around the sentience or ‘intelligence’ of these thinking machines, it has become even more important to explore our relationship with machines, and how it continues to evolve. By engaging with its output, it positions artificial intelligence as both a subject and tool. Through this approach, we may begin to expand the dynamics of this connection through new methods of collaboration. From this interaction, we can continue to learn more about how these systems function, how they think, if they even think at all, or can it help us think?</p> |
||||
|
<p>Sound design by <a href="https://eoin-osullivan.bandcamp.com/">Eoin O'Sullivan</a></p> |
||||
|
</div> |
||||
|
<div id="project-related"> |
||||
|
<div id="pr-header">References:</div> |
||||
|
<ul id="pr-list"> |
||||
|
|
||||
|
<li><a href='https://www.caileanfinn.ie'>some-title</a></li> |
||||
|
|
||||
|
</ul> |
||||
|
<div id="sleeping-mario"> |
||||
|
<img src="/images/website/mario-sleep-up.gif"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,58 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<title>cailean.finn</title> |
||||
|
<link rel="preconnect" href="https://fonts.googleapis.com"> |
||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet"> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=Gothic+A1&display=swap" rel="stylesheet"> |
||||
|
<link rel="stylesheet" href="/css/styles.css"> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="main-container"> |
||||
|
<div id="nav-container"> |
||||
|
<div class="nav-emoji"> |
||||
|
<a href="/"><div class="emoji-flip">(ง•_•)ง</div></a> |
||||
|
</div> |
||||
|
<div class="nav-about"><a href="/about">cailean.finn</a></div> |
||||
|
<div class="nav-emoji"><a href="/list">list</a></div> |
||||
|
<div class="nav-emoji"><a href='/gallery'>gallery</a></div> |
||||
|
</div> |
||||
|
|
||||
|
<div id="container"> |
||||
|
<div id="project-container"> |
||||
|
<div id="project-header">AI x Body, 2022</div> |
||||
|
<div id="project-tags"> |
||||
|
|
||||
|
<div class="project-tag">✳ HPE</div> |
||||
|
|
||||
|
<div class="project-tag">✳ AI</div> |
||||
|
|
||||
|
</div> |
||||
|
<div id="project-cover"> |
||||
|
<img src="/images/aixbody.webp"> |
||||
|
</div> |
||||
|
<div id="project-body"> |
||||
|
<p>This publication was created in collaboration with AIxDesign, as part of their AI Playground (S01) which ran from May 2022-February 2023.</p> |
||||
|
<p>The text explores the evolution of human pose estimation and recognition technologies through tracing their historical development, their contemporary applications, and how artists and creative practitioners have employed such tools in their artistic process.</p> |
||||
|
<p><a href="https://nadiapiet.notion.site/AIxDesign-s-Guide-to-AI-x-Body-26ea1c78f253425a92f9269895ea6f46">Article 📎</a></p> |
||||
|
</div> |
||||
|
<div id="project-related"> |
||||
|
<div id="pr-header">References:</div> |
||||
|
<ul id="pr-list"> |
||||
|
|
||||
|
<li><a href='https://www.caileanfinn.ie'>some-title</a></li> |
||||
|
|
||||
|
</ul> |
||||
|
<div id="sleeping-mario"> |
||||
|
<img src="/images/website/mario-sleep-up.gif"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,62 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<title>cailean.finn</title> |
||||
|
<link rel="preconnect" href="https://fonts.googleapis.com"> |
||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet"> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=Gothic+A1&display=swap" rel="stylesheet"> |
||||
|
<link rel="stylesheet" href="/css/styles.css"> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="main-container"> |
||||
|
<div id="nav-container"> |
||||
|
<div class="nav-emoji"> |
||||
|
<a href="/"><div class="emoji-flip">(ง•_•)ง</div></a> |
||||
|
</div> |
||||
|
<div class="nav-about"><a href="/about">cailean.finn</a></div> |
||||
|
<div class="nav-emoji"><a href="/list">list</a></div> |
||||
|
<div class="nav-emoji"><a href='/gallery'>gallery</a></div> |
||||
|
</div> |
||||
|
|
||||
|
<div id="container"> |
||||
|
<div id="project-container"> |
||||
|
<div id="project-header">Beauty and The Beep, 2024</div> |
||||
|
<div id="project-tags"> |
||||
|
|
||||
|
<div class="project-tag">✳ RL</div> |
||||
|
|
||||
|
<div class="project-tag">✳ AI</div> |
||||
|
|
||||
|
<div class="project-tag">✳ Unity</div> |
||||
|
|
||||
|
<div class="project-tag">✳ Simulation</div> |
||||
|
|
||||
|
</div> |
||||
|
<div id="project-cover"> |
||||
|
<img src="/images/beep.png"> |
||||
|
</div> |
||||
|
<div id="project-body"> |
||||
|
<p>Exploring the consequences of cohabiting with computer vision, <a href="https://www.technofle.sh/">Simone Niquille’s</a> ( ᐛ )و Beauty and The Beep follows Bertil, a chair that is trying to find a place to sit. Inspired by the enchanted household objects from the fairy tale Beauty and The Beast, the film is set in a suburban home instead of a castle, and the beast has been replaced by the continuous notification sounds of smart devices. In the film, Bertil navigates through a virtual house — a recreation of the model home built by the robotics company Boston Dynamics in 2016 to showcase their robot dog SpotMini.</p> |
||||
|
<p>Wondering who would buy an automated mechanical pet to assist and live in their home, the film explores Boston Dynamics' datafied definition of a home or what it takes for such a personal and intimate space to be standardised for computer vision to function. Bertil — a synthetic chair inspired by IKEA’s first 3D rendered image for their print catalogue, which marked their shift to rendered imagery — wanders through this seemingly simple virtual home, interacting with its objects, in search of some answers. Navigating the home for Bertil is no easy task, as they encounter the daily life noise that is littered throughout the home. A banana trips them, they cannot sit, they get stuck on a treadmill and why is there a toy pony on the floor? Revealing how the impossibility of gathering training data in the home has led to the widespread use of synthetic data, Bertil reminds us that the home is private and not for capture.</p> |
||||
|
<p>For this work, I collaborated with Simone C Niquille as a Creative Technologist. In the process of creating Beauty and The Beep, the chair was trained using reinforcement learning alogrthims in the Unity game engine. The training process took inspiration from Boston Dynamic's approach in the training of their SpotMini, as well as tradiontional <a href="https://www.youtube.com/watch?v=vppFvq2quQ0">DeepMimic</a> environments for Reinforcement Learning research. We chose to use Unity for this project, as it allowed us to work with the <a href="https://github.com/Unity-Technologies/ml-agents">ML-Agents Package</a> - an experimental Reinforcement Learning framework, which wraps complex reinforcement learning algorithms/methods into components which are more acessible for developers. Even though this package has been forgotten by Unity, for the most part, working with a user-friendly game engine was key in creating simuated environments for the 🪑 to explore. </p> |
||||
|
</div> |
||||
|
<div id="project-related"> |
||||
|
<div id="pr-header">References:</div> |
||||
|
<ul id="pr-list"> |
||||
|
|
||||
|
<li><a href='https://www.caileanfinn.ie'>some-title</a></li> |
||||
|
|
||||
|
</ul> |
||||
|
<div id="sleeping-mario"> |
||||
|
<img src="/images/website/mario-sleep-up.gif"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,60 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<title>cailean.finn</title> |
||||
|
<link rel="preconnect" href="https://fonts.googleapis.com"> |
||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet"> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=Gothic+A1&display=swap" rel="stylesheet"> |
||||
|
<link rel="stylesheet" href="/css/styles.css"> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="main-container"> |
||||
|
<div id="nav-container"> |
||||
|
<div class="nav-emoji"> |
||||
|
<a href="/"><div class="emoji-flip">(ง•_•)ง</div></a> |
||||
|
</div> |
||||
|
<div class="nav-about"><a href="/about">cailean.finn</a></div> |
||||
|
<div class="nav-emoji"><a href="/list">list</a></div> |
||||
|
<div class="nav-emoji"><a href='/gallery'>gallery</a></div> |
||||
|
</div> |
||||
|
|
||||
|
<div id="container"> |
||||
|
<div id="project-container"> |
||||
|
<div id="project-header">The BIG D.A.T.A Interview, 2023</div> |
||||
|
<div id="project-tags"> |
||||
|
|
||||
|
<div class="project-tag">✳ Web</div> |
||||
|
|
||||
|
<div class="project-tag">✳ ML</div> |
||||
|
|
||||
|
<div class="project-tag">✳ p5</div> |
||||
|
|
||||
|
</div> |
||||
|
<div id="project-cover"> |
||||
|
<img src="/images/data.png"> |
||||
|
</div> |
||||
|
<div id="project-body"> |
||||
|
<p><a href="http://conceptnull.org/data">🔗</a> In 2023, Concept Null had the pleasure to chat with Paul, Tom, and Aisling, who lead the Dublin Art & Technology Association (D.A.T.A). Since 2022, D.A.T.A has been a hub for artists, makers, and thinkers to exchange ideas on digital culture in Ireland. During the conversation, D.A.T.A explored it's identity, evolution, and the intricacies of event curation and organisation. </p> |
||||
|
<p>The website presents the interview in both linear and non-linear formats. By utilising machine learning and natural language processing, text segments extracted from the interview were ranked against key topics; creating a higher-dimensional understanding, and projection of the interview - which is commonly referred to as the latent space. After, a t-SNE algorithm was applied to high-dimensional space, flattening it into two dimensions, represented in the interactive map; allowing the user to navigate the interview from the perspective of the machine.</p> |
||||
|
<p>Designed and developed using p5js, by Cailean Finn.</p> |
||||
|
</div> |
||||
|
<div id="project-related"> |
||||
|
<div id="pr-header">References:</div> |
||||
|
<ul id="pr-list"> |
||||
|
|
||||
|
<li><a href='https://www.caileanfinn.ie'>some-title</a></li> |
||||
|
|
||||
|
</ul> |
||||
|
<div id="sleeping-mario"> |
||||
|
<img src="/images/website/mario-sleep-up.gif"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,59 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<title>cailean.finn</title> |
||||
|
<link rel="preconnect" href="https://fonts.googleapis.com"> |
||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet"> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=Gothic+A1&display=swap" rel="stylesheet"> |
||||
|
<link rel="stylesheet" href="/css/styles.css"> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="main-container"> |
||||
|
<div id="nav-container"> |
||||
|
<div class="nav-emoji"> |
||||
|
<a href="/"><div class="emoji-flip">(ง•_•)ง</div></a> |
||||
|
</div> |
||||
|
<div class="nav-about"><a href="/about">cailean.finn</a></div> |
||||
|
<div class="nav-emoji"><a href="/list">list</a></div> |
||||
|
<div class="nav-emoji"><a href='/gallery'>gallery</a></div> |
||||
|
</div> |
||||
|
|
||||
|
<div id="container"> |
||||
|
<div id="project-container"> |
||||
|
<div id="project-header">Dwelling, 2023</div> |
||||
|
<div id="project-tags"> |
||||
|
|
||||
|
<div class="project-tag">✳ Unity</div> |
||||
|
|
||||
|
<div class="project-tag">✳ Performance</div> |
||||
|
|
||||
|
<div class="project-tag">✳ VFX</div> |
||||
|
|
||||
|
</div> |
||||
|
<div id="project-cover"> |
||||
|
<img src="/images/dwelling.png"> |
||||
|
</div> |
||||
|
<div id="project-body"> |
||||
|
<p>Dwelling is a dynamic live performance and theatre installation created by <a href="https://peterpower.ie/">Peter Power</a> and <a href="https://bold.ie/">Leon Butler</a>. The performance explores the periphery of cultural isolation, and the dispersal of self across the multimedial, delving into themes of digital mortality, transformation, and rebirth. The performance takes place in the fragments of a home with dance performances by Robyn Byrne and Rosie Stebbing. The characters moves between the digital and real space through motion capture data in conjunction with live tracking. Over the duration of the performance, Rosie starts to form a connection between her physical self, and the digital divide.</p> |
||||
|
<p>The virtual world was created entirely within Unity. Data was captured from Robyn's movement through various methods, such as the Perception Neuron mo-cap suit, as well as emerging monocular 3d human pose detection models. Unity's particle system was used extensively in the project, converting point cloud and positional data into emergent movement, and ethereal landscapes.</p> |
||||
|
</div> |
||||
|
<div id="project-related"> |
||||
|
<div id="pr-header">References:</div> |
||||
|
<ul id="pr-list"> |
||||
|
|
||||
|
<li><a href='https://www.caileanfinn.ie'>some-title</a></li> |
||||
|
|
||||
|
</ul> |
||||
|
<div id="sleeping-mario"> |
||||
|
<img src="/images/website/mario-sleep-up.gif"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,62 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<title>cailean.finn</title> |
||||
|
<link rel="preconnect" href="https://fonts.googleapis.com"> |
||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet"> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=Gothic+A1&display=swap" rel="stylesheet"> |
||||
|
<link rel="stylesheet" href="/css/styles.css"> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="main-container"> |
||||
|
<div id="nav-container"> |
||||
|
<div class="nav-emoji"> |
||||
|
<a href="/"><div class="emoji-flip">(ง•_•)ง</div></a> |
||||
|
</div> |
||||
|
<div class="nav-about"><a href="/about">cailean.finn</a></div> |
||||
|
<div class="nav-emoji"><a href="/list">list</a></div> |
||||
|
<div class="nav-emoji"><a href='/gallery'>gallery</a></div> |
||||
|
</div> |
||||
|
|
||||
|
<div id="container"> |
||||
|
<div id="project-container"> |
||||
|
<div id="project-header">The Electronic Image, An Object of Time and Energy, 2021</div> |
||||
|
<div id="project-tags"> |
||||
|
|
||||
|
<div class="project-tag">✳ Virtualisation</div> |
||||
|
|
||||
|
<div class="project-tag">✳ Video-Synthesis</div> |
||||
|
|
||||
|
<div class="project-tag">✳ MaxMSP</div> |
||||
|
|
||||
|
<div class="project-tag">✳ Jitter</div> |
||||
|
|
||||
|
</div> |
||||
|
<div id="project-cover"> |
||||
|
<img src="/images/electronic-image.png"> |
||||
|
</div> |
||||
|
<div id="project-body"> |
||||
|
<p>This video series comprises of three individual studies, namely Embedded Energy, Electronic Phase, and Omnidirectional Objects, with each video study exploring an inherent characteristic of the video signal that reflects the key phases of the development in the evolution of the medium’s structural, temporal and spatial capabilities. Created as part of my Thesis “The Electronic Image: An Object of Time and Energy” in Art and Technology MA, University of Limerick, Ireland.</p> |
||||
|
<p>The three studies have been shaped by the experimental processes, techniques, and philosophies of the pioneering artists working with video. The artists in question, specifically the works of Steina and Woody Vasulka, who were driven by their yearning to understand the electronic signal and to formulate an electronic lexicon. The work, in its entirety, is an investigation of the unique set of “codes” embedded within the language of the video signal, consequently, recognising the electronic image as an object of time, energy, and it's programmable building element – the waveform.</p> |
||||
|
<p><a href="./assets/pdfs/thesis.pdf">📎thesis.pdf</a> </p> |
||||
|
</div> |
||||
|
<div id="project-related"> |
||||
|
<div id="pr-header">References:</div> |
||||
|
<ul id="pr-list"> |
||||
|
|
||||
|
<li><a href='https://www.caileanfinn.ie'>some-title</a></li> |
||||
|
|
||||
|
</ul> |
||||
|
<div id="sleeping-mario"> |
||||
|
<img src="/images/website/mario-sleep-up.gif"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,62 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<title>cailean.finn</title> |
||||
|
<link rel="preconnect" href="https://fonts.googleapis.com"> |
||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet"> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=Gothic+A1&display=swap" rel="stylesheet"> |
||||
|
<link rel="stylesheet" href="/css/styles.css"> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="main-container"> |
||||
|
<div id="nav-container"> |
||||
|
<div class="nav-emoji"> |
||||
|
<a href="/"><div class="emoji-flip">(ง•_•)ง</div></a> |
||||
|
</div> |
||||
|
<div class="nav-about"><a href="/about">cailean.finn</a></div> |
||||
|
<div class="nav-emoji"><a href="/list">list</a></div> |
||||
|
<div class="nav-emoji"><a href='/gallery'>gallery</a></div> |
||||
|
</div> |
||||
|
|
||||
|
<div id="container"> |
||||
|
<div id="project-container"> |
||||
|
<div id="project-header">Latent Mirror, 2022</div> |
||||
|
<div id="project-tags"> |
||||
|
|
||||
|
<div class="project-tag">✳ Performance</div> |
||||
|
|
||||
|
<div class="project-tag">✳ AI</div> |
||||
|
|
||||
|
<div class="project-tag">✳ DeepFakes</div> |
||||
|
|
||||
|
<div class="project-tag">✳ TD</div> |
||||
|
|
||||
|
</div> |
||||
|
<div id="project-cover"> |
||||
|
<img src="/images/latent-mirror.png"> |
||||
|
</div> |
||||
|
<div id="project-body"> |
||||
|
<p>This audio-visual performance was created in response to the 'Portraits: People & Place' exhibition at the WGOA (Waterford Gallery of Art). The performance explored the role portraits hold in the digital age, and how our perception of the 'subject' or 'sitter' has in some ways changed to facilitate virtual interactions. For this performance, I collaborated with local sound artist and producer <a href="https://www.instagram.com/theevanmiles/">Evan Miles</a>, to produce visuals in response to his music. Our aim was to understand what meaning has been lost or gained during this digital conversion, and in what ways can we re-imagine our digital identity through sound, and video.</p> |
||||
|
<p>The visual element of the performance was real-time and audio reactive, which captured the facial structure of the performing sound artist. By utilising Machine Learning Models, the captured face was manipulated and distorted further to animate another portrait, in an attempt to deconstruct and isolate key compositional elements of the 'subject'. Through this work, we hoped to reflect on our digital identity, and highlight the disconnection between our physical and virtual presence.</p> |
||||
|
<p>Created in TouchDesigner.</p> |
||||
|
</div> |
||||
|
<div id="project-related"> |
||||
|
<div id="pr-header">References:</div> |
||||
|
<ul id="pr-list"> |
||||
|
|
||||
|
<li><a href='https://www.caileanfinn.ie'>some-title</a></li> |
||||
|
|
||||
|
</ul> |
||||
|
<div id="sleeping-mario"> |
||||
|
<img src="/images/website/mario-sleep-up.gif"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,59 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<title>cailean.finn</title> |
||||
|
<link rel="preconnect" href="https://fonts.googleapis.com"> |
||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet"> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=Gothic+A1&display=swap" rel="stylesheet"> |
||||
|
<link rel="stylesheet" href="/css/styles.css"> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="main-container"> |
||||
|
<div id="nav-container"> |
||||
|
<div class="nav-emoji"> |
||||
|
<a href="/"><div class="emoji-flip">(ง•_•)ง</div></a> |
||||
|
</div> |
||||
|
<div class="nav-about"><a href="/about">cailean.finn</a></div> |
||||
|
<div class="nav-emoji"><a href="/list">list</a></div> |
||||
|
<div class="nav-emoji"><a href='/gallery'>gallery</a></div> |
||||
|
</div> |
||||
|
|
||||
|
<div id="container"> |
||||
|
<div id="project-container"> |
||||
|
<div id="project-header">Undefined Panorama, 2022</div> |
||||
|
<div id="project-tags"> |
||||
|
|
||||
|
<div class="project-tag">✳ Web</div> |
||||
|
|
||||
|
<div class="project-tag">✳ Creative-Coding</div> |
||||
|
|
||||
|
</div> |
||||
|
<div id="project-cover"> |
||||
|
<img src="/images/undefined-panorama.png"> |
||||
|
</div> |
||||
|
<div id="project-body"> |
||||
|
<p>Undefined Panorama is a project by Yang Ah Ham in collaboration with, <a href="https://manuelbuerger.com/">The Laboratory of Manuel Bürger</a>, Cailean Finn, and <a href="http://www.noraomurchu.com/">Nora O Murchú</a>. The work was made with the support of curator SungMin LJ, assistant researcher Parr Geng, coordinator Yena Ku.</p> |
||||
|
<p>Undefined Panorama (2018–present) explores socio-political infrastructures and systems, and the relations embedded within them. This continuously evolving project is based on research collected by the artist Yang Ah Ham as she observes how people deal with hardship generated by the impact of globalisation, societal crisis, inequality, economics, and politics in their lives. The aim of the project is to observe how society is organised and aims to ask: What possibilities are there for social structures based on care and solidarity?</p> |
||||
|
<p>The online version of Undefined Panorama allows people to move between micro and macro perspectives of global, national and local events. In moving between these scales, Yang Ah Ham aims to open up questions about our relations to these events, and to generate new meanings by altering the scale of observation.</p> |
||||
|
<p>This website was commissioned by 2022 Seo-Seoul Museum of Art Pre-opening Public Program Exceptional Times, Uncertain Moves, and created with support from the Arts Council Korea.</p> |
||||
|
</div> |
||||
|
<div id="project-related"> |
||||
|
<div id="pr-header">References:</div> |
||||
|
<ul id="pr-list"> |
||||
|
|
||||
|
<li><a href='https://www.caileanfinn.ie'>some-title</a></li> |
||||
|
|
||||
|
</ul> |
||||
|
<div id="sleeping-mario"> |
||||
|
<img src="/images/website/mario-sleep-up.gif"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,566 @@ |
|||||
|
@font-face { |
||||
|
font-family: 'Cotham'; |
||||
|
src: url('../fonts/CothamSans.otf') format('opentype'); |
||||
|
font-weight: normal; |
||||
|
font-style: normal; |
||||
|
} |
||||
|
|
||||
|
@font-face { |
||||
|
font-family: 'Redacted'; |
||||
|
src: url('../fonts/Redaction_70-Italic.woff2') format('woff2'); |
||||
|
font-display: fallback; |
||||
|
} |
||||
|
|
||||
|
@font-face { |
||||
|
font-family: 'Redacted Reg'; |
||||
|
src: url('../fonts/Redaction_70-Regular.woff2') format('woff2'); |
||||
|
font-display: fallback; |
||||
|
} |
||||
|
|
||||
|
body { |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
overflow: hidden; |
||||
|
font-family: 'IBM Plex Mono', monospace; |
||||
|
background-color: whitesmoke; |
||||
|
} |
||||
|
|
||||
|
#container-gallery { |
||||
|
position: absolute; |
||||
|
top:100px; |
||||
|
left: 0; |
||||
|
z-index: -999; |
||||
|
} |
||||
|
|
||||
|
#project-container-gallery { |
||||
|
flex-grow: 1; |
||||
|
width: 100%; |
||||
|
font-family: "Gothic A1", sans-serif; |
||||
|
font-weight: 400; |
||||
|
font-style: normal; |
||||
|
line-height: 30px; |
||||
|
font-size: 20px; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
overflow: hidden; |
||||
|
gap: 20px; |
||||
|
z-index: 100; |
||||
|
background: url('/images/website/skybox.png'); |
||||
|
background-size: cover; |
||||
|
background-repeat: repeat-x; |
||||
|
background-position: 0 0; |
||||
|
} |
||||
|
|
||||
|
#image-gallery { |
||||
|
overflow-y: scroll; |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
align-items: center; |
||||
|
row-gap: 20px; |
||||
|
padding-left: 25px; |
||||
|
padding-right: 25px; |
||||
|
} |
||||
|
|
||||
|
.gallery-image{ |
||||
|
max-width: 400px; |
||||
|
height: auto; |
||||
|
margin-left: auto; |
||||
|
margin-right: auto; |
||||
|
animation: turn 1s steps(2, end) infinite; |
||||
|
border: 2px solid rgb(245, 102, 102); |
||||
|
} |
||||
|
|
||||
|
.gallery-image:hover { |
||||
|
animation: swiv 5s steps(2, end) infinite; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
.image-container { |
||||
|
width: 100%; |
||||
|
height: 50px; |
||||
|
} |
||||
|
|
||||
|
#focused-image-container { |
||||
|
display: none; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
flex-direction: column; |
||||
|
text-align: center; |
||||
|
align-items: center; |
||||
|
gap: 20px; |
||||
|
z-index: 200; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.focused-image { |
||||
|
height: 600px; |
||||
|
width: auto; |
||||
|
margin-left: auto; |
||||
|
margin-right: auto; |
||||
|
} |
||||
|
|
||||
|
#close-button { |
||||
|
width: fit-content; |
||||
|
color: rgb(255,250,149); |
||||
|
font-size: 40px; |
||||
|
animation: rotate 5s steps(15, end) infinite; |
||||
|
} |
||||
|
|
||||
|
#main-container { |
||||
|
width: 100vw; |
||||
|
height: 100vh; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
overflow: auto; |
||||
|
} |
||||
|
|
||||
|
#nav-container { |
||||
|
height: 100px; |
||||
|
background-color: rgb(255, 250, 149); |
||||
|
flex-shrink: 0; |
||||
|
display: flex; /* Use Flexbox for alignment */ |
||||
|
flex-direction: row; |
||||
|
gap: 20px; |
||||
|
padding-left: 25px; |
||||
|
padding-right: 35px; |
||||
|
align-items: center; /* Center content vertically */ |
||||
|
color: rgb(0, 0, 0); /* Optional: text color for visibility */ |
||||
|
font-size: 24px; /* Optional: font size */ |
||||
|
border: 2px dotted black; |
||||
|
font-style: italic; |
||||
|
font-weight: 400; |
||||
|
overflow-x: auto; |
||||
|
z-index: 99999; |
||||
|
} |
||||
|
|
||||
|
#nav-container::-webkit-scrollbar { |
||||
|
/* for Chrome, Safari, and Opera */ |
||||
|
} |
||||
|
|
||||
|
.nav-emoji { |
||||
|
font-style: normal; |
||||
|
font-family:system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; |
||||
|
cursor: pointer; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
} |
||||
|
|
||||
|
.nav-emoji a { |
||||
|
text-decoration: none; |
||||
|
color: black; |
||||
|
cursor: crosshair; |
||||
|
} |
||||
|
|
||||
|
.nav-emoji .emoji-flip { |
||||
|
animation: flip 1s steps(1, end) infinite; |
||||
|
} |
||||
|
|
||||
|
.nav-about a{ |
||||
|
color:blue; |
||||
|
text-decoration: underline; |
||||
|
} |
||||
|
|
||||
|
#container { |
||||
|
width: 100%; |
||||
|
flex-grow: 1; |
||||
|
display: flex; |
||||
|
overflow-y: scroll; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
#project-container { |
||||
|
color: black; |
||||
|
flex-grow: 1; |
||||
|
margin: 25px; |
||||
|
width: 60%; |
||||
|
font-family: "Gothic A1", sans-serif; |
||||
|
font-weight: 400; |
||||
|
font-style: normal; |
||||
|
line-height: 30px; |
||||
|
font-size: 20px; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 20px; |
||||
|
z-index: 100; |
||||
|
} |
||||
|
|
||||
|
#list-container { |
||||
|
color: black; |
||||
|
flex-grow: 1; |
||||
|
width: 100%; |
||||
|
font-family: "Gothic A1", sans-serif; |
||||
|
font-weight: 400; |
||||
|
font-style: normal; |
||||
|
line-height: 30px; |
||||
|
font-size: 20px; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 20px; |
||||
|
z-index: 100; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
#list-container h2{ |
||||
|
font-family: 'Redacted Reg'; |
||||
|
letter-spacing: 2px; |
||||
|
font-size: 40px; |
||||
|
color: rgb(245, 102, 102); |
||||
|
line-height: 50px; |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
.article-image { |
||||
|
width: 100%; |
||||
|
height: 200px; |
||||
|
overflow: hidden; |
||||
|
position: relative; |
||||
|
} |
||||
|
|
||||
|
.article-image img { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
object-fit: cover; |
||||
|
position: absolute; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
} |
||||
|
|
||||
|
.articles { |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
align-items: stretch; |
||||
|
gap: 50px; |
||||
|
justify-content: center; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
.filter { |
||||
|
margin: 25px; |
||||
|
} |
||||
|
|
||||
|
.article { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
width: 500px; |
||||
|
height: 400px; |
||||
|
gap:10px; |
||||
|
} |
||||
|
|
||||
|
.article #project-tags{ |
||||
|
} |
||||
|
|
||||
|
#clouds { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
position: absolute; |
||||
|
z-index: -999; |
||||
|
} |
||||
|
|
||||
|
.cloud { |
||||
|
position: absolute; |
||||
|
top: 50%; |
||||
|
z-index: -99; |
||||
|
animation: cloud linear 20s infinite; |
||||
|
width: 100px; |
||||
|
} |
||||
|
|
||||
|
.cloud-gif { |
||||
|
position: absolute; |
||||
|
top: 45%; |
||||
|
z-index: -99; |
||||
|
animation: cloud linear 22s infinite; |
||||
|
} |
||||
|
|
||||
|
.cloud-gif img{ |
||||
|
width: 200px; |
||||
|
height: auto; |
||||
|
} |
||||
|
|
||||
|
.cloud1 { |
||||
|
position: absolute; |
||||
|
top: 67%; |
||||
|
z-index: -99; |
||||
|
animation: cloud linear 25s infinite; |
||||
|
width: 100px; |
||||
|
} |
||||
|
|
||||
|
.cloud2 { |
||||
|
position: absolute; |
||||
|
top: 25%; |
||||
|
z-index: -99; |
||||
|
animation: cloud linear 30s infinite; |
||||
|
width: 100px; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
#project-header { |
||||
|
font-family: 'Redacted Reg'; |
||||
|
letter-spacing: 2px; |
||||
|
font-size: 60px; |
||||
|
margin-top: 25px; |
||||
|
/* margin-bottom: 25px; */ |
||||
|
color: rgb(245, 102, 102); |
||||
|
height: fit-content; |
||||
|
line-height: initial; |
||||
|
} |
||||
|
|
||||
|
#project-tags { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
row-gap: 5px; |
||||
|
column-gap: 10px; |
||||
|
flex-wrap: wrap; |
||||
|
color: rgb(73, 146, 248); |
||||
|
text-transform: uppercase; |
||||
|
font-family: 'Redacted Reg'; |
||||
|
font-size: 21px; |
||||
|
} |
||||
|
|
||||
|
.project-tag { |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
#project-body a{ |
||||
|
color: rgb(73, 146, 248); |
||||
|
text-decoration: none; |
||||
|
font-family: 'IBM Plex Mono', monospace; |
||||
|
font-size: 17.5px; |
||||
|
padding-left: 2.5px; |
||||
|
padding-right: 2.5px; |
||||
|
font-style: italic; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
#project-cover img{ |
||||
|
/* border: 2px solid rgb(255 149 149); */ |
||||
|
width: 100%; |
||||
|
height: auto; |
||||
|
} |
||||
|
|
||||
|
#pr-header { |
||||
|
font-family: 'Redacted Reg'; |
||||
|
letter-spacing: 2px; |
||||
|
font-size: 25px; |
||||
|
color: rgb(245, 102, 102); |
||||
|
height: fit-content; |
||||
|
margin-bottom: 25px; |
||||
|
} |
||||
|
|
||||
|
#pr-list { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
gap: 10px; |
||||
|
} |
||||
|
|
||||
|
#pr-list ul { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
#pr-list a{ |
||||
|
color: rgb(73, 146, 248); |
||||
|
text-decoration: none; |
||||
|
font-family: 'IBM Plex Mono', monospace; |
||||
|
font-size: 17.5px; |
||||
|
padding-left: 2.5px; |
||||
|
padding-right: 2.5px; |
||||
|
font-style: italic; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
img:hover { |
||||
|
animation: turn 1s steps(2, end) infinite; |
||||
|
} |
||||
|
|
||||
|
#social-container { |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
flex-direction: row; |
||||
|
gap: 20px; |
||||
|
font-family: 'IBM Plex Mono', monospace; |
||||
|
font-style: italic; |
||||
|
color: rgb(0, 0, 0); |
||||
|
} |
||||
|
|
||||
|
#social-container a{ |
||||
|
color: rgb(73, 146, 248); |
||||
|
text-decoration: none; |
||||
|
font-style: normal; |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
#chat { |
||||
|
position: absolute; |
||||
|
bottom: 25px; |
||||
|
right: 25px; |
||||
|
z-index: 100; |
||||
|
font-size: 24px; |
||||
|
} |
||||
|
|
||||
|
#chat a{ |
||||
|
text-decoration: none; |
||||
|
} |
||||
|
|
||||
|
#monster { |
||||
|
position: absolute; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
flex-wrap: wrap; |
||||
|
gap: 10px; |
||||
|
top: 0px; |
||||
|
left: 0px; |
||||
|
color: rgb(245, 102, 102); |
||||
|
font-size: 24px; |
||||
|
animation: flip 1s steps(1, end) infinite; |
||||
|
z-index: 500; |
||||
|
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; |
||||
|
} |
||||
|
|
||||
|
#main-container a{ |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
#sleeping-mario { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
#sleeping-mario img{ |
||||
|
width: 10%; |
||||
|
height: auto; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
@keyframes turn { |
||||
|
0% { |
||||
|
transform: rotateY(0deg); |
||||
|
} |
||||
|
50% { |
||||
|
transform: rotateY(10deg); |
||||
|
} |
||||
|
100% { |
||||
|
transform: rotateY(0deg); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@keyframes rotate { |
||||
|
0% { |
||||
|
transform: rotateZ(0deg); |
||||
|
} |
||||
|
50% { |
||||
|
transform: rotateZ(180deg); |
||||
|
} |
||||
|
100% { |
||||
|
transform: rotateZ(359deg); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@keyframes flip { |
||||
|
0% { |
||||
|
transform: rotateY(0deg); |
||||
|
} |
||||
|
50% { |
||||
|
transform: rotateY(180deg); |
||||
|
} |
||||
|
100% { |
||||
|
transform: rotateY(360deg); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@keyframes swiv { |
||||
|
0% { |
||||
|
transform: rotateZ(0deg); |
||||
|
transform: scale(110%); |
||||
|
} |
||||
|
25% { |
||||
|
transform: rotateZ(10deg); |
||||
|
} |
||||
|
50% { |
||||
|
transform: rotateZ(0deg); |
||||
|
transform: scale(100%); |
||||
|
} |
||||
|
75% { |
||||
|
transform: rotateZ(-10deg); |
||||
|
} |
||||
|
100% { |
||||
|
transform: rotateZ(0deg); |
||||
|
transform: scale(110%); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@keyframes cloud { |
||||
|
0% { |
||||
|
left:-700px; |
||||
|
} |
||||
|
|
||||
|
100% { |
||||
|
left: calc(100vw + 335px); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media only screen and (max-width: 768px) { |
||||
|
.article { |
||||
|
height: auto; |
||||
|
width: 80%; |
||||
|
} |
||||
|
|
||||
|
#list-container { |
||||
|
padding-bottom: 25px; |
||||
|
} |
||||
|
|
||||
|
#project-container { |
||||
|
width: 90%; |
||||
|
line-height: 20px; |
||||
|
font-size: 15px; |
||||
|
gap: 10px; |
||||
|
} |
||||
|
|
||||
|
#project-header { |
||||
|
font-size: 30px; |
||||
|
} |
||||
|
|
||||
|
.focused-image { |
||||
|
height: auto; |
||||
|
width: 90%; |
||||
|
} |
||||
|
|
||||
|
#project-tags { |
||||
|
row-gap: 5px; |
||||
|
column-gap: 10px; |
||||
|
font-size: 15px; |
||||
|
} |
||||
|
|
||||
|
#pr-header { |
||||
|
font-family: 'Redacted Reg'; |
||||
|
letter-spacing: 2px; |
||||
|
font-size: 20px; |
||||
|
margin-bottom: 25px; |
||||
|
} |
||||
|
|
||||
|
#pr-list a { |
||||
|
font-size: 15px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@media only screen and (max-width: 480px) { |
||||
|
#list-container h2 { |
||||
|
letter-spacing: 1px; |
||||
|
font-size: 20px; |
||||
|
line-height: 25px; |
||||
|
} |
||||
|
|
||||
|
#project-tags { |
||||
|
row-gap: 2.5px; |
||||
|
column-gap: 5px; |
||||
|
font-size: 12px; |
||||
|
} |
||||
|
|
||||
|
.gallery-image{ |
||||
|
max-width: 90%; |
||||
|
} |
||||
|
} |
@ -0,0 +1,67 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<title>cailean.finn</title> |
||||
|
<link rel="preconnect" href="https://fonts.googleapis.com"> |
||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet"> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=Gothic+A1&display=swap" rel="stylesheet"> |
||||
|
<link rel="stylesheet" href="/css/styles.css"> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="main-container"> |
||||
|
<div id="nav-container"> |
||||
|
<div class="nav-emoji"> |
||||
|
<a href="/"><div class="emoji-flip">(ง•_•)ง</div></a> |
||||
|
</div> |
||||
|
<div class="nav-about"><a href="/about">cailean.finn</a></div> |
||||
|
<div class="nav-emoji"><a href="/list">list</a></div> |
||||
|
<div class="nav-emoji"><a href='/gallery'>gallery</a></div> |
||||
|
</div> |
||||
|
|
||||
|
<div id="project-container-gallery"> |
||||
|
<div id="image-gallery"> |
||||
|
<div class="image-container"></div> |
||||
|
|
||||
|
<img class="gallery-image" src="/images/aixbody.webp"> |
||||
|
|
||||
|
<img class="gallery-image" src="/images/beep.png"> |
||||
|
|
||||
|
<img class="gallery-image" src="/images/data.png"> |
||||
|
|
||||
|
<img class="gallery-image" src="/images/dwelling.png"> |
||||
|
|
||||
|
<img class="gallery-image" src="/images/electronic-image.png"> |
||||
|
|
||||
|
<img class="gallery-image" src="/images/latent-mirror.png"> |
||||
|
|
||||
|
<img class="gallery-image" src="/images/o-machine.png"> |
||||
|
|
||||
|
<img class="gallery-image" src="/images/undefined-panorama.png"> |
||||
|
|
||||
|
<img class="gallery-image" src="/images/website/checker.png"> |
||||
|
|
||||
|
<img class="gallery-image" src="/images/website/lakitu.gif"> |
||||
|
|
||||
|
<img class="gallery-image" src="/images/website/mario-sleep-up.gif"> |
||||
|
|
||||
|
<img class="gallery-image" src="/images/website/skybox.png"> |
||||
|
|
||||
|
<div class="image-container"></div> |
||||
|
</div> |
||||
|
<div id="focused-image-container"> |
||||
|
<div id="close-button"><a>☀</a></div> |
||||
|
<img class="focused-image" src="/images/website/lakitu.gif"> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div id="chat"><a href="/campfire">📬</a></div> |
||||
|
</div> |
||||
|
|
||||
|
<script src="js/skybox.js"></script> |
||||
|
|
||||
|
</div> |
||||
|
</body> |
||||
|
</html> |
After Width: | Height: | Size: 107 KiB |
After Width: | Height: | Size: 2.3 MiB |
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 3.2 MiB |
After Width: | Height: | Size: 127 KiB |
After Width: | Height: | Size: 370 KiB |
After Width: | Height: | Size: 181 KiB |
After Width: | Height: | Size: 294 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 451 KiB |
After Width: | Height: | Size: 164 KiB |
After Width: | Height: | Size: 90 KiB |
@ -0,0 +1,36 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<title>cailean.finn</title> |
||||
|
<link rel="preconnect" href="https://fonts.googleapis.com"> |
||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet"> |
||||
|
<link rel="stylesheet" href="./css/styles.css"> |
||||
|
<script type="importmap"> |
||||
|
{ |
||||
|
"imports": { |
||||
|
"three": "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js", |
||||
|
"three/addons/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/", |
||||
|
"three/src/": "https://cdn.jsdelivr.net/npm/[email protected]/src/", |
||||
|
"three/examples/": "https://cdn.jsdelivr.net/npm/[email protected]/examples/" |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="main-container"> |
||||
|
<div id="nav-container"> |
||||
|
<div class="nav-emoji"> |
||||
|
<a href="/"><div class="emoji-flip">(ง•_•)ง</div></a> |
||||
|
</div> |
||||
|
<div class="nav-about"><a href="/about">cailean.finn</a></div> |
||||
|
<div class="nav-emoji"><a href="/list">list</a></div> |
||||
|
<div class="nav-emoji"><a href='/gallery'>gallery</a></div> |
||||
|
</div> |
||||
|
<div id="container"></div> |
||||
|
</div> |
||||
|
<script type="module" src="./js/main.js"></script> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,450 @@ |
|||||
|
import * as THREE from 'three'; |
||||
|
import { FontLoader } from 'three/addons/loaders/FontLoader.js'; |
||||
|
import { Font } from 'three/examples/jsm/loaders/FontLoader.js'; |
||||
|
import { TextGeometry } from 'three/addons/geometries/TextGeometry.js'; |
||||
|
import { lerp, randFloat } from 'three/src/math/MathUtils.js'; |
||||
|
import { OrbitControls } from 'three/examples/jsm/Addons.js'; |
||||
|
import { depth } from 'three/examples/jsm/nodes/Nodes.js'; |
||||
|
|
||||
|
class PickHelper { |
||||
|
|
||||
|
constructor() { |
||||
|
this.raycaster = new THREE.Raycaster(); |
||||
|
this.pickedObject = null; |
||||
|
this.lastObjectPicked = null; |
||||
|
this.sameObjectPicked = false; |
||||
|
} |
||||
|
|
||||
|
pick(normalizedPosition, scene, camera, time) { |
||||
|
// restore the color if there is a picked object
|
||||
|
if (this.pickedObject) { |
||||
|
this.lastObjectPicked = this.pickedObject; |
||||
|
this.sameObjectPicked = false; |
||||
|
this.pickedObject = undefined; |
||||
|
} |
||||
|
|
||||
|
// cast a ray through the frustum
|
||||
|
this.raycaster.setFromCamera(normalizedPosition, camera); |
||||
|
// get the list of objects the ray intersected
|
||||
|
const intersectedObjects = this.raycaster.intersectObjects(scene.children); |
||||
|
if (intersectedObjects.length) { |
||||
|
// pick the first object. It's the closest one
|
||||
|
for (let i = 0; i < intersectedObjects.length; i++){ |
||||
|
|
||||
|
if(intersectedObjects[i].object.geometry.type != "SphereGeometry"){ |
||||
|
this.pickedObject = intersectedObjects[i].object; |
||||
|
if(intersectedObjects[i].object == this.lastObjectPicked) |
||||
|
this.sameObjectPicked = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (this.sameObjectPicked) |
||||
|
this.pickedObject = this.lastObjectPicked; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
|
|
||||
|
class Article { |
||||
|
|
||||
|
constructor(texture, title, filename, id){ |
||||
|
this.geom = new THREE.TetrahedronGeometry(1, 2); |
||||
|
this.material = new THREE.MeshLambertMaterial({map: texture}); |
||||
|
this.mesh = new THREE.Mesh(this.geom, this.material); |
||||
|
this.html = filename; |
||||
|
this.name = title; |
||||
|
this.id = id; |
||||
|
this.hover = false; |
||||
|
this.hoverScale = false; |
||||
|
this.hoverLerpTime = 0; |
||||
|
this.speed = randFloat(1.5, 2); |
||||
|
this.rotationSpeed = randFloat(0.5, 1.5); |
||||
|
this.scale = 1; |
||||
|
} |
||||
|
|
||||
|
AddToScene(scene, aspect){ |
||||
|
this.mesh.position.x = (Math.random() - 0.5) * 8 * aspect; |
||||
|
this.mesh.position.y = 8 + Math.random() * 15; |
||||
|
scene.add(this.mesh); |
||||
|
} |
||||
|
|
||||
|
UpdateRotation(time){ |
||||
|
this.mesh.rotation.x += this.rotationSpeed * time; |
||||
|
this.mesh.rotation.y += this.rotationSpeed * time; |
||||
|
} |
||||
|
|
||||
|
UpdatePosition(time, picker){ |
||||
|
this.BoundsCheck(); |
||||
|
this.HoverCheck(picker, time) |
||||
|
if (!this.hover) |
||||
|
this.mesh.position.y -= this.speed * time; |
||||
|
} |
||||
|
|
||||
|
BoundsCheck(){ |
||||
|
if (this.mesh.position.y < -8) { |
||||
|
const aspect = window.innerWidth / (window.innerHeight - 100); |
||||
|
this.mesh.position.y = 8 + (Math.random() * 3); |
||||
|
this.mesh.position.x = (Math.random() - 0.5) * 8 * aspect; |
||||
|
this.speed = randFloat(1.5, 2) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
UpdateScale(multipler){ |
||||
|
this.mesh.scale.set(multipler, multipler, multipler); |
||||
|
} |
||||
|
|
||||
|
HoverCheck(picker, time){ |
||||
|
const hoverIncrement = 0.01; |
||||
|
const offHoverDecrement = 0.01; |
||||
|
|
||||
|
if (picker.pickedObject === this.mesh) { |
||||
|
this.hover = true; |
||||
|
if (this.hoverLerpTime < 1) { |
||||
|
this.hoverLerpTime += hoverIncrement; |
||||
|
} |
||||
|
if (this.hoverLerpTime > 1) { |
||||
|
this.hoverLerpTime = 1; |
||||
|
} |
||||
|
this.scale = lerp(this.scale, 2, this.hoverLerpTime); |
||||
|
} else { |
||||
|
this.hover = false; |
||||
|
if (this.hoverLerpTime > 0) { |
||||
|
this.hoverLerpTime -= offHoverDecrement; |
||||
|
} |
||||
|
if (this.hoverLerpTime < 0) { |
||||
|
this.hoverLerpTime = 0; |
||||
|
} |
||||
|
this.scale = lerp(this.scale, 1, 1 - this.hoverLerpTime); |
||||
|
} |
||||
|
|
||||
|
this.UpdateScale(this.scale); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
} |
||||
|
|
||||
|
let scene, camera, renderer, cube; |
||||
|
let texture, planeMat, mesh, moloch_txt; |
||||
|
let lastTime = 0; // Keep track of the last frame time
|
||||
|
let textGeo, textWidth, textMaterial, textMesh; |
||||
|
let text_Geometries = []; |
||||
|
const pickPosition = {x: 0, y: 0}; |
||||
|
const pickHelper = new PickHelper(); |
||||
|
const object_list = [] |
||||
|
const object_count = 20; |
||||
|
const fontLoader = new FontLoader(); |
||||
|
|
||||
|
function init() { |
||||
|
|
||||
|
// Texture Loader
|
||||
|
const loader = new THREE.TextureLoader(); |
||||
|
texture = loader.load('/images/website/checker.png'); |
||||
|
|
||||
|
// Create a renderer and attach it to our document
|
||||
|
renderer = new THREE.WebGLRenderer({ antialias: true }); |
||||
|
renderer.shadowMap.enabled = true; |
||||
|
renderer.setSize(window.innerWidth, window.innerHeight - 100); |
||||
|
document.getElementById('container').appendChild(renderer.domElement); |
||||
|
document.getElementById('container').style.overflowY = 'hidden'; |
||||
|
|
||||
|
// Create the scene
|
||||
|
scene = new THREE.Scene(); |
||||
|
scene.background = new THREE.Color('black'); |
||||
|
|
||||
|
// Camera Setup
|
||||
|
const aspect = window.innerWidth / (window.innerHeight - 100); // Adjust for nav height
|
||||
|
const frustumSize = 10; |
||||
|
camera = new THREE.OrthographicCamera( |
||||
|
frustumSize * aspect / -2, |
||||
|
frustumSize * aspect / 2, |
||||
|
frustumSize / 2, |
||||
|
frustumSize / -2, |
||||
|
0.1, |
||||
|
5000 |
||||
|
); |
||||
|
|
||||
|
//const controls = new OrbitControls(camera, renderer.domElement);
|
||||
|
|
||||
|
camera.position.z = 20; |
||||
|
|
||||
|
// Fetch JSON data
|
||||
|
fetch('../json/articles.json') |
||||
|
.then(response => response.json()) |
||||
|
.then(jsonData => { |
||||
|
for (let i = 0; i < jsonData.length; i++) { |
||||
|
let temp_txt = loader.load('../images/' + jsonData[i]['image']); |
||||
|
temp_txt.minFilter = THREE.NearestFilter; |
||||
|
let title = jsonData[i]['name'] |
||||
|
let filename = jsonData[i]['filename'] |
||||
|
let article = new Article(temp_txt, title, filename, i); |
||||
|
article.AddToScene(scene, aspect); |
||||
|
object_list.push(article); |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
|
||||
|
|
||||
|
// Plane Setup
|
||||
|
{ |
||||
|
const planeSize = 40; |
||||
|
|
||||
|
|
||||
|
texture.wrapS = THREE.RepeatWrapping; |
||||
|
texture.wrapT = THREE.RepeatWrapping; |
||||
|
texture.magFilter = THREE.NearestFilter; |
||||
|
texture.colorSpace = THREE.SRGBColorSpace; |
||||
|
const repeats = planeSize / 1; |
||||
|
texture.repeat.set(repeats, repeats); |
||||
|
|
||||
|
const planeGeo = new THREE.SphereGeometry(10); |
||||
|
planeMat = new THREE.MeshPhongMaterial({ |
||||
|
map: texture, |
||||
|
side: THREE.DoubleSide, |
||||
|
}); |
||||
|
|
||||
|
mesh = new THREE.Mesh(planeGeo, planeMat); |
||||
|
mesh.position.z = -20 |
||||
|
scene.add(mesh); |
||||
|
} |
||||
|
|
||||
|
// Light Setup
|
||||
|
{ |
||||
|
const color = 0xFFFFFF; |
||||
|
const intensity = 3; |
||||
|
const light = new THREE.DirectionalLight(color, intensity); |
||||
|
light.castShadow = true; |
||||
|
light.position.set(1, 1, 10); |
||||
|
light.target.position.set(-0, 0, -0); |
||||
|
light.shadow.camera.top = 25; |
||||
|
light.shadow.camera.bottom = -25; |
||||
|
light.shadow.camera.left = -25; |
||||
|
light.shadow.camera.right = 25; |
||||
|
light.shadow.camera.zoom = 1; |
||||
|
scene.add(light); |
||||
|
scene.add(light.target); |
||||
|
} |
||||
|
|
||||
|
// Start with an initial timestamp
|
||||
|
animate(0); |
||||
|
} |
||||
|
|
||||
|
function animate(time) { |
||||
|
requestAnimationFrame(animate); |
||||
|
|
||||
|
// Calculate the time elapsed since the last frame
|
||||
|
const deltaTime = (time - lastTime) / 1000; // Convert time to seconds
|
||||
|
lastTime = time; |
||||
|
|
||||
|
pickHelper.pick(pickPosition, scene, camera, time); |
||||
|
|
||||
|
for (let i = 0; i < object_list.length; i++){ |
||||
|
object_list[i].UpdateRotation(deltaTime); |
||||
|
object_list[i].UpdatePosition(deltaTime, pickHelper); |
||||
|
} |
||||
|
|
||||
|
// Update the plane texture offset
|
||||
|
const scrollSpeed = 0.2; |
||||
|
planeMat.map.offset.y += scrollSpeed * deltaTime; |
||||
|
planeMat.map.offset.x += scrollSpeed / 0.75 * deltaTime; |
||||
|
|
||||
|
ChangeCursor(); |
||||
|
// Render the scene from the perspective of the camera
|
||||
|
renderer.render(scene, camera); |
||||
|
} |
||||
|
|
||||
|
// Handle window resize
|
||||
|
window.addEventListener('resize', () => { |
||||
|
const aspect = window.innerWidth / (window.innerHeight - 100); |
||||
|
const frustumSize = 10; |
||||
|
camera.left = -frustumSize * aspect / 2; |
||||
|
camera.right = frustumSize * aspect / 2; |
||||
|
camera.top = frustumSize / 2; |
||||
|
camera.bottom = -frustumSize / 2; |
||||
|
camera.updateProjectionMatrix(); |
||||
|
renderer.setPixelRatio(window.devicePixelRatio); |
||||
|
renderer.setSize(window.innerWidth, window.innerHeight - 100); |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
|
||||
|
function getCanvasRelativePosition(event) { |
||||
|
const rect = document.querySelector('#container').getBoundingClientRect(); |
||||
|
return { |
||||
|
x: (event.clientX - rect.left) * window.innerWidth / rect.width, |
||||
|
y: (event.clientY - rect.top ) * (window.innerHeight - 100) / rect.height, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
function setPickPosition(event) { |
||||
|
const pos = getCanvasRelativePosition(event); |
||||
|
pickPosition.x = (pos.x / window.innerWidth ) * 2 - 1; |
||||
|
pickPosition.y = (pos.y / ( window.innerHeight-100 ) ) * -2 + 1; // note we flip Y
|
||||
|
} |
||||
|
|
||||
|
function clearPickPosition() { |
||||
|
// unlike the mouse which always has a position
|
||||
|
// if the user stops touching the screen we want
|
||||
|
// to stop picking. For now we just pick a value
|
||||
|
// unlikely to pick something
|
||||
|
pickPosition.x = -100000; |
||||
|
pickPosition.y = -100000; |
||||
|
} |
||||
|
|
||||
|
function objectClicked(event) { |
||||
|
if (pickHelper.pickedObject) { |
||||
|
// Find the corresponding Article object
|
||||
|
const pickedArticle = object_list.find(article => article.mesh === pickHelper.pickedObject); |
||||
|
if (pickedArticle) { |
||||
|
window.location.href = pickedArticle.html; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function ChangeCursor(){ |
||||
|
const pickedArticle = object_list.find(article => article.mesh === pickHelper.pickedObject); |
||||
|
if (pickedArticle) { |
||||
|
document.body.style.cursor = 'pointer'; |
||||
|
UpdateText(pickedArticle.name); |
||||
|
}else{ |
||||
|
document.body.style.cursor = 'default'; |
||||
|
UpdateText(""); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function UpdateText(text) { |
||||
|
MeasureText(text); |
||||
|
} |
||||
|
|
||||
|
function ClearTextGeoList() { |
||||
|
for (let i = 0; i < text_Geometries.length; i++) { |
||||
|
scene.remove(text_Geometries[i]); |
||||
|
} |
||||
|
text_Geometries.length = 0; |
||||
|
} |
||||
|
|
||||
|
function MeasureText(text) { |
||||
|
fontLoader.load('fonts/Redaction 50_Regular.json', (font) => { |
||||
|
let initialFontSize = 1; |
||||
|
if (window.innerWidth < 1024) { |
||||
|
initialFontSize = 0.5; |
||||
|
} else if (window.innerWidth < 512) { |
||||
|
initialFontSize = 0.25; |
||||
|
} |
||||
|
|
||||
|
const aspect = window.innerWidth / (window.innerHeight - 100); // Adjust for nav height
|
||||
|
const frustumSize = 10; |
||||
|
const orthoWidth = (frustumSize * aspect / 2) - (frustumSize * aspect / -2); |
||||
|
|
||||
|
function createTextGeometry(text, size) { |
||||
|
return new TextGeometry(text, { |
||||
|
height: 2, |
||||
|
depth: 1, |
||||
|
font: font, |
||||
|
size: size |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// Split text into words
|
||||
|
const split = text.split(" "); |
||||
|
const word_count = split.length; |
||||
|
const sentences = []; |
||||
|
|
||||
|
let currentText = ""; |
||||
|
let currentFontSize = initialFontSize; |
||||
|
let textGeo; |
||||
|
|
||||
|
for (let i = 0; i < word_count; i++) { |
||||
|
const testText = currentText + (currentText ? " " : "") + split[i]; |
||||
|
textGeo = createTextGeometry(testText, currentFontSize); |
||||
|
textGeo.computeBoundingBox(); |
||||
|
const proportion = textGeo.boundingBox.max.x / orthoWidth; |
||||
|
|
||||
|
if (proportion > 0.8) { |
||||
|
if (currentText) { |
||||
|
sentences.push(currentText); |
||||
|
} |
||||
|
currentText = split[i]; |
||||
|
} else { |
||||
|
currentText = testText; |
||||
|
} |
||||
|
} |
||||
|
if (currentText) { |
||||
|
sentences.push(currentText); |
||||
|
} |
||||
|
|
||||
|
ClearTextGeoList(); |
||||
|
|
||||
|
const numSentences = sentences.length; |
||||
|
const totalHeight = (numSentences - 1) * (1.5 * currentFontSize); |
||||
|
const startY = totalHeight / 2; |
||||
|
|
||||
|
for (let i = 0; i < sentences.length; i++) { |
||||
|
textGeo = createTextGeometry(sentences[i], currentFontSize); |
||||
|
textGeo.computeBoundingBox(); |
||||
|
const proportion = textGeo.boundingBox.max.x / orthoWidth; |
||||
|
|
||||
|
if (proportion > 0.8) { |
||||
|
currentFontSize *= 0.8 / proportion; |
||||
|
textGeo = createTextGeometry(sentences[i], currentFontSize); |
||||
|
textGeo.computeBoundingBox(); |
||||
|
} |
||||
|
|
||||
|
const textMaterial = new THREE.MeshNormalMaterial(); |
||||
|
const textMesh = new THREE.Mesh(textGeo, textMaterial); |
||||
|
|
||||
|
const centerOffsetX = (textGeo.boundingBox.max.x - textGeo.boundingBox.min.x) / 2; |
||||
|
const centerOffsetY = (textGeo.boundingBox.max.y - textGeo.boundingBox.min.y) / 2; |
||||
|
const centerOffsetZ = (textGeo.boundingBox.max.z - textGeo.boundingBox.min.z) / 2; |
||||
|
|
||||
|
textGeo.translate(-centerOffsetX, -centerOffsetY, -centerOffsetZ); |
||||
|
textMesh.rotation.x = Math.PI / 2 * 0.05; |
||||
|
textMesh.position.y = startY - (i * (1.5 * currentFontSize)); |
||||
|
textMesh.position.z = 5; |
||||
|
text_Geometries.push(textMesh); |
||||
|
} |
||||
|
|
||||
|
for (let i = 0; i < text_Geometries.length; i++) { |
||||
|
scene.add(text_Geometries[i]); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
window.addEventListener('mousemove', setPickPosition); |
||||
|
window.addEventListener('mouseout', clearPickPosition); |
||||
|
window.addEventListener('mouseleave', clearPickPosition); |
||||
|
window.addEventListener('click', objectClicked) |
||||
|
|
||||
|
// Add touch event listeners
|
||||
|
window.addEventListener('touchstart', onTouchStart, {passive: false}); |
||||
|
window.addEventListener('touchmove', onTouchMove, {passive: false}); |
||||
|
window.addEventListener('touchend', onTouchEnd, {passive: false}); |
||||
|
window.addEventListener('touchcancel', clearPickPosition); |
||||
|
|
||||
|
let touchStartTime; |
||||
|
const touchHoldDuration = 500; // Duration in milliseconds to distinguish between tap and hold
|
||||
|
|
||||
|
function onTouchStart(event) { |
||||
|
touchStartTime = Date.now(); |
||||
|
setPickPosition(event.touches[0]); |
||||
|
} |
||||
|
|
||||
|
function onTouchMove(event) { |
||||
|
setPickPosition(event.touches[0]); |
||||
|
} |
||||
|
|
||||
|
function onTouchEnd(event) { |
||||
|
const touchDuration = Date.now() - touchStartTime; |
||||
|
clearPickPosition(); |
||||
|
if (touchDuration < touchHoldDuration) { |
||||
|
// It's a tap
|
||||
|
objectClicked(event); |
||||
|
} else { |
||||
|
// It's a hold
|
||||
|
// Do nothing extra, as hover effect should already be handled by setPickPosition
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Initialize the application
|
||||
|
init(); |
@ -0,0 +1,33 @@ |
|||||
|
const monster = document.getElementById('monster') |
||||
|
let x = window.innerWidth / 2 |
||||
|
let y = window.innerHeight / 2 |
||||
|
let dx = 2.5 |
||||
|
let dy = 2.5 |
||||
|
|
||||
|
|
||||
|
function init(){ |
||||
|
monster.style.top = + "px" |
||||
|
monster.style.left = window.innerWidth / 2 + "px" |
||||
|
movement() |
||||
|
} |
||||
|
|
||||
|
function movement(){ |
||||
|
|
||||
|
if( x > window.innerWidth - 100 || x <= 0 ){ |
||||
|
dx *= -1; |
||||
|
} |
||||
|
|
||||
|
if( y > (window.innerHeight - 100) || y <= 100 ){ |
||||
|
dy *= -1; |
||||
|
} |
||||
|
|
||||
|
x += dx |
||||
|
y += dy |
||||
|
|
||||
|
monster.style.top = y + "px" |
||||
|
monster.style.left = x + "px" |
||||
|
|
||||
|
requestAnimationFrame(movement) |
||||
|
} |
||||
|
|
||||
|
init() |
@ -0,0 +1,17 @@ |
|||||
|
function filterArticles() { |
||||
|
const tagSelect = document.getElementById('tag-select'); |
||||
|
const selectedTag = tagSelect.value; |
||||
|
const articles = document.querySelectorAll('.article'); |
||||
|
|
||||
|
articles.forEach(article => { |
||||
|
const tags = article.getAttribute('data-tags').split(' '); |
||||
|
if (selectedTag === 'all' || tags.includes(selectedTag)) { |
||||
|
article.style.display = 'flex'; |
||||
|
} else { |
||||
|
article.style.display = 'none'; |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
// Initial call to display all articles
|
||||
|
filterArticles(); |
@ -0,0 +1,51 @@ |
|||||
|
const box = document.getElementById('project-container-gallery') |
||||
|
let x, y |
||||
|
|
||||
|
function init(){ |
||||
|
x = 0 |
||||
|
y = 0 |
||||
|
AnimateSkybox() |
||||
|
} |
||||
|
|
||||
|
function AnimateSkybox(){ |
||||
|
x += 1 |
||||
|
|
||||
|
box.style.backgroundPosition = x + "px " + y + "px" |
||||
|
requestAnimationFrame(AnimateSkybox) |
||||
|
} |
||||
|
|
||||
|
// Wait for the DOM to fully load
|
||||
|
document.addEventListener("DOMContentLoaded", function() { |
||||
|
// Get all gallery images
|
||||
|
const galleryImages = document.querySelectorAll("#image-gallery .gallery-image"); |
||||
|
// Get the focused image container and the focused image
|
||||
|
const focusedImageContainer = document.getElementById("focused-image-container"); |
||||
|
const focusedImage = document.querySelector(".focused-image"); |
||||
|
// Get the gallery container
|
||||
|
const galleryContainer = document.getElementById("image-gallery"); |
||||
|
// Get the close button
|
||||
|
const closeButton = document.getElementById("close-button"); |
||||
|
|
||||
|
// Add click event listeners to each gallery image
|
||||
|
galleryImages.forEach(image => { |
||||
|
image.addEventListener("click", function() { |
||||
|
// Hide the gallery
|
||||
|
galleryContainer.style.display = "none"; |
||||
|
// Update the src of the focused image to the src of the clicked image
|
||||
|
focusedImage.src = this.src; |
||||
|
// Show the focused image container
|
||||
|
focusedImageContainer.style.display = "flex"; |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
// Add click event listener to the close button
|
||||
|
closeButton.addEventListener("click", function() { |
||||
|
// Hide the focused image container
|
||||
|
focusedImageContainer.style.display = "none"; |
||||
|
// Show the gallery
|
||||
|
galleryContainer.style.display = "flex"; |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
|
||||
|
init() |
@ -0,0 +1,42 @@ |
|||||
|
[ |
||||
|
{ |
||||
|
"name": "(O)MACHINE", |
||||
|
"filename": "/articles/(o)machine.html", |
||||
|
"image": "o-machine.png" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "AI x Body", |
||||
|
"filename": "/articles/ai-x-body.html", |
||||
|
"image": "aixbody.webp" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "Beauty and The Beep", |
||||
|
"filename": "/articles/beauty-and-the-beep.html", |
||||
|
"image": "beep.png" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "The BIG D.A.T.A Interview", |
||||
|
"filename": "/articles/data.html", |
||||
|
"image": "data.png" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "Dwelling", |
||||
|
"filename": "/articles/dwelling.html", |
||||
|
"image": "dwelling.png" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "The Electronic Image, An Object of Time and Energy", |
||||
|
"filename": "/articles/electronic-image.html", |
||||
|
"image": "electronic-image.png" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "Latent Mirror", |
||||
|
"filename": "/articles/latent-mirror.html", |
||||
|
"image": "latent-mirror.png" |
||||
|
}, |
||||
|
{ |
||||
|
"name": "Undefined Panorama", |
||||
|
"filename": "/articles/undefined.html", |
||||
|
"image": "undefined-panorama.png" |
||||
|
} |
||||
|
] |
@ -0,0 +1,276 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<title>cailean.finn</title> |
||||
|
<link rel="preconnect" href="https://fonts.googleapis.com"> |
||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet"> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=Gothic+A1&display=swap" rel="stylesheet"> |
||||
|
<link rel="stylesheet" href="/css/styles.css"> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="main-container"> |
||||
|
<div id="nav-container"> |
||||
|
<div class="nav-emoji"> |
||||
|
<a href="/"><div class="emoji-flip">(ง•_•)ง</div></a> |
||||
|
</div> |
||||
|
<div class="nav-about"><a href="/about">cailean.finn</a></div> |
||||
|
<div class="nav-emoji"><a href="/list">list</a></div> |
||||
|
<div class="nav-emoji"><a href='/gallery'>gallery</a></div> |
||||
|
</div> |
||||
|
|
||||
|
<div id="container"> |
||||
|
<div id="list-container"> |
||||
|
<div class="filter"> |
||||
|
<label for="tag-select">Filter by tag:</label> |
||||
|
<select id="tag-select" onchange="filterArticles()"> |
||||
|
<option value="all">All</option> |
||||
|
|
||||
|
<option value="AI">AI</option> |
||||
|
|
||||
|
<option value="NLP">NLP</option> |
||||
|
|
||||
|
<option value="Simulation">Simulation</option> |
||||
|
|
||||
|
<option value="HPE">HPE</option> |
||||
|
|
||||
|
<option value="RL">RL</option> |
||||
|
|
||||
|
<option value="Unity">Unity</option> |
||||
|
|
||||
|
<option value="Web">Web</option> |
||||
|
|
||||
|
<option value="ML">ML</option> |
||||
|
|
||||
|
<option value="p5">p5</option> |
||||
|
|
||||
|
<option value="Performance">Performance</option> |
||||
|
|
||||
|
<option value="VFX">VFX</option> |
||||
|
|
||||
|
<option value="Virtualisation">Virtualisation</option> |
||||
|
|
||||
|
<option value="Video-Synthesis">Video-Synthesis</option> |
||||
|
|
||||
|
<option value="MaxMSP">MaxMSP</option> |
||||
|
|
||||
|
<option value="Jitter">Jitter</option> |
||||
|
|
||||
|
<option value="DeepFakes">DeepFakes</option> |
||||
|
|
||||
|
<option value="TD">TD</option> |
||||
|
|
||||
|
<option value="Creative-Coding">Creative-Coding</option> |
||||
|
|
||||
|
</select> |
||||
|
</div> |
||||
|
<div class="articles"> |
||||
|
|
||||
|
<div class="article" data-tags="AI NLP Simulation"> |
||||
|
|
||||
|
<a href= 'articles/(o)machine'><div class="article-image"> |
||||
|
<img src="/images/o-machine.png"> |
||||
|
</div></a> |
||||
|
|
||||
|
<div id="project-tags"> |
||||
|
|
||||
|
<div class="project-tag">✳ AI</div> |
||||
|
|
||||
|
<div class="project-tag">✳ NLP</div> |
||||
|
|
||||
|
<div class="project-tag">✳ Simulation</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<h2>(O)MACHINE</h2> |
||||
|
</div> |
||||
|
|
||||
|
<div class="article" data-tags="HPE AI"> |
||||
|
|
||||
|
<a href= 'articles/ai-x-body'><div class="article-image"> |
||||
|
<img src="/images/aixbody.webp"> |
||||
|
</div></a> |
||||
|
|
||||
|
<div id="project-tags"> |
||||
|
|
||||
|
<div class="project-tag">✳ HPE</div> |
||||
|
|
||||
|
<div class="project-tag">✳ AI</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<h2>AI x Body</h2> |
||||
|
</div> |
||||
|
|
||||
|
<div class="article" data-tags="RL AI Unity Simulation"> |
||||
|
|
||||
|
<a href= 'articles/beauty-and-the-beep'><div class="article-image"> |
||||
|
<img src="/images/beep.png"> |
||||
|
</div></a> |
||||
|
|
||||
|
<div id="project-tags"> |
||||
|
|
||||
|
<div class="project-tag">✳ RL</div> |
||||
|
|
||||
|
<div class="project-tag">✳ AI</div> |
||||
|
|
||||
|
<div class="project-tag">✳ Unity</div> |
||||
|
|
||||
|
<div class="project-tag">✳ Simulation</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<h2>Beauty and The Beep</h2> |
||||
|
</div> |
||||
|
|
||||
|
<div class="article" data-tags="Web ML p5"> |
||||
|
|
||||
|
<a href= 'articles/data'><div class="article-image"> |
||||
|
<img src="/images/data.png"> |
||||
|
</div></a> |
||||
|
|
||||
|
<div id="project-tags"> |
||||
|
|
||||
|
<div class="project-tag">✳ Web</div> |
||||
|
|
||||
|
<div class="project-tag">✳ ML</div> |
||||
|
|
||||
|
<div class="project-tag">✳ p5</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<h2>The BIG D.A.T.A Interview</h2> |
||||
|
</div> |
||||
|
|
||||
|
<div class="article" data-tags="Unity Performance VFX"> |
||||
|
|
||||
|
<a href= 'articles/dwelling'><div class="article-image"> |
||||
|
<img src="/images/dwelling.png"> |
||||
|
</div></a> |
||||
|
|
||||
|
<div id="project-tags"> |
||||
|
|
||||
|
<div class="project-tag">✳ Unity</div> |
||||
|
|
||||
|
<div class="project-tag">✳ Performance</div> |
||||
|
|
||||
|
<div class="project-tag">✳ VFX</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<h2>Dwelling</h2> |
||||
|
</div> |
||||
|
|
||||
|
<div class="article" data-tags="Virtualisation Video-Synthesis MaxMSP Jitter"> |
||||
|
|
||||
|
<a href= 'articles/electronic-image'><div class="article-image"> |
||||
|
<img src="/images/electronic-image.png"> |
||||
|
</div></a> |
||||
|
|
||||
|
<div id="project-tags"> |
||||
|
|
||||
|
<div class="project-tag">✳ Virtualisation</div> |
||||
|
|
||||
|
<div class="project-tag">✳ Video-Synthesis</div> |
||||
|
|
||||
|
<div class="project-tag">✳ MaxMSP</div> |
||||
|
|
||||
|
<div class="project-tag">✳ Jitter</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<h2>The Electronic Image, An Object of Time and Energy</h2> |
||||
|
</div> |
||||
|
|
||||
|
<div class="article" data-tags="Performance AI DeepFakes TD"> |
||||
|
|
||||
|
<a href= 'articles/latent-mirror'><div class="article-image"> |
||||
|
<img src="/images/latent-mirror.png"> |
||||
|
</div></a> |
||||
|
|
||||
|
<div id="project-tags"> |
||||
|
|
||||
|
<div class="project-tag">✳ Performance</div> |
||||
|
|
||||
|
<div class="project-tag">✳ AI</div> |
||||
|
|
||||
|
<div class="project-tag">✳ DeepFakes</div> |
||||
|
|
||||
|
<div class="project-tag">✳ TD</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<h2>Latent Mirror</h2> |
||||
|
</div> |
||||
|
|
||||
|
<div class="article" data-tags="Web Creative-Coding"> |
||||
|
|
||||
|
<a href= 'articles/undefined'><div class="article-image"> |
||||
|
<img src="/images/undefined-panorama.png"> |
||||
|
</div></a> |
||||
|
|
||||
|
<div id="project-tags"> |
||||
|
|
||||
|
<div class="project-tag">✳ Web</div> |
||||
|
|
||||
|
<div class="project-tag">✳ Creative-Coding</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<h2>Undefined Panorama</h2> |
||||
|
</div> |
||||
|
|
||||
|
<div id="clouds"> |
||||
|
<div class="cloud"> |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣶⣶⣶⣶⣶⣶⣤⣤⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⢀⣀⣴⠟⠋⠁⠀⠀⠀⠀⠈⠉⠙⠻⢿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⠛⠿⣿⣛⣿⠿⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣾⣿⣻⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠿⣶⣦⣤⣤⣤⣤⣤⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣟⣛⣛⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⡀⠾⢿⣿⣿⣭⣭⣿⣄⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⣠⣶⡾⠿⠛⠛⠋⠉⠉⠙⠻⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠋⠐⠾⠏⠉⠉⠙⠛⠻⢷⣄⠀⠀⠀ |
||||
|
⠀⠀⢠⣾⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣆⠀ |
||||
|
⣀⣀⣸⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣷ |
||||
|
⣿⡛⠋⠉⢀⣀⠀⠀⠀⣤⡄⠀⠀⠀⣀⣴⢶⣿⣯⢄⣀⣀⣀⡀⠀⢀⣄⣠⣤⣀⠁⠀⠀⢸⣿⣿⣶⠶⣶⡄⠀⠀⢠⣀⣀⡀⣀⣠⣿⣾ |
||||
|
⣯⣿⣿⣦⣽⣭⣷⣦⣤⣼⣿⣶⣴⣶⣿⣁⣸⠃⠀⠀⠀⠉⢻⣧⠀⠻⠉⣉⣿⣿⡙⣶⡄⠈⠿⢷⡿⢻⣾⠿⢷⣾⣿⠿⠟⠛⠛⠋⠁⠉ |
||||
|
⠀⠀⠙⠛⠋⠀⠘⠛⠉⠛⢿⣟⣻⣿⣟⣿⣿⣤⣴⣶⣄⣶⣌⡋⠀⠀⠞⢻⣧⣿⣧⣿⣷⣾⣷⣴⣿⣿⡿⣿⠿⠟⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠛⠛⠛⠛⠛⠛⠛⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
</div> |
||||
|
<div class="cloud2"> |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣶⣶⣶⣶⣶⣶⣤⣤⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⢀⣀⣴⠟⠋⠁⠀⠀⠀⠀⠈⠉⠙⠻⢿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⠛⠿⣿⣛⣿⠿⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣾⣿⣻⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠿⣶⣦⣤⣤⣤⣤⣤⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣟⣛⣛⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⡀⠾⢿⣿⣿⣭⣭⣿⣄⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⣠⣶⡾⠿⠛⠛⠋⠉⠉⠙⠻⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠋⠐⠾⠏⠉⠉⠙⠛⠻⢷⣄⠀⠀⠀ |
||||
|
⠀⠀⢠⣾⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣆⠀ |
||||
|
⣀⣀⣸⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣷ |
||||
|
⣿⡛⠋⠉⢀⣀⠀⠀⠀⣤⡄⠀⠀⠀⣀⣴⢶⣿⣯⢄⣀⣀⣀⡀⠀⢀⣄⣠⣤⣀⠁⠀⠀⢸⣿⣿⣶⠶⣶⡄⠀⠀⢠⣀⣀⡀⣀⣠⣿⣾ |
||||
|
⣯⣿⣿⣦⣽⣭⣷⣦⣤⣼⣿⣶⣴⣶⣿⣁⣸⠃⠀⠀⠀⠉⢻⣧⠀⠻⠉⣉⣿⣿⡙⣶⡄⠈⠿⢷⡿⢻⣾⠿⢷⣾⣿⠿⠟⠛⠛⠋⠁⠉ |
||||
|
⠀⠀⠙⠛⠋⠀⠘⠛⠉⠛⢿⣟⣻⣿⣟⣿⣿⣤⣴⣶⣄⣶⣌⡋⠀⠀⠞⢻⣧⣿⣧⣿⣷⣾⣷⣴⣿⣿⡿⣿⠿⠟⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠛⠛⠛⠛⠛⠛⠛⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
</div> |
||||
|
<div class="cloud1"> |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣶⣶⣶⣶⣶⣶⣤⣤⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⢀⣀⣴⠟⠋⠁⠀⠀⠀⠀⠈⠉⠙⠻⢿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⠛⠿⣿⣛⣿⠿⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣾⣿⣻⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠿⣶⣦⣤⣤⣤⣤⣤⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣟⣛⣛⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⡀⠾⢿⣿⣿⣭⣭⣿⣄⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⣠⣶⡾⠿⠛⠛⠋⠉⠉⠙⠻⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠋⠐⠾⠏⠉⠉⠙⠛⠻⢷⣄⠀⠀⠀ |
||||
|
⠀⠀⢠⣾⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣆⠀ |
||||
|
⣀⣀⣸⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣷ |
||||
|
⣿⡛⠋⠉⢀⣀⠀⠀⠀⣤⡄⠀⠀⠀⣀⣴⢶⣿⣯⢄⣀⣀⣀⡀⠀⢀⣄⣠⣤⣀⠁⠀⠀⢸⣿⣿⣶⠶⣶⡄⠀⠀⢠⣀⣀⡀⣀⣠⣿⣾ |
||||
|
⣯⣿⣿⣦⣽⣭⣷⣦⣤⣼⣿⣶⣴⣶⣿⣁⣸⠃⠀⠀⠀⠉⢻⣧⠀⠻⠉⣉⣿⣿⡙⣶⡄⠈⠿⢷⡿⢻⣾⠿⢷⣾⣿⠿⠟⠛⠛⠋⠁⠉ |
||||
|
⠀⠀⠙⠛⠋⠀⠘⠛⠉⠛⢿⣟⣻⣿⣟⣿⣿⣤⣴⣶⣄⣶⣌⡋⠀⠀⠞⢻⣧⣿⣧⣿⣷⣾⣷⣴⣿⣿⡿⣿⠿⠟⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠛⠛⠛⠛⠛⠛⠛⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
</div> |
||||
|
<div class="cloud-gif"> |
||||
|
<img src="/images/website/lakitu.gif"> |
||||
|
</div> |
||||
|
</div> |
||||
|
<script src="js/search.js"></script> |
||||
|
|
||||
|
</div> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,4 @@ |
|||||
|
Jinja2==3.1.4 |
||||
|
Markdown==3.6 |
||||
|
PyYAML==6.0.1 |
||||
|
PyYAML==6.0.1 |
@ -0,0 +1,65 @@ |
|||||
|
const express = require('express'); |
||||
|
const path = require('path'); |
||||
|
|
||||
|
const app = express(); |
||||
|
const PORT = process.env.PORT || 3000; |
||||
|
|
||||
|
// Serve static files from the 'public' directory
|
||||
|
app.use(express.static(path.join(__dirname, 'public'))); |
||||
|
|
||||
|
// Custom middleware to handle URLs without .html for specific routes
|
||||
|
app.use((req, res, next) => { |
||||
|
// Extract the path without any query parameters
|
||||
|
const urlPath = req.path.split('?')[0]; |
||||
|
|
||||
|
// Define routes that should render HTML files without .html extension
|
||||
|
const htmlRoutes = ['/about', '/list', '/gallery']; |
||||
|
|
||||
|
// Check if the requested path is in the htmlRoutes array
|
||||
|
if (htmlRoutes.includes(urlPath)) { |
||||
|
// Append .html to the path and continue
|
||||
|
req.url += '.html'; |
||||
|
} |
||||
|
|
||||
|
// Continue to the next middleware
|
||||
|
next(); |
||||
|
}); |
||||
|
|
||||
|
// Route to serve the index.html file
|
||||
|
app.get('/', (req, res) => { |
||||
|
res.sendFile(path.join(__dirname, 'public', 'index.html')); |
||||
|
}); |
||||
|
|
||||
|
// Routes to serve the HTML files without .html extension
|
||||
|
app.get('/about.html', (req, res) => { |
||||
|
res.sendFile(path.join(__dirname, 'public', 'about.html')); |
||||
|
}); |
||||
|
|
||||
|
app.get('/list.html', (req, res) => { |
||||
|
res.sendFile(path.join(__dirname, 'public', 'list.html')); |
||||
|
}); |
||||
|
|
||||
|
app.get('/gallery.html', (req, res) => { |
||||
|
res.sendFile(path.join(__dirname, 'public', 'gallery.html')); |
||||
|
}); |
||||
|
|
||||
|
// Serve articles without .html extension
|
||||
|
app.get('/articles/:articleName', (req, res) => { |
||||
|
const articleName = req.params.articleName; |
||||
|
res.sendFile(path.join(__dirname, 'public/articles', `${articleName}.html`)); |
||||
|
}); |
||||
|
|
||||
|
// Error handling
|
||||
|
app.use((err, req, res, next) => { |
||||
|
console.error(err.stack); |
||||
|
res.status(500).send('Something broke!'); |
||||
|
}); |
||||
|
|
||||
|
// Start the server
|
||||
|
app.listen(PORT, (err) => { |
||||
|
if (err) { |
||||
|
console.error('Error starting the server:', err); |
||||
|
process.exit(1); |
||||
|
} |
||||
|
console.log(`Server is running on http://localhost:${PORT}`); |
||||
|
}); |
@ -0,0 +1,26 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
|
<title>cailean.finn</title> |
||||
|
<link rel="preconnect" href="https://fonts.googleapis.com"> |
||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet"> |
||||
|
<link href="https://fonts.googleapis.com/css2?family=Gothic+A1&display=swap" rel="stylesheet"> |
||||
|
<link rel="stylesheet" href="/css/styles.css"> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="main-container"> |
||||
|
<div id="nav-container"> |
||||
|
<div class="nav-emoji"> |
||||
|
<a href="/"><div class="emoji-flip">(ง•_•)ง</div></a> |
||||
|
</div> |
||||
|
<div class="nav-about"><a href="/about">cailean.finn</a></div> |
||||
|
<div class="nav-emoji"><a href="/list">list</a></div> |
||||
|
<div class="nav-emoji"><a href='/gallery'>gallery</a></div> |
||||
|
</div> |
||||
|
{% block content %} {% endblock %} |
||||
|
</div> |
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,26 @@ |
|||||
|
{% extends "_base.html" %} |
||||
|
|
||||
|
{% block content %} |
||||
|
<div id="container"> |
||||
|
<div id="project-container"> |
||||
|
<div id="project-header">ABOUT</div> |
||||
|
<div id="project-body"> |
||||
|
{{ content }} |
||||
|
</div> |
||||
|
<div id="social-container"> |
||||
|
<div>[email protected]</div> |
||||
|
{% for key, value in socials.items() %} |
||||
|
<div>{{ key }} <a href="{{ value }}" target="_blank">↯</a></div> |
||||
|
{% endfor %} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
<a><span id="monster"> |
||||
|
<span id="fire-1">🔥</span> |
||||
|
<span>🔥╰(#°Д°)╯🔥</span> |
||||
|
<span id="fire-3">🔥</span> |
||||
|
</span> |
||||
|
</a> |
||||
|
</div> |
||||
|
<script src="js/mob.js"></script> |
||||
|
{% endblock %} |
@ -0,0 +1,31 @@ |
|||||
|
{% extends "_base.html" %} |
||||
|
|
||||
|
{% block content %} |
||||
|
<div id="container"> |
||||
|
<div id="project-container"> |
||||
|
<div id="project-header">{{ title }}, {{ year }}</div> |
||||
|
<div id="project-tags"> |
||||
|
{% for tag in tags %} |
||||
|
<div class="project-tag">✳ {{ tag }}</div> |
||||
|
{% endfor %} |
||||
|
</div> |
||||
|
<div id="project-cover"> |
||||
|
<img src="/images/{{ image }}"> |
||||
|
</div> |
||||
|
<div id="project-body"> |
||||
|
{{ content }} |
||||
|
</div> |
||||
|
<div id="project-related"> |
||||
|
<div id="pr-header">References:</div> |
||||
|
<ul id="pr-list"> |
||||
|
{% for reference in references %} |
||||
|
<li><a href='{{ reference['link'] }}'>{{ reference['title'] }}</a></li> |
||||
|
{% endfor %} |
||||
|
</ul> |
||||
|
<div id="sleeping-mario"> |
||||
|
<img src="/images/website/mario-sleep-up.gif"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
{% endblock %} |
@ -0,0 +1,22 @@ |
|||||
|
{% extends "_base.html" %} |
||||
|
|
||||
|
{% block content %} |
||||
|
<div id="project-container-gallery"> |
||||
|
<div id="image-gallery"> |
||||
|
<div class="image-container"></div> |
||||
|
{% for image in images %} |
||||
|
<img class="gallery-image" src="{{ image }}"> |
||||
|
{% endfor %} |
||||
|
<div class="image-container"></div> |
||||
|
</div> |
||||
|
<div id="focused-image-container"> |
||||
|
<div id="close-button"><a>☀</a></div> |
||||
|
<img class="focused-image" src="/images/website/lakitu.gif"> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div id="chat"><a href="/campfire">📬</a></div> |
||||
|
</div> |
||||
|
|
||||
|
<script src="js/skybox.js"></script> |
||||
|
{% endblock %} |
@ -0,0 +1,80 @@ |
|||||
|
{% extends "_base.html" %} |
||||
|
|
||||
|
{% block content %} |
||||
|
<div id="container"> |
||||
|
<div id="list-container"> |
||||
|
<div class="filter"> |
||||
|
<label for="tag-select">Filter by tag:</label> |
||||
|
<select id="tag-select" onchange="filterArticles()"> |
||||
|
<option value="all">All</option> |
||||
|
{% for tag in tags %} |
||||
|
<option value="{{ tag }}">{{ tag }}</option> |
||||
|
{% endfor %} |
||||
|
</select> |
||||
|
</div> |
||||
|
<div class="articles"> |
||||
|
{% for page in pages %} |
||||
|
<div class="article" data-tags="{{ page.get_tags() | join(' ') }}"> |
||||
|
|
||||
|
<a href= 'articles/{{ page.get_src() }}'><div class="article-image"> |
||||
|
<img src="/images/{{ page.get_image() }}"> |
||||
|
</div></a> |
||||
|
|
||||
|
<div id="project-tags"> |
||||
|
{% for tag in page.get_tags()%} |
||||
|
<div class="project-tag">✳ {{ tag }}</div> |
||||
|
{% endfor %} |
||||
|
</div> |
||||
|
|
||||
|
<h2>{{ page.get_title() }}</h2> |
||||
|
</div> |
||||
|
{% endfor %} |
||||
|
<div id="clouds"> |
||||
|
<div class="cloud"> |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣶⣶⣶⣶⣶⣶⣤⣤⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⢀⣀⣴⠟⠋⠁⠀⠀⠀⠀⠈⠉⠙⠻⢿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⠛⠿⣿⣛⣿⠿⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣾⣿⣻⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠿⣶⣦⣤⣤⣤⣤⣤⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣟⣛⣛⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⡀⠾⢿⣿⣿⣭⣭⣿⣄⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⣠⣶⡾⠿⠛⠛⠋⠉⠉⠙⠻⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠋⠐⠾⠏⠉⠉⠙⠛⠻⢷⣄⠀⠀⠀ |
||||
|
⠀⠀⢠⣾⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣆⠀ |
||||
|
⣀⣀⣸⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣷ |
||||
|
⣿⡛⠋⠉⢀⣀⠀⠀⠀⣤⡄⠀⠀⠀⣀⣴⢶⣿⣯⢄⣀⣀⣀⡀⠀⢀⣄⣠⣤⣀⠁⠀⠀⢸⣿⣿⣶⠶⣶⡄⠀⠀⢠⣀⣀⡀⣀⣠⣿⣾ |
||||
|
⣯⣿⣿⣦⣽⣭⣷⣦⣤⣼⣿⣶⣴⣶⣿⣁⣸⠃⠀⠀⠀⠉⢻⣧⠀⠻⠉⣉⣿⣿⡙⣶⡄⠈⠿⢷⡿⢻⣾⠿⢷⣾⣿⠿⠟⠛⠛⠋⠁⠉ |
||||
|
⠀⠀⠙⠛⠋⠀⠘⠛⠉⠛⢿⣟⣻⣿⣟⣿⣿⣤⣴⣶⣄⣶⣌⡋⠀⠀⠞⢻⣧⣿⣧⣿⣷⣾⣷⣴⣿⣿⡿⣿⠿⠟⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠛⠛⠛⠛⠛⠛⠛⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
</div> |
||||
|
<div class="cloud2"> |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣶⣶⣶⣶⣶⣶⣤⣤⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⢀⣀⣴⠟⠋⠁⠀⠀⠀⠀⠈⠉⠙⠻⢿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⠛⠿⣿⣛⣿⠿⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣾⣿⣻⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠿⣶⣦⣤⣤⣤⣤⣤⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣟⣛⣛⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⡀⠾⢿⣿⣿⣭⣭⣿⣄⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⣠⣶⡾⠿⠛⠛⠋⠉⠉⠙⠻⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠋⠐⠾⠏⠉⠉⠙⠛⠻⢷⣄⠀⠀⠀ |
||||
|
⠀⠀⢠⣾⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣆⠀ |
||||
|
⣀⣀⣸⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣷ |
||||
|
⣿⡛⠋⠉⢀⣀⠀⠀⠀⣤⡄⠀⠀⠀⣀⣴⢶⣿⣯⢄⣀⣀⣀⡀⠀⢀⣄⣠⣤⣀⠁⠀⠀⢸⣿⣿⣶⠶⣶⡄⠀⠀⢠⣀⣀⡀⣀⣠⣿⣾ |
||||
|
⣯⣿⣿⣦⣽⣭⣷⣦⣤⣼⣿⣶⣴⣶⣿⣁⣸⠃⠀⠀⠀⠉⢻⣧⠀⠻⠉⣉⣿⣿⡙⣶⡄⠈⠿⢷⡿⢻⣾⠿⢷⣾⣿⠿⠟⠛⠛⠋⠁⠉ |
||||
|
⠀⠀⠙⠛⠋⠀⠘⠛⠉⠛⢿⣟⣻⣿⣟⣿⣿⣤⣴⣶⣄⣶⣌⡋⠀⠀⠞⢻⣧⣿⣧⣿⣷⣾⣷⣴⣿⣿⡿⣿⠿⠟⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠛⠛⠛⠛⠛⠛⠛⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
</div> |
||||
|
<div class="cloud1"> |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⣶⣶⣶⣶⣶⣶⣤⣤⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⢀⣀⣴⠟⠋⠁⠀⠀⠀⠀⠈⠉⠙⠻⢿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⠛⠿⣿⣛⣿⠿⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣾⣿⣻⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠿⣶⣦⣤⣤⣤⣤⣤⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣟⣛⣛⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⡀⠾⢿⣿⣿⣭⣭⣿⣄⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⣠⣶⡾⠿⠛⠛⠋⠉⠉⠙⠻⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠋⠐⠾⠏⠉⠉⠙⠛⠻⢷⣄⠀⠀⠀ |
||||
|
⠀⠀⢠⣾⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣆⠀ |
||||
|
⣀⣀⣸⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣷ |
||||
|
⣿⡛⠋⠉⢀⣀⠀⠀⠀⣤⡄⠀⠀⠀⣀⣴⢶⣿⣯⢄⣀⣀⣀⡀⠀⢀⣄⣠⣤⣀⠁⠀⠀⢸⣿⣿⣶⠶⣶⡄⠀⠀⢠⣀⣀⡀⣀⣠⣿⣾ |
||||
|
⣯⣿⣿⣦⣽⣭⣷⣦⣤⣼⣿⣶⣴⣶⣿⣁⣸⠃⠀⠀⠀⠉⢻⣧⠀⠻⠉⣉⣿⣿⡙⣶⡄⠈⠿⢷⡿⢻⣾⠿⢷⣾⣿⠿⠟⠛⠛⠋⠁⠉ |
||||
|
⠀⠀⠙⠛⠋⠀⠘⠛⠉⠛⢿⣟⣻⣿⣟⣿⣿⣤⣴⣶⣄⣶⣌⡋⠀⠀⠞⢻⣧⣿⣧⣿⣷⣾⣷⣴⣿⣿⡿⣿⠿⠟⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠉⠛⠛⠛⠛⠛⠛⠛⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ |
||||
|
</div> |
||||
|
<div class="cloud-gif"> |
||||
|
<img src="/images/website/lakitu.gif"> |
||||
|
</div> |
||||
|
</div> |
||||
|
<script src="js/search.js"></script> |
||||
|
{% endblock %} |