From 1f3af8f7f5bf0e3d4fb7e9fbcf7b468d6a3392ed Mon Sep 17 00:00:00 2001 From: twotalesanimation <80506065+twotalesanimation@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:17:41 +0200 Subject: [PATCH] Initial ttvfx.com site: Next.js static export Co-Authored-By: Claude Opus 5.5 --- .claude/launch.json | 11 + .gitignore | 41 + AGENTS.md | 9 + CLAUDE.md | 1 + DEPLOY.md | 64 + README.md | 36 + app/apple-icon.png | Bin 0 -> 1102 bytes app/globals.css | 87 + app/icon.png | Bin 0 -> 3432 bytes app/layout.tsx | 50 + app/not-found.tsx | 18 + app/page.tsx | 140 + app/robots.ts | 11 + app/sitemap.ts | 12 + app/work/[slug]/page.tsx | 110 + app/work/page.tsx | 28 + components/Footer.tsx | 43 + components/Fox.tsx | 11 + components/Frame.tsx | 38 + components/Header.tsx | 81 + components/Hero.tsx | 78 + components/ProjectCard.tsx | 22 + components/Reveal.tsx | 37 + components/SectionLabel.tsx | 17 + components/Timecode.tsx | 30 + content/site.ts | 138 + eslint.config.mjs | 18 + next.config.ts | 11 + package-lock.json | 6792 +++++++++++++++++++++++++++++++++++ package.json | 27 + postcss.config.mjs | 7 + public/fox.svg | 1 + tsconfig.json | 34 + 33 files changed, 8003 insertions(+) create mode 100644 .claude/launch.json create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 CLAUDE.md create mode 100644 DEPLOY.md create mode 100644 README.md create mode 100644 app/apple-icon.png create mode 100644 app/globals.css create mode 100644 app/icon.png create mode 100644 app/layout.tsx create mode 100644 app/not-found.tsx create mode 100644 app/page.tsx create mode 100644 app/robots.ts create mode 100644 app/sitemap.ts create mode 100644 app/work/[slug]/page.tsx create mode 100644 app/work/page.tsx create mode 100644 components/Footer.tsx create mode 100644 components/Fox.tsx create mode 100644 components/Frame.tsx create mode 100644 components/Header.tsx create mode 100644 components/Hero.tsx create mode 100644 components/ProjectCard.tsx create mode 100644 components/Reveal.tsx create mode 100644 components/SectionLabel.tsx create mode 100644 components/Timecode.tsx create mode 100644 content/site.ts create mode 100644 eslint.config.mjs create mode 100644 next.config.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 postcss.config.mjs create mode 100644 public/fox.svg create mode 100644 tsconfig.json diff --git a/.claude/launch.json b/.claude/launch.json new file mode 100644 index 0000000..74eba1f --- /dev/null +++ b/.claude/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.0.1", + "configurations": [ + { + "name": "ttvfx-dev", + "runtimeExecutable": "npm", + "runtimeArgs": ["run", "dev", "--", "-p", "3210"], + "port": 3210 + } + ] +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5ef6a52 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..643577d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,9 @@ + + +# This is NOT the Next.js you know + +This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices. + +This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean. + + diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..43c994c --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 0000000..a658db4 --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,64 @@ +# Deploying ttvfx.com + +The site is a fully static export, so there is no Node process to run in production. + +## 1. Build + +```bash +npm ci +npm run build # outputs ./out +``` + +## 2. DNS + +`ttvfx.com` does not resolve yet. At the registrar or DNS host (Cloudflare, if +you want it alongside twotalesvfx.com), add: + +| Type | Name | Value | +|------|------|-------| +| A | `@` | `` | +| A | `www`| `` | + +## 3. Upload + +```bash +rsync -avz --delete out/ user@your-vps:/var/www/ttvfx.com/ +``` + +## 4. nginx + +`/etc/nginx/sites-available/ttvfx.com`: + +```nginx +server { + listen 80; + server_name ttvfx.com www.ttvfx.com; + root /var/www/ttvfx.com; + + # www -> apex + if ($host = www.ttvfx.com) { return 301 https://ttvfx.com$request_uri; } + + location / { + try_files $uri $uri/ $uri.html =404; + } + + # Hashed build assets can be cached forever + location /_next/static/ { + add_header Cache-Control "public, max-age=31536000, immutable"; + } + + error_page 404 /404.html; + + gzip on; + gzip_types text/css application/javascript image/svg+xml application/json; +} +``` + +```bash +sudo ln -s /etc/nginx/sites-available/ttvfx.com /etc/nginx/sites-enabled/ +sudo nginx -t && sudo systemctl reload nginx +sudo certbot --nginx -d ttvfx.com -d www.ttvfx.com # HTTPS via Let's Encrypt +``` + +(For Apache, set `DocumentRoot` to the same folder plus `ErrorDocument 404 /404.html`. +Because the build uses `trailingSlash`, every page is a folder with its own `index.html`.) diff --git a/README.md b/README.md new file mode 100644 index 0000000..dfd7379 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# ttvfx.com + +The marketing site for **Two Tales VFX**: Next.js 16 (App Router), Tailwind CSS v4, exported as static HTML. + +```bash +npm install +npm run dev # http://localhost:3000 +npm run build # static site in ./out +``` + +## Editing content + +All copy (studio info, services, projects, contact email) lives in +[`content/site.ts`](content/site.ts). Items marked `TODO` are placeholders. + +- **Projects:** add an entry to `projects`. Put stills in `public/work/` and set + `image: "/work/name.jpg"` (2.39:1 looks best). `video` accepts an MP4 path + or a Vimeo/YouTube embed URL. Each project gets a page at `/work//`. +- **Showreel:** drop `public/reel.mp4` (muted, looping, ideally under 10 MB) and + `public/reel-poster.jpg`, then set `site.reel` and `site.reelPoster`. +- **Logo:** the fox mark is a vector in `components/Fox.tsx` / `public/fox.svg`, + traced from `G:\_ADMIN\TWOTALESLOGO.png`. Favicons are `app/icon.png` and `app/apple-icon.png`. + +## Structure + +``` +app/ + page.tsx home: hero, selected work, services, studio, contact + work/page.tsx project index + work/[slug]/ project detail pages (statically generated) + sitemap.ts, robots.ts +components/ Header, Footer, Hero, Frame, ProjectCard, Reveal, Timecode… +content/site.ts all site content +``` + +Deployment is covered in [DEPLOY.md](DEPLOY.md). diff --git a/app/apple-icon.png b/app/apple-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bdfb2c024cec188db63c1d610265557195e0698e GIT binary patch literal 1102 zcmeAS@N?(olHy`uVBq!ia0vp^TR@nD2}o{QKQWbofko8Q#WAE}&fB|Zi*7jxxF*i| z)b;VVz2@HUNkN*dI~11ueEH7uzR^<^C%b(?3diEFXYXV<|EKcTYKFoGqSKg#3LTXA zJdQASS||vzt2}COk(tneT~PLs!EV`qdpR1PI?T6_=)V)eSJK`5(WB?7!+9Wg4UpRh zprRNAJ@Edw|>>KyELP+w~Oapc%64;nO-I_$)erBJEEb`hf}}BKURy&F5%&+OQnR z)rjburgL5T`?*f#o1SNWEdX*;r+g~*Oo@`2x$m6L#pIhwmb0b?7M*1)oRjKlp8T)K z#eVZP%YQYS4;HP7E^DanTd@2_lH9lDI}~#R+)h^b7789$if+pqjOC*seG*du~Z zZEn4LY_eN*$F-XCb`1tO@sfc~{F`O|onP5`c=zn~l=IUz$>bg~yt6!@ z&Tik`f6`D|=lIRIv#A$2ryst(bK7+G;8W)>^LrRe%S}HVD$m(xe7@*_D867P5J3XZ zXV3cf;(KiB=6#_jtN!h6_YXf|5wvf53BS63;Dgg$H`o3Yzcb~*n!`E3@c6f}C8kd| zY)Sn6#_5YMck#&aXPZm=UQ@bSl%u;_Y3X@qKcFANRl?$y-oCgjs;@vhX`iy6wEF@V zK8epALUCcUVluq`Z?4ZVn{~f*ot%50d6pLQ<`w(aUEg#|@_v2S+t2RTAM?*IjkKvg zAJ}glrX-i=l5<~jcJ+kZJP*$$mi*=y)AhCclh&TNeZHl1$K=}Ie9Ohe}8+ZZD{?+%whik>`d2=S#oKu0HzHvE$w8jiMS8uiJ}1Y~4QprGtHM z*^A51*L1Z0%Cr@Zi{_l=p{=-SMnPnyh{nX4`PJ{&ukESd%WyERLRoNr%#px}9p-(H zR(KrYFE0#Hd33OR)AI!r>Mh?Hs01JBSgbVhpwiL6i6;y`0+pmRtN<#x6#B+OOX#JF zvy5n`$#nI;E*Gg&V(zX=zE669%Fp=EH$PbXp*=OO&zk4ta8 z*0)$mu+`$9(nR+oALWH^D)p&*EK%ZHsq>#V{K!YQNQ;SofJOvP`&0ix=Xm-lFJ~ti z_bH}AihYm0zPWTfUNgyD@KMI%6rYq&*1r!Jlafop$t7k=yoFVdQ I&MBb@0AN$@WdHyG literal 0 HcmV?d00001 diff --git a/app/globals.css b/app/globals.css new file mode 100644 index 0000000..a337210 --- /dev/null +++ b/app/globals.css @@ -0,0 +1,87 @@ +@import "tailwindcss"; + +:root { + --ink: #0a0a0b; + --ink-2: #111113; + --line: rgb(255 255 255 / 0.1); + --fg: #ecebe8; + --muted: #8d8c88; + --accent: #e8572a; +} + +@theme inline { + --color-ink: var(--ink); + --color-ink-2: var(--ink-2); + --color-line: var(--line); + --color-fg: var(--fg); + --color-muted: var(--muted); + --color-accent: var(--accent); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); + --font-display: var(--font-josefin); +} + +html { + color-scheme: dark; + scroll-behavior: smooth; + scroll-padding-top: 5rem; +} + +body { + background: var(--ink); + color: var(--fg); +} + +::selection { + background: var(--accent); + color: var(--ink); +} + +/* Film grain overlay */ +.grain::after { + content: ""; + position: fixed; + inset: -50%; + pointer-events: none; + z-index: 60; + opacity: 0.07; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); + animation: grain 0.8s steps(4) infinite; +} + +@keyframes grain { + 0% { transform: translate(0, 0); } + 25% { transform: translate(-3%, 2%); } + 50% { transform: translate(2%, -3%); } + 75% { transform: translate(-2%, -1%); } + 100% { transform: translate(1%, 3%); } +} + +/* Scroll reveal (toggled by ) */ +.reveal { + opacity: 0; + transform: translateY(24px); + transition: opacity 0.9s cubic-bezier(0.2, 0.7, 0.2, 1), transform 0.9s cubic-bezier(0.2, 0.7, 0.2, 1); +} +.reveal.is-visible { + opacity: 1; + transform: none; +} + +@keyframes drift { + 0%, 100% { transform: translate3d(-6%, -4%, 0) scale(1); } + 50% { transform: translate3d(6%, 4%, 0) scale(1.15); } +} +.animate-drift { animation: drift 18s ease-in-out infinite; } + +@keyframes scan { + from { transform: translateY(-10vh); } + to { transform: translateY(110vh); } +} +.animate-scan { animation: scan 7s linear infinite; } + +@media (prefers-reduced-motion: reduce) { + html { scroll-behavior: auto; } + .grain::after, .animate-drift, .animate-scan { animation: none; } + .reveal { opacity: 1; transform: none; transition: none; } +} diff --git a/app/icon.png b/app/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..602a492dbdda83ac4750d277de8888ea0928fe62 GIT binary patch literal 3432 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 pBp0kBXn*5Bga2rA) { + return ( + + +
+
{children}
+