Skip to content

Latest commit

 

History

History
199 lines (158 loc) · 8.78 KB

CLAUDE.md

File metadata and controls

199 lines (158 loc) · 8.78 KB

See Through Lab

Portfolio site for See Through Lab, an experimental art & technology studio. Built with Astro 7, deployed to GitHub Pages. Live at https://seethroughlab.com.

Tech Stack

  • Astro 7 — static output, compressHTML: true (astro.config.mjs)
  • Tailwind CSS 4 — via @tailwindcss/vite plugin (no tailwind.config; theme in src/styles/global.css)
  • MDX — content authoring (@astrojs/mdx)
  • TypeScript (strict)
  • Node 22 (.nvmrc)

Commands

npm run dev       # local dev server
npm run build     # production build
npm run preview   # preview production build

Project Structure

src/
  components/     # Nav, Footer, ProjectCard, ResearchCard, VideoPlayer,
                  #   ImageCarousel, InstagramEmbed
  content/
    projects/     # 26 MDX files
    research/     # 7 MDX files
  layouts/        # Base.astro (only layout)
  pages/
    index.astro            # homepage
    projects/[slug].astro  # project detail
    research/index.astro   # research listing
    research/[slug].astro  # research detail
  styles/
    global.css             # Tailwind @theme tokens + prose styles
  content.config.ts        # collection schemas

public/
  images/projects/<slug>/  # project images
  home/                    # WebGPU homepage animation (JS + WGSL shaders)
  lab/                     # interactive experiments (ambient-generator, interactive-frank-2, shaderlm);
                           #   cassvision/ holds the Cassvision viewer's assets (synced, see below)

scripts/
  process-video.sh         # HLS encoding (ffmpeg): [--grain] <input> <slug>; quality-based, capped per rendition
  upload-video.js          # S3 upload
  sync-cassvision.sh       # copy Cassvision's STLs, pipeline JS, clips, carrier renders from ~/Developer/cassvision [--rebuild]
  cassvision-form-drawing.py # regenerate the Cassvision Form drawing from the CAD (Cassvision's enclosure venv)
  scrape-squarespace.js    # original site scraper (migration)
  enrich-frontmatter.js    # MDX frontmatter enrichment (migration)

Content Collections

Schemas defined in src/content.config.ts.

Projects

Field Type Notes
title string required
slug string required
client string defaults ''
year number optional
tags string[] defaults []
coverImage string defaults ''
videoSrc string CloudFront HLS URL; defaults ''
featured boolean defaults false
order number sort key; defaults 99
role string defaults ''
tech string[] defaults []

Research

Field Type Notes
title string required
slug string required
subtitle string defaults ''
status 'active' | 'archived' | 'experiment' defaults 'active'
githubUrl string defaults ''
coverImage string defaults ''
labUrl string defaults ''
videoSrc string CloudFront HLS URL; replaces the cover image as hero; defaults ''
videoAspect string CSS aspect-ratio for the hero video (e.g. 1920 / 888); defaults to 16:9
heroViewer '' | 'cassvision-case' interactive hero in place of the video/cover (Cassvision's 3D case: src/components/cassvision/, scene in public/lab/cassvision/cassvision.js); defaults ''
instagramPosts string[] defaults []
credits { role, name, url? }[] shown as "Team" below the body; defaults []

Cassvision assets

The Cassvision page's model comes from the Cassvision repo (~/Developer/cassvision, CAD in enclosure/pxl_case.py). After a CAD, pipeline or clip change there, run scripts/sync-cassvision.sh (add --rebuild to regenerate there first), check the page locally, and commit. A part added or renamed in the CAD also needs its name in the script's PARTS, in cassvision.js (CASE_PARTS or CASE_INTERNALS) and, for a printed part, a button in CaseViewer.astro. Details the CAD only cuts holes for (screen glass, lens, USB-C, switch, screws, LED, window disc) are drawn in cassvision.js from the CAD's numbers: update them there if those numbers change. The Form section's dimensioned drawing is generated by scripts/cassvision-form-drawing.py (run it with the Cassvision enclosure's .venv/bin/python); rerun it when the outline changes.

Private (password-protected) projects

The repo is public and GitHub Pages has no auth, so a project with private: true is never pushed in plain form:

  • its .mdx and public/images/projects/<slug>/ are listed in .gitignore;
  • it is excluded from the homepage, tag pages, BTS and the normal build;
  • node scripts/build-private.mjs renders it locally, inlines CSS and images, encrypts the content (AES-GCM / PBKDF2) and writes public/projects/<slug>/index.html, the only file committed. Rerun it after editing the project.
  • Password: .private-password (gitignored) or PRIVATE_PAGE_PASSWORD.
  • To publish normally: remove private: true, its .gitignore lines and public/projects/<slug>/, then commit the .mdx and images.

Currently private: none.

Hero priority on project pages

Video → Image carousel → Cover image (first available wins).

Styling Conventions

  • Dark theme: black bg (#000), white primary text, muted gray (#666) for body/secondary
  • Theme tokens in src/styles/global.css @theme block — colors, fonts, font sizes, container width
  • Prose styles for MDX content in @layer base (.prose class)
  • .prose-no-images hides MDX inline images when an ImageCarousel is present
  • .logo-text — chromatic aberration text effect for homepage logo

Homepage WebGPU Animation

  • Source: public/home/js/ (modules) + public/home/shaders/ (WGSL)
  • Loaded by Nav.astro via <script is:inline type="module" src="/home/js/main.js">
  • CRT scanline background effect + interactive logo rendering
  • Falls back gracefully when WebGPU is unavailable

Deployment

GitHub Actions (.github/workflows/deploy.yml):

  • Trigger: push to master or manual dispatch
  • Uses withastro/action@v3 (Node 20) → actions/deploy-pages@v4
  • Concurrency group pages, does not cancel in-progress deploys

Video Infrastructure

Project videos are self-hosted on S3/CloudFront with HLS adaptive streaming.

AWS Resources

  • S3 Bucket: seethroughlab-media (us-east-1)
  • CloudFront Distribution: E3HHODJ5DZ82EV — d13tobysqmg65w.cloudfront.net
  • IAM User: s3_user (has S3 + CloudFront permissions)
  • OAC ID: E3LVSUOS6K0DW2

S3 Layout

videos/<slug>/
  ├── original.mp4      ← source file from Vimeo (for future re-encoding)
  ├── index.m3u8        ← master HLS playlist
  ├── 1080p/index.m3u8 + seg_*.ts
  ├── 720p/index.m3u8  + seg_*.ts
  ├── 480p/index.m3u8  + seg_*.ts
  └── poster.jpg        ← frame extracted at 2s

Videos (8 projects)

Slug Original Vimeo ID
cosmic-crisis 878052416
pandora-sounds-like-you 259486511
intel-mega-experience 259486965
one-time-in-new-orleans 259501609
driven-by-emotion 187023351
endec 168886039
light-echoes 134693404
acer-predator 130166758

Research: tidewalker (sizzle reel, from the Tidewalker repo's Builds/Reel/post/Tidewalker.mp4), antiphon (sizzle reel 16x9, from ~/Music/Antiphon/common-tone/videos/).

To replace a published video, upload to a new path (e.g. videos/antiphon/v2/ via process-video.sh <in> antiphon/v2 + upload-video.js antiphon/v2) rather than overwriting: segments are cached as immutable for a year. Then delete the old files.

Scripts

  • scripts/process-video.sh [--grain] <input.mp4> <slug> — Encodes HLS in 3 renditions, quality-based with a ceiling (1080p CRF 18 ≤ 12 Mbps, 720p CRF 19 ≤ 6 Mbps, 480p CRF 20 ≤ 2.5 Mbps; keyframes every 2 s, aligned across renditions; landscape fits within 1920x1080 etc., output always 4:2:0 limited range) + poster; the master playlist states each rendition's measured peak and average bitrate. --grain adds x264 grain tuning for grainy, noisy or shader-textured footage. Output: processed/videos/<slug>/.
  • scripts/upload-video.js <slug> — Uploads processed video to S3 with correct MIME types and cache headers. Set CF_DOMAIN env var to override CloudFront domain.

Re-encoding a video

# 1. Download original from S3 (or use local copy in raw/videos/)
aws s3 cp s3://seethroughlab-media/videos/<slug>/original.mp4 raw/videos/<slug>.mp4

# 2. Process
./scripts/process-video.sh raw/videos/<slug>.mp4 <slug>

# 3. Upload
node scripts/upload-video.js <slug>

Frontend

VideoPlayer.astro detects .m3u8 URLs and uses:

  • Native HLS in Safari
  • hls.js (loaded via CDN) in Chrome/Firefox

MDX frontmatter videoSrc field points to the CloudFront master playlist URL.