feat: speakers lineup page synced from Sessionize#228
Merged
Conversation
Add functions/src/speakers/ domain — daily onSchedule refreshSpeakers mirrors the Sessionize Speakers view into Firestore `speakers` with a guarded upsert/delete batch. Includes the plan + brainstorm docs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Scope a public read on the `speakers` collection in firestore.rules (invoices/default stay deny-all), add getFirestoreDb() on the App-Check app, and add browser-safe speaker types + kind→icon map + helpers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add /speakers page with a live Firestore-backed Speakers island (noir colour-bleed cards mirroring /team, loading/empty/error states, monogram + onError + tagline/clamp fallbacks), page styles, a <noscript> fallback, and a Speakers nav entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Guard against duplicate Sessionize speaker ids, dedupe links, strip URL userinfo + re-check scheme at the render boundary, add a fetch timeout, drop a redundant delete-guard branch, and document the batch 500-op cap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Point the sync at the /view/All endpoint (object payload) and store the complete Sessionize speaker record (bio, sessions, categories, Q&A, isTopSpeaker) alongside processed links/order. Rename the domain speakers/ -> sessionize/ and refreshSpeakers -> refreshSessionize; the browser still reads the `speakers` collection unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Feature is implemented and shipped; drop the planning artifacts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The deployed refreshSessionize 400'd because the SESSIONIZE_ENDPOINT_ID secret held a full URL, which the code appended /view/All to. Add parseEndpointId to extract the bare id from a URL or bare value, try the All-data view then fall back to the Speakers view, and accept either the object or bare-array payload shape. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the /team-cloned colour-bleed mugshot cards with a unique speaker treatment: full-bleed grayscale portrait, overlaid caption + gradient scrim, lineup index number, and a restrained hover (zoom + desaturation lift + red edge). Same film-noir system and brand fonts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Append a dashed "More speakers announced soon" tile to the /speakers grid, and add an auto-rotating speaker spotlight panel to the home page (SpeakersTeaser island) that cycles the live lineup and links to /speakers. The teaser renders nothing until data loads. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the card caption into a panel below the portrait so text no longer covers the image, and make each card a button that opens an accessible detail dialog (portrait, bio, talks, social links) with focus trap, Esc / backdrop close, scroll lock, and focus restore. Expose bio + sessions on the client Speaker type. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Switch the home SpeakersTeaser to client:load — it renders nothing until Firestore resolves, so a client:visible observer never fired on the zero-height placeholder and it never hydrated. Also join speaker session ids against the All payload's top-level sessions[] so the detail dialog shows talk titles (the All view gives a speaker only session ids). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Raise the detail dialog overlay above the fixed site header (z 10050 > 10001) so the close button is no longer covered by the top bar. - Equalize speaker card heights: stretch cards to row height, reserve two-line name/tagline slots, and bottom-align the "View profile" CTA. - Replace the rotating home spotlight with a grayscale mugshot wall that shows the whole lineup at once (noir suspects board) + an "All" tile. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The lineup changes often; don't cite a fixed number. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Give the speaker detail dialog a fixed desktop height (min(78vh, 620px)) so it doesn't resize between speakers with longer/shorter bios; the content column scrolls. Mobile keeps content height. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the generic grayscale boxes with an evidence-wall dossier look: red corner crop-brackets, a scanline + red-spotlight grain overlay, higher-contrast duotone portraits, and a red-ruled file plate for names. Applied to both the home lineup wall and the /speakers grid. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the TV/scanline layer from the home lineup tiles (and the "ALL" tile); keep the crop-brackets, red spotlight tint, and name plates. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
"On the bill" read awkwardly. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Show 5 mugshots at a time (down from 11) and rotate the visible set through the full roster on a timer (crossfade, pause on hover, respects reduced motion). Drop the "+N" tile for a centered "See all speakers" link below the wall. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cap the wall at 4 tiles in one fixed row at every width (no wrapping on phone/tablet), and centre it at max-width 880px so wide desktops get left/right breathing room. Tighten spacing/name size on small screens. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove the TV/scanline overlay and the 01/02 index from the speaker grid cards; keep the crop-brackets, red spotlight tint, and name plate. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Align the speaker card name to .crew-name (Bebas 1.55rem / lh 1 / 0.04em) and the tagline to .crew-role (JetBrains Mono 0.75rem / 0.16em / uppercase / lh 1.4). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drop the inset margin around the red-tinted caption so it aligns edge-to-edge with the photo instead of floating with a gap. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Resolve each session's abstract (not just its title) from the All payload's sessions[] during sync, store it on the speaker doc, and render the title + description in the modal's Talks section. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The audit waited for 'networkidle', which never fires while the Firestore speakers wall / RTDB tickets listeners hold a channel open — timing out page.goto. Load the DOM, then wait for idle only briefly and fall through. Add /speakers to the audited paths, and give the "all speakers" link a visible focus ring instead of removing its outline. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Load the first row of speaker card images eagerly (fetchpriority high) instead of lazy, clearing Astro's above-the-fold loading audit and improving LCP on the lineup page. Later rows stay lazy. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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
Adds a
/speakerslineup page for DevFest.cz 2026, fed by a daily Cloud Function that mirrors the Sessionize All-data view into a public-read Firestorespeakerscollection. Cards follow the approved/teamfilm-noir treatment (grayscale headshots, cursor colour-bleed hover). Also drops the "2025 Edition" link from the top nav.Data flow: Sessionize → daily Cloud Function → Firestore
speakers→ browser island — mirroring the existing tickets pipeline, with Firestore instead of RTDB for structured docs + explicit ordering.Why
The site had no way to show who's speaking. The org maintains the roster in Sessionize (its source of truth); a daily sync keeps the page current with zero per-speaker code edits.
Behavior
refreshSessionize(onSchedule, daily 06:00 Europe/Prague,europe-west1) fetches…/view/All, extracts + validates thespeakers[], and writes the full speaker record (bio, sessions, categories, Q&A, isTopSpeaker + sanitized/kind-mapped links +order) in one atomic batch. Sessions/rooms/categories are on the wire but not persisted yet./speakerspage —client:loadReact island subscribes live (onSnapshot,orderBy('order')) with loading / ready / empty ("Lineup announced soon") / error states. Per-card fallbacks: missing/broken photo → monogram (onError), missing tagline omitted, long text clamped, unknown link type → globe.<noscript>fallback for no-JS/crawlers (client-only render is an accepted v1 tradeoff, matching Tickets).firestore.rulesopens read onspeakersonly —invoices(PII) and the default stay deny-all; client writes blocked (Admin SDK writes). App Check attach-only (enforcement is a later console toggle). Link URLs sanitized to http(s) + userinfo-stripped at write time, re-checked at the render boundary.Speakersentry added;2025 Editionremoved.Files
functions/src/sessionize/— new domain:params.ts(endpoint-id secret),sessionize-api.ts(pure fetch/validate/normalize + delete-guard),refresh-sessionize.ts(scheduler),index.ts; wired intofunctions/src/index.ts.firestore.rules— scopedspeakerspublic read + revised header/merge note.src/lib/firebase.ts—getFirestoreDb()on the App-Check app.src/lib/speakers.ts— browser-safe types,linkType→icon map (globe fallback),initials, defensive doc mapper.src/components/Speakers.tsx+Speakers.module.scss— island + noir card styles.src/pages/speakers.astro+speakers.scss— page shell, hero,<noscript>.src/components/Menu.astro— add Speakers, remove 2025 Edition.Deploy prerequisites (maintainer)
firestore.rulesis not wired intofirebase.json(shared project) — the rule change must be merged into the Firebase console by hand, scoped tospeakersonly.firebase functions:secrets:set SESSIONIZE_ENDPOINT_ID— a JSON All data endpoint id (not an embed id; an embed returns HTML).speakersread block into the console ruleset — do not wideninvoices/default.refreshSessionizerun to populate before launch (else the page shows "Lineup announced soon").