Browse Source

'init'

master
Cailean Finn 4 months ago
commit
694571a3db
  1. 2
      .gitignore
  2. 194
      build.py
  3. 12
      content/about.md
  4. 21
      content/posts/(o)machine.md
  5. 32
      content/posts/ai-x-body.md
  6. 31
      content/posts/beauty-and-the-beep.md
  7. 27
      content/posts/data.md
  8. 24
      content/posts/dwelling.md
  9. 25
      content/posts/electronic-image.md
  10. 24
      content/posts/latent-mirror.md
  11. 26
      content/posts/undefined.md
  12. 17
      package.json
  13. 56
      public/about.html
  14. 59
      public/articles/(o)machine.html
  15. 58
      public/articles/ai-x-body.html
  16. 62
      public/articles/beauty-and-the-beep.html
  17. 60
      public/articles/data.html
  18. 59
      public/articles/dwelling.html
  19. 62
      public/articles/electronic-image.html
  20. 62
      public/articles/latent-mirror.html
  21. 59
      public/articles/undefined.html
  22. 566
      public/css/styles.css
  23. BIN
      public/fonts/CothamSans.otf
  24. 1
      public/fonts/Redaction 50_Regular.json
  25. BIN
      public/fonts/Redaction_70-Bold.woff2
  26. BIN
      public/fonts/Redaction_70-Italic.woff2
  27. BIN
      public/fonts/Redaction_70-Regular.woff2
  28. 67
      public/gallery.html
  29. BIN
      public/images/aixbody.webp
  30. BIN
      public/images/beep.png
  31. BIN
      public/images/data.png
  32. BIN
      public/images/dwelling.png
  33. BIN
      public/images/electronic-image.png
  34. BIN
      public/images/latent-mirror.png
  35. BIN
      public/images/o-machine.png
  36. BIN
      public/images/undefined-panorama.png
  37. BIN
      public/images/website/checker.png
  38. BIN
      public/images/website/lakitu.gif
  39. BIN
      public/images/website/mario-sleep-up.gif
  40. BIN
      public/images/website/skybox.png
  41. 36
      public/index.html
  42. 450
      public/js/main.js
  43. 33
      public/js/mob.js
  44. 17
      public/js/search.js
  45. 51
      public/js/skybox.js
  46. 42
      public/json/articles.json
  47. 276
      public/list.html
  48. 4
      requirements.txt
  49. 65
      server.js
  50. 26
      templates/_base.html
  51. 26
      templates/about.html
  52. 31
      templates/article.html
  53. 22
      templates/gallery.html
  54. 80
      templates/list.html

2
.gitignore

@ -0,0 +1,2 @@
/node_modules/
/package-lock.json

194
build.py

@ -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()

12
content/about.md

@ -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.

21
content/posts/(o)machine.md

@ -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/]

32
content/posts/ai-x-body.md

@ -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]

31
content/posts/beauty-and-the-beep.md

@ -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.

27
content/posts/data.md

@ -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.

24
content/posts/dwelling.md

@ -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.

25
content/posts/electronic-image.md

@ -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]

24
content/posts/latent-mirror.md

@ -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.

26
content/posts/undefined.md

@ -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.

17
package.json

@ -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"
}

56
public/about.html

@ -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 &amp; 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>

59
public/articles/(o)machine.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>

58
public/articles/ai-x-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>

62
public/articles/beauty-and-the-beep.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>

60
public/articles/data.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 &amp; 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>

59
public/articles/dwelling.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>

62
public/articles/electronic-image.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>

62
public/articles/latent-mirror.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 &amp; 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>

59
public/articles/undefined.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>

566
public/css/styles.css

@ -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%;
}
}

BIN
public/fonts/CothamSans.otf

Binary file not shown.

1
public/fonts/Redaction 50_Regular.json

File diff suppressed because one or more lines are too long

BIN
public/fonts/Redaction_70-Bold.woff2

Binary file not shown.

BIN
public/fonts/Redaction_70-Italic.woff2

Binary file not shown.

BIN
public/fonts/Redaction_70-Regular.woff2

Binary file not shown.

67
public/gallery.html

@ -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>

BIN
public/images/aixbody.webp

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

BIN
public/images/beep.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

BIN
public/images/data.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

BIN
public/images/dwelling.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

BIN
public/images/electronic-image.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

BIN
public/images/latent-mirror.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

BIN
public/images/o-machine.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

BIN
public/images/undefined-panorama.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

BIN
public/images/website/checker.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
public/images/website/lakitu.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 451 KiB

BIN
public/images/website/mario-sleep-up.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

BIN
public/images/website/skybox.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

36
public/index.html

@ -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>

450
public/js/main.js

@ -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();

33
public/js/mob.js

@ -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()

17
public/js/search.js

@ -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();

51
public/js/skybox.js

@ -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()

42
public/json/articles.json

@ -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"
}
]

276
public/list.html

@ -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>

4
requirements.txt

@ -0,0 +1,4 @@
Jinja2==3.1.4
Markdown==3.6
PyYAML==6.0.1
PyYAML==6.0.1

65
server.js

@ -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}`);
});

26
templates/_base.html

@ -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>

26
templates/about.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 %}

31
templates/article.html

@ -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 %}

22
templates/gallery.html

@ -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 %}

80
templates/list.html

@ -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 %}
Loading…
Cancel
Save