The Mappe section rendered "Bildschirmfoto einsetzen" placeholders for
all three entries. Two of them are our own concept work with invented
businesses, so they can be shown:
- Maison Verte (tanzschulprototyp-maisonverte.html)
- Tanzhaus Kranich, Terra variant (tanzhaus-kranich-terra.html)
Captured at 1600x900 @2x via Playwright, downscaled to WebP. The first
card spans the full grid width (.mappe-item:first-child), which maxes
out at 1104px, so Maison Verte is 2208px wide for a true 2x; the
half-width card is 1200px. 60 KB and 32 KB.
Stored under public/mappe/ rather than public/uploads/, because uploads
are gitignored (production mounts a volume there) and these are fixed
repo assets that must ship with the build.
The third entry (faceart Goslar) deliberately keeps its placeholder: it
carries a real business's name, prices and copy, and the owner has not
approved any of it - which is why nameHidden is already true. A
screenshot would put her name on our sales page regardless.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The footer rendered a red "[Nachname]" placeholder for both names.
Pascal's surname is already in the repo via impressum.anbieterAngaben
("Pascal Scholz, Weinkampswende 14, 30539 Hannover"), so it can be
filled in without guessing.
Lara's surname stays empty on purpose - it is not recorded anywhere in
the repo and must not be invented.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Replace the checkbox-hack mobile menu (display:none checkbox was
unreachable by keyboard/screen readers) with a real button using
aria-expanded/aria-controls
- Add a skip-to-content link
- Add a consistent :focus-visible outline across interactive elements
- Underline prose links by default instead of only on hover (WCAG 1.4.1
use-of-color), keep nav/button links unstyled since they're already
visually distinct
- Respect prefers-reduced-motion for smooth scrolling
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lets content edits pushed through Git reach the live admin-content
volume (and vice versa) without silently overwriting whatever was
changed through the admin editor UI in the meantime.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
.build-log set display:flex unconditionally, which as an author rule
beat the browser's default [hidden]{display:none} (author styles always
win over user-agent styles regardless of the hidden attribute). Add an
explicit .build-log[hidden]{display:none} rule so closing the panel -
and its initial hidden state on page load - actually works.
Rich-text fields (hero lead, paragraphs, FAQ-style copy, datenschutz
sections, etc.) now use a proper toolbar editor - bold/italic/underline
buttons plus a per-selection font picker drawn from the same curated
font list as the Design tab - instead of typed **markdown** syntax.
Content is stored as a small allowlisted HTML subset (strong/em/u/span
with a validated data-font/br) and sanitized server-side with
sanitize-html on every save (admin/lib/rich-sanitize.js), independent
of what the client sends - verified this rejects script tags, event
handlers, and made-up font names while preserving legitimate markup,
and that re-saving already-sanitized content is a no-op.
The Astro site mirrors the same allowlist at render time for
defense-in-depth (src/lib/rich-sanitize.ts) and only requests Google
Fonts for fonts actually referenced somewhere in content, in addition
to the two Design-tab fonts - verified a font used via a data-font span
gets its own Google Fonts request and [data-font] CSS rule in the
built page, and that unused fonts don't get requested.
Migrated the three existing fields that used the old markdown-lite
syntax (**bold**/*italic*) to real HTML so they keep rendering
correctly under the new system.
Also replaces the single long scrolling admin page with a tabbed
sidebar (grouped "Inhalte"/"Design") to cut down on clutter, per
feedback that the all-in-one-page layout was hard to navigate.
Docker Compose interpolates $VAR references within env_file values (not
just docker-compose.yml), so a raw bcrypt hash like $2a$12$... gets
partially swallowed as undefined variables - exactly the truncation
Pascal hit. Pipe hash-password.js output through `sed 's/\$/$$/g'` and
store the escaped hash; document the 63-char (60 + 3 escaped $) length
check to verify it went in correctly.
ADMIN_USERNAME/ADMIN_PASSWORD_HASH now optionally extends to _2, _3, ...
for additional accounts (e.g. a second login for Lara), all with equal
access - no roles, still no self-service password reset. Falls back to
a dummy bcrypt compare on unknown usernames to keep login timing
consistent. Verified locally: both users log in, wrong password and
unknown usernames are rejected, and single-user setups still work
unchanged (backward compatible).
Also documents the optional second-user setup in DEPLOYMENT_ADMIN.md (B6/B7).
Dockerfile.admin sets WORKDIR /app and copies the whole repo there, so
hash-password.js lives at /app/admin/hash-password.js, not /app/hash-password.js
(same pattern as the existing admin/server.js CMD). The docker compose run
command in B6 was missing the admin/ prefix, causing MODULE_NOT_FOUND.
The ad-hoc `npm install bcryptjs` in a throwaway container hit npm's known
"Tracker 'idealTree' already exists" bug. Reorder Teil B so the admin
service is built first (a proper `npm ci`, the same path already used for
the real deployment), then generate the password hash and session secret
via `docker compose run --rm admin ...` against that image instead.
The deploy host only has Docker installed, not Node/npm directly, so the
original `npm install` / `node hash-password.js` steps failed with
"command not found". Both the bcrypt hash and session secret are now
generated with a throwaway `node:22-alpine` container instead.
Adds a self-service editor at admin.larpaso.de so texts, images, links,
Impressum/Datenschutz, fonts, font sizes and colors can be changed without
a developer. All homepage/legal content moves into content/site.json and
a new content/theme.json drives every CSS color/font/size token; the
Astro pages read from these at build time.
The admin/ service (Node/Express) provides bcrypt+session login with
rate limiting, a schema-driven form UI (including a hex/RGB/alpha color
picker), image upload, and a publish action that runs the Astro build
and republishes into a volume shared with the nginx website container -
so edits go live without redeploying that container.
See DEPLOYMENT_ADMIN.md for the server-side setup steps.
Replaces the previous multi-page structure (Startseite, Leistungen,
Über uns, Kontakt) with one long-form page covering pricing, work
samples, process and FAQ, matching the new visual identity (dark
theme, serif display type, sage-green logo mark). Old routes now
redirect to the corresponding anchors. Adds Impressum and Datenschutz
stub pages with clearly marked placeholders for the still-missing
real content (contact details, founder bios, portfolio, pricing
specifics).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
larpaso.de is now confirmed live (verified 200 OK + valid TLS cert).
Records what was done for Teil B and clarifies the ongoing workflow:
Claude Code can edit/build/push code changes directly, but the
server-side rebuild step still needs to run on the server manually
unless an auto-deploy hook is set up later.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T8eccP4XnXFJvpSYMm26as
Documents that Teil A is verified (build tested locally) and provides
exact commands for deploy key, docker-compose service, Caddyfile
block, and DNS steps so the server-side rollout needs no improvisation.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T8eccP4XnXFJvpSYMm26as
Multi-stage Dockerfile (node build -> nginx serve), nginx config with
_astro asset caching, .dockerignore, and .gitignore hardening for
TLS key files. Astro already builds static output with site set to
https://larpaso.de.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JmTVcwbGJBYP7gu4P7b1yG