Skip to content

ctwhome/portfolio

Repository files navigation

CTW Studio Portfolio

Static portfolio site for ctw.studio. No build framework — plain HTML, CSS, and vanilla JS.

Tech Stack

  • HTML/CSS/JS — Static pages, no framework
  • Tailwind CSS — Utility-first styling via CLI-generated static CSS (no CDN)
  • Three.js — Blueprint background effects (loaded async)
  • Anime.js — Entry animations on landing page (loaded async)
  • Google Fonts — Inter typeface (non-render-blocking)
  • Vercel — Hosting and deployment

Structure

ctw.studio/
├── index.html              # Landing page
├── tailwind.css             # Generated Tailwind CSS (do not edit directly)
├── tailwind.config.js       # Tailwind configuration
├── portfolio/
│   ├── index.html           # Portfolio grid page
│   ├── portfolio.css        # Portfolio styles + utility classes
│   ├── projects.js          # Project data
│   └── projects/            # Project media assets
├── favicon.png
└── og-image.png

Adding a New Project

Edit ctw.studio/portfolio/projects.js. Each project object supports these fields:

Field Type Required Description
id string yes URL-friendly slug (used in #hash routing)
title string yes Project name
client string yes Client or institution name
category string yes Short category label (e.g. "Climate Science")
headline string yes Detail page headline
description string yes Multi-paragraph description (template literal)
coverImage string|null yes Path to cover image (relative to portfolio/), or null for gradient
gradientFrom string - Gradient start color (when coverImage is null)
gradientTo string - Gradient end color (when coverImage is null)
gridSpan number yes Grid width: 1 (1/3), 3 (1/2), or 4 (2/3)
liveUrl string|null - Live site URL
repoUrl string - GitHub repository URL
blogUrl string - Related blog post URL
tags string[] yes Technology/topic tags
institution string|null - Associated institution badge
gallery array yes Media items for detail view (see below)

Gallery items

{ type: 'image', src: 'projects/my-project/img.avif', caption: 'Optional caption' }
{ type: 'video', src: 'projects/my-project/demo.mp4', caption: 'Optional caption' }
{ type: 'pair',  src: 'projects/my-project/a.avif', src2: 'projects/my-project/b.avif', caption: 'Optional caption' }

Image guidelines

  • Use AVIF format for all images (convert with avifenc --min 20 --max 40 input.png output.avif)
  • Place media in ctw.studio/portfolio/projects/<project-id>/
  • Cover images are displayed in the grid; gallery images appear in the detail view
  • Images use loading="lazy", decoding="async", and CSS fade-in on load
  • The first 3 grid cards use fetchpriority="high" instead of lazy loading

Rebuilding Tailwind CSS

The landing page (ctw.studio/index.html) uses a pre-built tailwind.css file generated by the Tailwind CLI. The portfolio page uses hand-written utility classes in portfolio.css instead.

After adding or changing Tailwind classes in index.html, rebuild:

cd ctw.studio
bunx tailwindcss -i <(echo '@tailwind base; @tailwind components; @tailwind utilities;') -o tailwind.css --content './index.html' --minify

Performance Notes

All third-party scripts are loaded asynchronously to avoid render-blocking:

  • Tailwind CSS — Static pre-built CSS file (~20KB) instead of the 124KB CDN JIT compiler
  • Three.js & Anime.js — Dynamically injected via document.createElement('script') after initial render
  • Google Fonts — Loaded with media="print" onload="this.media='all'" pattern

No build tool (Vite, Webpack, etc.) is needed. The Tailwind CLI handles CSS generation and minification. Inline scripts are small enough that bundling provides no meaningful benefit.

License

MIT

About

Personal portfolio

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors