Portfolio site for See Through Lab, an experimental art & technology studio.
Built with Astro 5, deployed to GitHub Pages. Live at https://seethroughlab.com.
- Astro 5.17 — static output (
astro.config.mjs) - Tailwind CSS 4 — via
@tailwindcss/viteplugin (notailwind.config; theme insrc/styles/global.css) - MDX — content authoring (
@astrojs/mdx) - TypeScript (strict)
- Node 20 (
.nvmrc)
npm run dev # local dev server
npm run build # production build
npm run preview # preview production buildsrc/
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)
scripts/
process-video.sh # HLS encoding (ffmpeg)
upload-video.js # S3 upload
scrape-squarespace.js # original site scraper (migration)
enrich-frontmatter.js # MDX frontmatter enrichment (migration)
Schemas defined in src/content.config.ts.
| 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 [] |
| 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 '' |
| instagramPosts | string[] |
defaults [] |
Video → Image carousel → Cover image (first available wins).
- Dark theme: black bg (
#000), white primary text, muted gray (#666) for body/secondary - Theme tokens in
src/styles/global.css@themeblock — colors, fonts, font sizes, container width - Prose styles for MDX content in
@layer base(.proseclass) .prose-no-imageshides MDX inline images when anImageCarouselis present.logo-text— chromatic aberration text effect for homepage logo
- Source:
public/home/js/(modules) +public/home/shaders/(WGSL) - Loaded by
Nav.astrovia<script is:inline type="module" src="/home/js/main.js"> - CRT scanline background effect + interactive logo rendering
- Falls back gracefully when WebGPU is unavailable
GitHub Actions (.github/workflows/deploy.yml):
- Trigger: push to
masteror manual dispatch - Uses
withastro/action@v3(Node 20) →actions/deploy-pages@v4 - Concurrency group
pages, does not cancel in-progress deploys
Project videos are self-hosted on S3/CloudFront with HLS adaptive streaming.
- S3 Bucket:
seethroughlab-media(us-east-1) - CloudFront Distribution:
E3HHODJ5DZ82EV—d13tobysqmg65w.cloudfront.net - IAM User:
s3_user(has S3 + CloudFront permissions) - OAC ID:
E3LVSUOS6K0DW2
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
| 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 |
scripts/process-video.sh <input.mp4> <slug>— Encodes HLS in 3 renditions (1080p@4Mbps, 720p@2Mbps, 480p@800kbps) + poster. Output:processed/videos/<slug>/.scripts/upload-video.js <slug>— Uploads processed video to S3 with correct MIME types and cache headers. SetCF_DOMAINenv var to override CloudFront domain.
# 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>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.