feat(invite): personal invitation page for each team member - #325
Open
ryzizub wants to merge 9 commits into
Open
feat(invite): personal invitation page for each team member#325ryzizub wants to merge 9 commits into
ryzizub wants to merge 9 commits into
Conversation
ryzizub
force-pushed
the
agent/astro-engineer/devf-45
branch
3 times, most recently
from
September 4, 2026 20:39
63fa6e9 to
475a929
Compare
An unlisted referral channel: one page per person in `src/content/team.json`,
written in their voice, which they share with their own network themselves.
Built from the approved direction B ("The Plate") — the portrait owns the right
half of the frame and the black-and-white → colour bleed is the page's only
effect.
Unlisted means three things at once, and dropping any one makes it cosmetic:
`noindex`, out of the sitemap, and linked from nowhere on the site.
`InviteCta` is the single primary action, and it is an island because it has to
do two things a plain link cannot: resolve the ti.to href from `/api/tickets`
(the live wave is not known at build time) and report `begin_checkout` carrying
the member's identity. With no discount code in v1, that click is the only
per-member attribution the channel has — checkout happens on ti.to and its
redirect carries no source. Until the endpoint answers, and if it never does,
the href falls back to the on-site ticket section.
Copy is the agreed generic v1 in `src/lib/invite.ts`, with one line varying by
role so the eleven pages are not the same page with a name swapped; when the
members write their own paragraphs, only `body` changes.
On a phone there is no side-by-side frame, so the scrim over the read zone is
what keeps the type off the face — a legibility condition, not a finish.
`Closer.astro` gains an optional `actions` slot so the closing repeat of the CTA
can be that same tracked island; the `actions` prop stays the path everywhere
else.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The header's red `Tickets` action is dropped on `/invite/*`: an invitation has exactly one primary action (`Take the ticket`, straight to ti.to), and the bar action was a second one pointing off the page. The mark and the menu toggle stay, so the bar is still a bar and every destination is one click away behind it. Also from review: - `Tickets.tsx` no longer returns null when the cache has no event slugs (the state before `refreshTicketsScheduled` has ever run). It deleted `#tickets` from the home page after hydration, so every `/#tickets` link — the header action, an invitation's fallback CTA — dropped the visitor at the top of the page with no ticket section anywhere. Only the ti.to link is dropped now. - The invite page's bleed script runs on `astro:page-load`, the repo convention: a soft navigation back to the page left the plate black and white, and that bleed is the page's only effect. - The plate declares a real `sizes` (62% of the frame on desktop, full-bleed under 860px) and asks for widths that exist: the masters are 700x875, so 1080/1440 only ever emitted one 700w candidate. The smaller widths are the saving actually available. - `/api/tickets` is memoised for the page, so the invitation's two islands make one request (the endpoint sends `max-age=0`; the HTTP cache cannot coalesce them). The caller's `signal` aborts its own wait, not the shared request. - `begin_checkout` fires only on the ti.to click. On the `/#tickets` fallback the visitor still presses Buy, which sends the event itself — reporting both double-counted the wave and filed an item-less event against the member. - README: register `invite_member` / `invite_member_name` as GA4 custom dimensions. Unregistered custom parameters are collected and invisible, and registration is not retroactive. - Title nit: no full stop before the em dash in the document title. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Six full-page screenshots and a dev log landed on this branch as `chore(agent): uncommitted changes from task` — ~15 MB of agent working files in a public repo, not part of the change under review. Removed from the tree; the blobs stay in this branch's history until the PR is squashed (or the branch is rewritten). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
BaseLayout gets an ogImage/ogImageAlt prop (absolute URL, default unchanged); each /invite/<id> page now points its own og:image at a prerendered og/invite/<id>.png built from the same team collection, so a new roster member gets a card automatically instead of silently falling back to the generic conference banner. Composed with satori + resvg using the two brand faces (Bebas Neue, JetBrains Mono) embedded as static TTFs, no new fonts.
npm audit fix for GHSA-2v37-7h3g-55p8, pulled in by the new satori/resvg deps.
Ports the picked "poster, one field" study (proto-og/render.mjs, 188016e2 on agent/mika/devf-45) into the shipping src/pages/og/invite/[member].png.ts: plate feathered by sharp (satori has no mask-image), the page's own headline sentence with red on "list." only, and the per-role line set in Special Elite — never the member's name, since that face lacks the Czech diacritics Bebas already carries (#305). Vendors SpecialElite-Regular.ttf alongside the two faces already in src/assets/fonts, for satori to embed at render time. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… preview origin - Rebased agent/astro-engineer/devf-45 onto 2026 (was missing checkoutUrl, dropped as a dead export upstream before InviteCta.tsx started importing it). - Restore checkoutUrl in src/lib/tito.ts so the merge build stops failing with MISSING_EXPORT. - OG invite card headline: "on the" + red "list." were two stacked lines instead of one row — group them as inline flex-row children with an explicit gap (satori collapses a trailing-space text node in a flex row). - Add PUBLIC_SITE_URL (src/env.d.ts, .env.example), defaulting to https://devfest.cz, so the invite page's og:image can be built against a preview channel's own origin for verification without touching astro.config.mjs's site/canonical/sitemap. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ryzizub
force-pushed
the
agent/astro-engineer/devf-45
branch
from
September 5, 2026 21:42
a84e849 to
5912acf
Compare
DEVFEST.CZ was rendered as text, drifting from the site mark. Now draws src/assets/logo.png (same file as Menu/Footer) as a data URI, pre-shrunk 2x by sharp so resvg doesn't fray the downscale.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
An unlisted personal invitation page for each of the eleven people in
src/content/team.json—/invite/<slug>, built from the approved visual direction B ("The Plate"). DEVF-45.Why
The team's personal network is the strongest channel the conference has and it currently has no home: a member shares a generic
devfest.czlink and the recipient gets marketing, not an invitation. Each page speaks in one member's voice and carries exactly one action.Behavior
team.jsonentry key, so the roster and the URLs can't drift apart.noindex(BaseLayout also drops the canonical + JSON-LD), excluded from the sitemap, and linked from nowhere on the site. Not access control: the URLs are guessable and nothing here is sensitive.InviteCtaresolves the ti.to href client-side from the cached/api/ticketsendpoint (the live wave isn't known at build time) and reportsbegin_checkoutwithinvite_member/invite_member_name. With no discount code in v1 that click is the only per-member attribution the channel has — checkout runs on ti.to and its redirect carries no source. Before the endpoint answers, and if it never does, the href is/#tickets.src/lib/invite.ts, with one closing line varying by role so the eleven pages aren't one page with a name swapped. When members write their own paragraphs, onlybodychanges.fetchpriority="high"); the colour layer is the enhancement (low).Files
src/pages/invite/[member].astro— the routesrc/lib/invite.ts— generic v1 copy + the role linessrc/components/InviteCta.tsx— the resolved, tracked CTAsrc/components/Closer.astro— optionalactionsslot so the closing repeat can be that islandastro.config.mjs— sitemap exclusionscripts/a11y.mjs— one invite page added to the sweepVerification
npm run build— 26 pages, all eleven invite pages emitted, none insitemap-0.xml,noindexpresent, no canonical.npm run a11y— 16 pages pass WCAG 2.2 AA (axe-core + the custom control-contrast pass), including/invite/eliska-cejpova/.🤖 Generated with Claude Code