Commit Graph

7 Commits

Author SHA1 Message Date
3dtours 89beb5f160 web: put the traffic screen in the admin frame
installTracking() beacons one view per page load and one click per control that
carries a data-key, so every existing button is already counted. The new STATS
pane reads it back: a 7/30/90-day range, the three totals, an SVG timeline and
eight proportional bar lists (pages, clicked features, country, region, city,
browser, system, device).
2026-09-18 14:23:25 +07:00
3dtours b4d5d2926b web: give each member a photo folder and burn the strip into the export
Every member gets /photos — their own uploads, counted against a 12-photo
cap, each card showing the tagline and the technical line the studio would
print. The studio gains SAVE PHOTO n/12 in the top bar: it renders the full
resolution look, stores the strip (tag/title/meta) with the upload so the
landing reel frames it the same way, and refuses past the cap.

EXPORT now burns that strip into the file: the amber #TAG over the photo's
top-left plus a dark caption band below carrying the recipe name and the
ISO / grain / warmth line. The live preview stays clean, and the saved
upload stays clean too — the reel draws its own frame from the stored
labels, so a burned band would tag the tag twice.

Admins manage any photo through DELETE /api/photos/:id; members only their
own. The users table's photo counts stay in step with the folder.
2026-09-18 10:56:26 +07:00
3dtours 6bbf77860b web: account avatars, member /profile, framed admin panel
- an account can carry a picture: POST /api/auth/avatar (raw bytes,
  sniffed, replaces and unlinks the old file) and the public
  GET /api/users/:id/avatar. It rides wherever the account is named —
  the landing chip, the studio TopBar, the profile form.
- new /profile page for members, sharing one Profile form (picture,
  email, password) with the admin drawer.
- /admin is now one bordered frame whose left column is
  Profile / User account / Pictures / Close. Pictures lists every
  photo in the system with the slot that shows it; User account lists
  each account's name, email, picture and contribution count.
- account control opens a menu: Admin page + Log out for an admin,
  Profile + Log out for a member.
2026-09-18 08:19:36 +07:00
3dtours ffdefd2c9c feat(photos): community film strip uploads + admin moderation
Backend
- photos table + upload storage under DATA_DIR/uploads (magic-byte sniffing,
  no multipart dep, SVG rejected, wx exclusive writes)
- POST/GET /api/photos, GET /api/photos/:id/file with nosniff + sandboxed CSP
- admin routes (ADMIN_EMAILS allowlist): list, delete one, clear all
- identity-keyed rate limits (login 20/15m, signup 5/h, upload 60/h)
- cookie gains Secure when the request is https (via trustProxy)
- /api/auth/me now 200 {user:null} instead of 401 when signed out

Frontend
- landing strip section: signed-in users upload straight from the reel,
  guests get a /app?auth=1 link
- /admin page: grid of uploads with delete + clear all
- nginx: nosniff / X-Frame-Options / Referrer-Policy, forward
  X-Forwarded-Proto so the API can mark cookies Secure behind TLS

Tests: docker/backend test/security.mjs (45 checks)
2026-09-17 22:35:12 +07:00
3dtours cc88471256 Offer the three proposed font groups in Themes
The theme menu now carries a fourth choice beside light/dark and the accent:
the font pairing. All three pairings are free for commercial use (SIL OFL),
have a Vietnamese subset, and are self-hosted — the landing still makes no
CDN request.

- styles/fonts.css: 22 @font-face blocks for Plus Jakarta Sans, Inter,
  JetBrains Mono, Fraunces, Be Vietnam Pro, Courier Prime and Space Mono,
  vietnamese/latin-ext/latin subsets only, under public/assets/fonts.
- styles/tokens.css: [data-fonts="studio|editorial|native"] sets --font,
  --font-heading and --mono. Studio (Plus Jakarta Sans + Inter + JetBrains
  Mono) is the default.
- theme/: FontSetId + FONT_SETS, persisted as rc.fonts, applied as
  <html data-fonts> next to data-theme and data-accent.
- TopBar and the landing nav both get the picker; on the landing the theme
  tool now opens a small popover (light/dark + font group) instead of
  toggling on click.
- landing.css: --lp-display/--lp-mono now resolve to the chosen group, so the
  picker retypes the whole page. Syne.woff2 goes with its @font-face.
2026-09-17 19:20:23 +07:00
3dtours 340f0374fc Rebuild the web landing page to the blueprint spec
Ten dark cinematic sections: fixed glass nav with a hamburger sheet, hero
with the badge/dual CTA/stats bar, a pausable 35mm film-strip marquee, the
live preset tester (5 stocks, HUD, spec bars), the three-knob custom recipe
simulator, the 6-card feature grid, the QR sharing showcase, free-vs-Pro
pricing, reviews + FAQ accordion and the final CTA/footer.

The page owns its palette and its .lp-* styles, so it renders the same in
either workspace theme, and it pulls no CDN: Syne is bundled and the six
sample negatives are vendored (see docker/README.md). Store buttons raise a
toast instead of the old alert(). The now-dead landing CSS and the unused
land.* dictionary keys are gone.
2026-09-17 18:26:18 +07:00
3dtours 8c6e7930db Add self-contained docker/ stack for the web UI
`docker/` now holds the whole web build — frontend (Vite + React + CanvasKit),
backend (Fastify + SQLite) and the compose file — so the folder can be moved to
another machine and run without the React Native project:

    cd docker && cp .env.example .env && docker compose up -d --build

Only `${WEB_PORT:-8090}` is published; nginx serves the SPA and proxies /api to
the `api` container over Docker's DNS. Photos never reach the server.

The shared render code is vendored into `docker/frontend/shared/` and aliased to
a CanvasKit shim, so the app's own frameUtils/toneShader/jpegDpi run unchanged.

Fix the all-black render on GPU surfaces: `MakeWebGLCanvasSurface` creates a
separate WebGL context per call, and a texture from one context cannot be
sampled by a surface on another — so any pass that drew a snapshot onto a second
surface (output sharpen, screen sharpen, polaroid/wallframe cards) came out
solid black, while the raster fallback was correct. Use one shared
GrDirectContext + MakeRenderTarget instead.

Verified in headless Chromium against the running stack: 12MP JPEG in, preview
mean=120.5 sd=60.5, export 2048x1536 mean=107.2 sd=62.1, JFIF density 300/300,
EXIF present, no console errors; health/signup/login/me/recipes all 2xx through
the nginx proxy.
2026-09-17 17:43:03 +07:00