From 22bc90304140c0874f3f7f0b6782db9269dc3d7d Mon Sep 17 00:00:00 2001 From: twotalesanimation <80506065+twotalesanimation@users.noreply.github.com> Date: Sat, 17 Oct 2026 06:21:25 +0200 Subject: [PATCH] /previz: password protect, add PDF export of the crowd previz - nginx basic auth on /previz/ (login file lives on the server, mounted from docker/auth/, see DEPLOY.md step 8); private, no-store so Cloudflare never caches it - Print styles for previz pages: dark, A4 landscape, one section a page; videos print as poster frames with a link - scripts/previz-pdf.mjs (npm run previz:pdf) prints the page to public/previz/crowd/crowd-previz.pdf via Edge/Chrome DevTools - "PDF" download link on the page Co-Authored-By: Claude Opus 5.5 --- .dockerignore | 2 + .gitignore | 3 + DEPLOY.md | 16 +++++ app/globals.css | 17 +++++ app/previz/crowd/page.tsx | 64 +++++++++++------ components/previz/ShotList.tsx | 4 +- docker-compose.yml | 4 ++ docker/nginx.conf | 10 +++ package.json | 3 +- public/previz/crowd/crowd-previz.pdf | Bin 0 -> 1592785 bytes scripts/previz-pdf.mjs | 103 +++++++++++++++++++++++++++ 11 files changed, 200 insertions(+), 26 deletions(-) create mode 100644 public/previz/crowd/crowd-previz.pdf create mode 100644 scripts/previz-pdf.mjs diff --git a/.dockerignore b/.dockerignore index 919fa26..0256fe1 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,3 +7,5 @@ content/*.mp4 content/*.pdf *.md !README.md +docker/auth +content/crowd diff --git a/.gitignore b/.gitignore index cc2d5e1..6f17b5d 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,6 @@ content/*.mov # previz source renders (web versions in public/previz) content/crowd/ + +# server-side login files for /previz/ (see DEPLOY.md) +docker/auth/ diff --git a/DEPLOY.md b/DEPLOY.md index b21607b..7702fe0 100644 --- a/DEPLOY.md +++ b/DEPLOY.md @@ -112,6 +112,22 @@ The workflow is already in the repo (`.gitea/workflows/deploy.yml`). In Gitea, o Test it: push any commit to `main` and watch **Actions** in the repo. The job should run `git pull` and `docker compose up -d --build`, then the site updates. +### 8. Password for /previz/ + +Client previz pages under `/previz/` need a login (HTTP basic auth in `docker/nginx.conf`). +The password file stays on the server and is never committed. Create it once: + +```bash +cd /opt/ttvfx_web +mkdir -p docker/auth +printf "production:$(openssl passwd -apr1)\n" > docker/auth/previz.htpasswd # prompts for the password +docker compose up -d --force-recreate +``` + +`production` is the username. To change the password, run the `printf` line again. nginx reads +the file on every request, so no restart is needed. To add a second login, append another line +with `>>`. If the file is missing, `/previz/` returns a 500 error, so it is never left open. + --- ## Day to day diff --git a/app/globals.css b/app/globals.css index c43facc..05dbac1 100644 --- a/app/globals.css +++ b/app/globals.css @@ -89,6 +89,23 @@ body { .reveal { opacity: 1; transform: none; transition: none; } } +/* Previz pages (/previz/*) print/PDF dark, edge to edge, like the screen version. + Margins come from padding cloned onto every page instead of white @page margins. + The page sets its own @page (landscape, no margin) so it doesn't get the report's. */ +@media print { + html:has(.previz), html:has(.previz) body { background: var(--ink) !important; color-scheme: dark; } + .previz { + padding-block: 12mm; + box-decoration-break: clone; + -webkit-box-decoration-break: clone; + print-color-adjust: exact; + -webkit-print-color-adjust: exact; + } + .previz.grain::after { display: none; } + .previz figure, .previz li, .previz img, .previz dl { break-inside: avoid; } + .previz h2 { break-after: avoid; } +} + /* Printable VFX report (/set/report/view): A4 paper, keep the accent colours */ @media print { @page { size: A4; margin: 14mm 12mm; background: white; } diff --git a/app/previz/crowd/page.tsx b/app/previz/crowd/page.tsx index 1d0d377..6bc1206 100644 --- a/app/previz/crowd/page.tsx +++ b/app/previz/crowd/page.tsx @@ -162,17 +162,25 @@ const setTips: Tip[] = [ function PreviewVideo({ src, poster, label }: { src?: string; poster?: string; label: string }) { if (src) { return ( -