Agent Skills: Cinema Voice Architect

|

UncategorizedID: Spectaculous-Code/raamattu-nyt/cinema-voice-architect

Install this agent skill to your local

pnpm dlx add-skill https://github.com/Spectaculous-Code/raamattu-nyt/tree/HEAD/.claude/skills/cinema-voice-architect

Skill Files

Browse the full folder contents for cinema-voice-architect.

Download Skill

Loading file tree…

.claude/skills/cinema-voice-architect/SKILL.md

Skill Metadata

Name
cinema-voice-architect
Description
|

Cinema Voice Architect

Expert skill for Cinema Mode and audio/voice implementation.

Quick Reference

Base Layer (CinemaShell)

| Component | Location | |-----------|----------| | CinemaShell | src/components/cinema/CinemaShell.tsx | | Cinema Background | src/components/cinema/CinemaBackground.tsx | | Background Music Picker | src/components/cinema/BackgroundMusicPicker.tsx | | Background Visual Picker | src/components/cinema/BackgroundVisualPicker.tsx | | Cinema Preferences Hook | src/hooks/useCinemaPreferences.ts | | Cinema Fullscreen Hook | src/hooks/useCinemaFullscreen.ts | | Cinema Audio Hook | src/hooks/useCinemaAudio.ts | | CinemaShellContext (shell ctx as React context for shell-less apps) | src/components/cinema/CinemaShellContext.ts |

Cinema OS / Command Center (orchestration layer)

| Component / Module | Location | |-----------|----------| | Design doc (read first) | Docs/cinema/CINEMA-OS.md | | Types (Intent / CinemaApp / CinemaNav) | src/cinema-os/types.ts | | Navigation reducer (stack) | src/cinema-os/navigation.ts | | Registry + deep-link translation | src/cinema-os/registry.ts | | App registration (all apps) | src/cinema-os/cinemaApps.ts | | CinemaOSProvider (stack + history + 2-level back) | src/cinema-os/CinemaOSProvider.tsx | | CinemaHost (the ONE persistent shell) | src/cinema-os/CinemaHost.tsx | | Contexts + useCinemaNav / useCinemaBackHandler | src/cinema-os/context.ts | | useCinemaShell (re-export) | src/cinema-os/useCinemaShell.ts | | Session history (localStorage) | src/cinema-os/history.ts | | Route element (/cinema-os/*) | src/cinema-os/CinemaOSRoot.tsx | | Shared settings/music sheets | src/cinema-os/CinemaOSSheets.tsx | | Shared settings-sheet wiring (single source for CinemaOSSheets AND CinemaReaderScreen). βš™ sheet = bg+textsize+completion+speed; Raamatun versio + lukuÀÀni (CinemaVersionVoiceSection) are split to the πŸ”Š music popover (audio side) | src/components/cinema/useCinemaSettingsProps.ts | | Cinema OS help overlay (i-icon β†’ "what is Cinema OS") | src/cinema-os/CinemaOSHelpOverlay.tsx | | CinemaOSNavCluster (top-left Back + Command Center pill; shared by CinemaOSChrome + ownsChrome apps like the prayer room) | src/cinema-os/CinemaOSNavCluster.tsx | | Apps: command-center / search / topic / question / questions / curated / reading / reading-plan / prayer-room / discipleship / summary / profile / author | src/cinema-os/apps/*.tsx | | AuthorCinemaApp (julkinen tekijΓ€profiili in-cinema; author intent, user+guest; reuses useAuthorContributions) | src/cinema-os/apps/AuthorCinemaApp.tsx | | PrayerRoomApp (multiplayer Rukoushuone in-shell; ownsChrome β†’ OS chrome hidden) | src/cinema-os/apps/PrayerRoomApp.tsx | | DiscipleshipCinemaApp (full TΓ€nÀÀn Discipleship-tila in-shell; dual-mode CinemaReaderScreen + ownsChrome) | src/cinema-os/apps/DiscipleshipCinemaApp.tsx | | SummaryCinemaApp (Kooste played as verse cinema; dual-mode CinemaReaderScreen mode="summaryItems" + ownsChrome) | src/cinema-os/apps/SummaryCinemaApp.tsx | | Launch entry (TΓ€nÀÀn top) | src/pages/tanaan-page/CinemaOSEntryCard.tsx |

Content Layer (CinemaReaderScreen)

| Component | Location | |-----------|----------| | CinemaReaderScreen | src/features/cinema/CinemaReaderScreen.tsx | | Bible Audio Hook | src/hooks/useBibleAudio.ts | | Auto-Advance Hook | src/hooks/useAutoAdvance.ts | | Chapter Bundle Hook | src/hooks/useChapterBundle.tsx | | Audio Sync | src/lib/cinemaAudioSync.ts | | Audio Estimation | src/lib/audioEstimation.ts | | Cinema Types | src/types/cinema.ts |

Discipleship Layer

| Component | Location | |-----------|----------| | Discipleship Orchestration | src/hooks/useDiscipleshipOrchestration.ts | | Discipleship Utils | src/components/cinema/discipleshipUtils.tsx | | Discipleship TaskSelector | src/components/cinema/DiscipleshipTaskSelector.tsx | | Discipleship InlineTask | src/components/cinema/DiscipleshipInlineTask.tsx | | Discipleship VerseBar | src/components/cinema/DiscipleshipVerseBar.tsx | | Discipleship Pomodoro | src/components/cinema/DiscipleshipPomodoroButton.tsx | | Discipleship Transition | src/components/cinema/DiscipleshipTransitionOverlay.tsx | | Verse Memory Quiz | src/components/practice/VerseMemoryQuiz.tsx | | Discipleship Tasks Hook | src/hooks/useDiscipleshipTasks.ts | | Nyt Summary Hook | src/hooks/useNytSummary.ts | | Reading Plan Quiz Hook | src/hooks/useReadingPlanQuiz.ts | | Smart Verse Selection | src/hooks/useSmartVerseSelection.ts |

Program Mode (Curated Plans)

| Component | Location | |-----------|----------| | CuratedPlanCinema (wrapper + flow router) | src/components/cinema/CuratedPlanCinema.tsx | | CuratedPlanFlow (continuous / linear) | src/components/cinema/CuratedPlanFlow.tsx | | ScrollingPlanFlow (scrolling) | src/components/cinema/ScrollingPlanFlow.tsx | | MiniTaskView (inside CuratedPlanFlow) | src/components/cinema/MiniTaskView.tsx | | Curated Plan Cinema Hook | packages/shared-practices/src/hooks/useCuratedPlanCinema.ts | | Shared types (GrandPlanProgressionType, MiniTaskContentConfig, MiniTaskRoute) | packages/shared-practices/src/types.ts | | Shared snap-scroll mechanics (InfoFlow + ScrollingPlanFlow) | src/hooks/useSnapScrollSteps.ts |

Info Cinema (Q&A / Topic Info-palaset)

| Component / Hook | Location | |-----------|----------| | InfoCinema (wrapper: CinemaShell + sheets) | src/components/cinema/InfoCinema.tsx | | InfoFlow (one-page snap-scroll, card per block) | src/components/cinema/InfoFlow.tsx | | InfoView (variant="scroll" = one block card) | src/components/cinema/InfoView.tsx | | CinemaQuestionWrapUp (Q&A finalSlide) | src/components/cinema/CinemaQuestionWrapUp.tsx | | QuestionAnswersCinema (the Film CTA) | src/pages/question-detail/QuestionAnswersCinema.tsx | | CinemaWrapUpRelatedQuestions (shared "LiittyvΓ€t kysymykset" list) | src/components/cinema/CinemaWrapUpRelatedQuestions.tsx |

Topic Cinema (aihe-cinema)

| Component / Hook | Location | |-----------|----------| | TopicCinema (wrapper: CinemaShell + sheets, mirrors InfoCinema) | src/components/cinema/TopicCinema.tsx | | TopicFlow (snap-scroll over heterogeneous TopicCinemaStep[]) | src/components/cinema/topic/TopicFlow.tsx | | TopicCinemaWrapUp (topic finalSlide: feedback β†’ related questions β†’ LaunchTile row Aiheet/Lukusuunnitelma/Rukoushuone) | src/components/cinema/topic/TopicCinemaWrapUp.tsx | | Card views (explanation / strongs / verses / highlights) | src/components/cinema/topic/Topic*View.tsx | | Step composer hook | src/hooks/cinema/useTopicCinemaSteps.ts | | TopicCinemaButton (the Film CTA on /aihe/<slug> + haku/aihekortti + preview) | src/pages/topic-page/TopicCinemaButton.tsx | | CinemaVerseActions (per-verse: pohdittavaksi/koosteeseen/muistiinpano + open) | src/components/cinema/CinemaVerseActions.tsx | | TopicChapterReader (kevyt luku-cinema: 6-verse scroll, prev/next chapter, X) | src/components/cinema/topic/TopicChapterReader.tsx | | TopicVerseCinema (1-verse carousel via @raamattu-nyt/cinema-reader embedded) | src/components/cinema/topic/TopicVerseCinema.tsx | | TopicVersesCinemaView (default verses step: full-width single-verse carousel, useVerseCues+useAutoAdvance, ←/β†’=verse, ↑/↓ bubble to flow=card. Renders a FIXED portaled bottom cluster (snapshot strip + segmented progress + consolidated control bar, one rounded pill: β—€prev Β· πŸ“–Lue (openChapter) Β· 🎬Cinema (Film icon β†’ 1-verse jae-cinema, only if onOpenVerseCinema) Β· πŸŽ“Discipleship popover (amber GraduationCap β†’ CinemaVerseActions menu: Pohdittavaksi/LisÀÀ koosteeseen/Muistiinpano/Harjoittele jae/Tutki jaetta) Β· β–Ά/⏸ (amber play/pause) Β· ☰Kaikki (β†’onShowAllVerses, only if set) Β· β–Άnext Β· β”‚ Β· βš™πŸ”Šβ„ΉοΈ) via getCinemaPortalContainer, only while isActive. Header is ONLY the jae row (book-ikoni Β· "jakeet" Β· versesShown/referenceCount); the topic name + card N/M live in TopicFlow's single global "AIHE: <nimi> N/M" title, not repeated here) | src/components/cinema/topic/TopicVersesCinemaView.tsx | | VerseSnapshotStrip (horizontal snapshot filmstrip: ref + 2-line preview per verse, start/end Flag bookends, click-to-jump) | src/components/cinema/topic/VerseSnapshotStrip.tsx | | TopicStepHeader (shared emphasized step header: icon + heading + N/M) | src/components/cinema/topic/TopicStepHeader.tsx | | TopicVersesView (OLD grid model β€” now the list option; versePresentation toggle) | src/components/cinema/topic/TopicVersesView.tsx | | useTopicVersePresentation (localStorage cinema/list, default cinema; toggle in settings sheet) | src/hooks/topic/useTopicVersePresentation.ts | | CinemaVersePreviewPopup (Haku>Jakeet result β†’ verse-in-chapter popup, per-verse jae-cinema) | src/components/cinema/CinemaVersePreviewPopup.tsx |

Topic cinema is a 4th CinemaShell consumer (its own TopicFlow over a discriminated TopicCinemaStep[], NOT InfoFlow), reusing useSnapScrollSteps, CinemaShell, and InfoView variant="scroll" for info-block cards; background music only, no verse-TTS.

For full details β€” internal view stack (flow β†’ chapter β†’ verse) + portal/ESC-pino gotchas, CinemaVerseActions, TopicChapterReader picker gotchas, the TWO presentation models (cinema/list) + fixed bottom cluster + TopicFlow.suspended, verse-range/verse-order rules, get_chapter_bundle book normalization, TopicVerseCinema auto-advance/start-verse, the single global "AIHE: <nimi> N/M" title, the info-palanen card + jae-snapshot popup + author link, the author cinema-app, and the wrap-up layout/continuation paths: See references/topic-cinema.md.

Prayer Room (Rukoushuone)

| Component / Hook | Location | |-----------|----------| | PrayerRoomScreen | src/features/prayer-room/PrayerRoomScreen.tsx | | PrayerRoomPage (route) | src/pages/PrayerRoomPage.tsx | | PrayerRoomSetup | src/features/prayer-room/PrayerRoomSetup.tsx | | PrayerRoomContent | src/features/prayer-room/PrayerRoomContent.tsx | | PrayerRoomHeader | src/features/prayer-room/PrayerRoomHeader.tsx | | PrayerRoomBottomBar | src/features/prayer-room/PrayerRoomBottomBar.tsx | | PrayerRoomCalendarRail (left weekday rail) | src/features/prayer-room/PrayerRoomCalendarRail.tsx | | useCalendarRailModel | src/features/prayer-room/hooks/useCalendarRailModel.ts | | PrayerRoomInviteDialog | src/features/prayer-room/PrayerRoomInviteDialog.tsx | | CalendarPrayerBrowser | src/features/prayer-room/CalendarPrayerBrowser.tsx | | Prayer Room Types | src/features/prayer-room/types.ts | | useTodayPrayerRoom | src/features/prayer-room/useTodayPrayerRoom.ts | | usePrayerRooms | src/hooks/usePrayerRooms.ts | | usePrayerRoomSync (Realtime) | src/hooks/usePrayerRoomSync.ts | | usePrayerRoomInvitations | src/hooks/usePrayerRoomInvitations.ts | | usePushToTalk | src/hooks/usePushToTalk.ts | | useWebRTCAudio | src/hooks/useWebRTCAudio.ts |

Audio Pipeline

| Component | Location | |-----------|----------| | Audio Service | src/lib/audioService.ts | | ElevenLabs Voices | src/lib/elevenLabsVoices.ts | | Audio Generation | supabase/functions/generate-audio/index.ts |

Pages

| Component | Location | |-----------|----------| | Tanaan Page | src/pages/TanaanPage.tsx | | Discipleship Landing | src/pages/DiscipleshipLandingPage.tsx | | Spiritual Path Faith | src/pages/SpiritualPathFaithPage.tsx | | Daily Reading View | src/components/reading-plans/DailyReadingView.tsx |

Architecture Overview

CinemaShell (base layer ~438 lines)
β”‚  Fullscreen, background visuals (Ken Burns), background music,
β”‚  preferences, visual/music pickers, keyboard shortcuts (B/N/V/M)
β”‚  Props: isOpen, onClose, title?, hideControls?, dimControls?
β”‚  Exports: CinemaShellContext (render props for children)
β”‚
β”œβ”€β”€ CinemaReaderScreen (content layer ~1033 lines)
β”‚   β”‚  Verse fetching/mapping, Bible audio, auto-advance, audio sync,
β”‚   β”‚  completion overlay. Uses CinemaShell as wrapper via render props.
β”‚   β”‚  Modes: "chapter" | "verseList" | "summaryItems"
β”‚   β”‚
β”‚   └── useDiscipleshipOrchestration (task queue ~792 lines)
β”‚       β”‚  Task queue, completion persistence, quiz insertion,
β”‚       β”‚  transition overlays, verse bar, kooste, prayer messages
β”‚       β”‚
β”‚       └── Overlay Components
β”‚           β”œβ”€β”€ DiscipleshipTaskSelector (pick tasks)
β”‚           β”œβ”€β”€ DiscipleshipInlineTask (prayer/practice/quiz)
β”‚           β”œβ”€β”€ DiscipleshipTransitionOverlay (between tasks)
β”‚           └── DiscipleshipVerseBar (note/share on pause)
β”‚
β”œβ”€β”€ CuratedPlanCinema (program mode ~66 lines)
β”‚   β”‚  Wrapper: loads grand plan mini_tasks + progression_type via useCuratedPlanCinema.
β”‚   β”‚  CinemaShell with hideControls. Routes to flow based on progression_type:
β”‚   β”‚    progressionType === "scrolling" ? ScrollingPlanFlow : CuratedPlanFlow
β”‚   β”‚  On all-tasks-complete: calls complete_grand_plan RPC + invalidates ["grand-plans"].
β”‚   β”‚
β”‚   β”œβ”€β”€ CuratedPlanFlow (continuous / linear ~133 lines)
β”‚   β”‚   β”‚  One mini-task card at a time, AnimatePresence crossfade.
β”‚   β”‚   β”‚  Scrim bg-black/40 + card bg-black/60 backdrop-blur-md.
β”‚   β”‚   β”‚  completedIds Set; backward jumps un-complete the target.
β”‚   β”‚   β”‚  Choice routing: next | jump_to_sort_order.
β”‚   β”‚   β”‚
β”‚   β”‚   └── MiniTaskView (step UI ~223 lines)
β”‚   β”‚          SVG circular countdown timer, pause via DiscipleshipPomodoroButton,
β”‚   β”‚          JATKA hidden when choices exist (choice-to-advance).
β”‚   β”‚
β”‚   └── ScrollingPlanFlow (scrolling ~336 lines)
β”‚       Snap-scroll all steps on one page, numbered progress pills (click-to-jump),
β”‚       IntersectionObserver (threshold 0.4) tracks active step,
β”‚       per-step auto-advance timer (duration_seconds, default 30s),
β”‚       user-scroll detection pauses auto-advance 1.5s.
β”‚       Inline StepContent (NOT MiniTaskView) β€” no countdown ring, no completedIds.
β”‚       See references/curated-plans.md for full details.
β”‚
β”œβ”€β”€ InfoCinema (Q&A / topic info-palaset ~121 lines)
β”‚   β”‚  CinemaShell with hideControls; renders InfoFlow from parent-loaded
β”‚   β”‚  InfoBlocks. Mirrors CuratedPlanCinema. See references/info-cinema.md.
β”‚   β”‚
β”‚   └── InfoFlow (one-page snap-scroll, card per block ~350 lines)
β”‚          Uses useSnapScrollSteps (SHARED mechanics). Left progress rail +
β”‚          numbered pills, per-card auto-advance, prayer-room-style bottom bar.
β”‚          Optional finalSlide (CinemaQuestionWrapUp). key={contentKey} remount.
β”‚
β”œβ”€β”€ PrayerRoomScreen (prayer room consumer ~499 lines)
β”‚   β”‚  CinemaShell with hideControls; owns prayer/verse state, realtime sync,
β”‚   β”‚  PTT, WebRTC audio mesh, invitations. Route: /rukoushuone.
β”‚   β”‚  See references/prayer-room.md for full details.
β”‚   β”‚
β”‚   └── Parallel runtime systems (scoped to config.id)
β”‚       β”œβ”€β”€ usePrayerRoomSync     (presence + host state broadcast, debounced 100ms)
β”‚       β”œβ”€β”€ usePushToTalk         (single-speaker floor + FIFO hand queue)
β”‚       └── useWebRTCAudio        (STUN-only audio mesh, 2–5 users)
β”‚
└── Future: Meditation, etc.
    └── CinemaShell + custom content

Audio Pipeline:
  generate-audio Edge Function β†’ ElevenLabs API (with timestamps)
  β†’ audio_assets table (hash-cached) β†’ audio_cues table (verse timing)

Audio Split

CinemaShell and CinemaReaderScreen both use useCinemaAudio but for different purposes:

  • CinemaShell: bibleAudioUrl: null (music-only)
  • CinemaReaderScreen: backgroundMusicUrl: null (Bible audio-only, music from CinemaShell context)

Cinema OS / Command Center

Orchestration layer that wraps the individual cinemas in one persistent CinemaShell so the user moves Topic β†’ Search β†’ Question β†’ Curated as a single continuous session (music/background never reset β€” "Γ€lΓ€ poistu Cinemasta"). Route /cinema-os (entry card atop the TΓ€nÀÀn page). Read Docs/cinema/CINEMA-OS.md first.

/cinema-os β†’ CinemaOSProvider (stack reducer + history + localStorage)
             └─ CinemaHost (mounted ONCE)
                └─ CinemaPreferencesProvider (hoisted, shared once)
                   └─ <CinemaShell> (persistent: music / bg / fullscreen)
                      └─ CinemaShellContext.Provider
                         └─ ActiveCinemaApp = registry[topIntent.type].Component
                              command-center Β· search Β· topic Β· question Β· curated

Three separated concerns: Intent (serializable {type,payload}) β†’ Registry (intent.type β†’ CinemaApp, declarative, no host branches) β†’ Navigation (stack reducer + history). Apps are shell-less content that consume useCinemaShell(); they never render a CinemaShell.

Search β†’ Topic chaining: SearchCinemaApp picks a topic β†’ nav.launch({type:"topic", payload:{slug}}) β†’ OS pushes Topic Cinema; back returns to Search. QuestionCinemaApp's info-block CTA β†’ nav.launch({type:"curated", ...}). Apps special-case nothing β€” they emit intents.

Invariants (load-bearing β€” don't break these)

  • Apps are shell-less. Consume useCinemaShell() (from components/cinema/CinemaShellContext). The standalone wrappers (TopicCinema, InfoCinema, CuratedPlanCinema) are LEFT UNTOUCHED for their existing launch buttons; the OS apps (src/cinema-os/apps/*) are parallel shell-less mirrors. Don't merge them yet.
  • Intent.payload = plain serializable data only (slug/id/query) β€” never functions/objects. One rule β†’ history + localStorage + deep-link + (Phase 2) DB are the same representation. Derive auth/version/i18n INSIDE the app, not in the payload.
  • Two-level back: apps with internal layers (Topic's flowβ†’chapterβ†’verse) register useCinemaBackHandler(fn); the host's nav.back() (and ESC, shell X, fullscreen-exit) runs interceptors LIFO first, then pops the OS stack. Generalizes TopicCinema's old popOrClose. Question/Curated have NO internal layers β†’ no interceptor.
  • Only the top frame is mounted (Phase 1). nav.replace PRESERVES the frame key (in-place payload update, no remount) β€” Search uses it to persist query/tab so the topic round-trip restores them. Provider URL-sync is path-based; replace does NOT record history. Keep-alive is a Phase 2 opt-in (CinemaApp.keepAlive, default false, never Reader/PrayerRoom).
  • Only "destinations" are intents. Topic's chapter/verse stay internal layers (transient, not URL-addressable). Only top-level intents get URLs.
  • Phase 1 base is /cinema-os so the /cinema landing (CinemaLandingPage) is untouched. CinemaShell already portals to document.body (universal overlay root) β€” apps must NOT add their own portal.

Phase 2 (deferred)

DB history (cinema_sessions/cinema_events), opt-in keep-alive, browser-back↔OS-back, verses⇄topics swipe remain deferred.

Discipleship-tila is now an in-shell app (discipleship, done β€” the previously-deferred CinemaReaderScreen migration). CinemaReaderScreen is dual-mode: the embedded flag makes its outer wrapper skip its own CinemaPreferencesProvider AND its CinemaShell, and CinemaReaderScreenContent consumes useCinemaShell() instead of the render-prop ctx (the music-ref-sync just reads the same CinemaShellContext). DiscipleshipCinemaApp feeds it useDiscipleshipTasks() with no autoStart β†’ full useDiscipleshipOrchestration flow (selector β†’ reading + prayer + practice + memory quiz + Nyt KΓΆsete). ownsChrome: true (CinemaReader ControlBar + overlays replace OS pills; exit/ESC β†’ nav.back, completion β†’ nav.home); heavy engine/audio mount-bound (never keep-alive). Standalone /tanaan launch (TanaanModals) unchanged β€” it omits embedded. Same dual-mode + ownsChrome template as the prayer room.

Kooste (summary) is an in-shell app too (summary, done). SummaryCinemaApp resolves {summaryId?} (else active via useActiveSummary) β†’ fetchSummaryDetail (pages/summary/summaryDetailLoader) β†’ buildCinemaItems (pages/summary/SummaryCinemaMode, exported pure fns) β†’ dual-mode CinemaReaderScreen embedded mode="summaryItems". ownsChrome:true. Launched from the SearchCinemaApp "Muut" tab (Koosteet result) and a Command Center "Kooste" tile. SearchCinemaApp now has 4 tabs β€” Jakeet, Aiheet, Kysymykset (usePublishedQuestionSearch β†’ question intent), Muut (search_user_content RPC; only Koosteet links β†’ summary, rest read-only). Strongs is intentionally absent (no cinema target β†’ per the "no link for non-cinema" rule). The Jakeet tab is NOT an auto-carousel by default β€” it mirrors the base /search page. Data comes from useUnifiedSearch (NOT the old useVersesSearch) β†’ verses (match_rank/is_word_form) + topicalVerses. Hits are ordered literal-first then inflected (splitVersesByRank drops rank-5 fuzzy; compareByRankThenLocation within each group). Classification = isInflectedHit(v, query): single-word query β†’ inflected iff the verse text lacks the query as an exact token (Finnish FTS returns inflections as rank-1 WITHOUT is_word_form, so the rank/flag alone misses them); multi-word β†’ falls back to is_word_form || match_rank>=2. Inflected hits carry a TAIVUTUS badge. The "Rajaus taivutuksin" filter uses exact token match (NOT startsWith β€” the base form would otherwise match all its inflections and filter nothing). This orderedVerses drives the whole cinema verse search (list + preview + "Aja" carousel). Two filter dropdowns ("Rajaus kirjoittain" = books from results; "Rajaus taivutuksin" = extractWordForms, now EXPORTED from VerseFilterBar) are inline (NOT Radix Select β†’ stay visible in fullscreen). Pagination 16/page; below it the "Aiheeseen liittyvΓ€t jakeet" panel (topicalVerses deduped vs shown ids, VIA-topic tag) β€” both click β†’ CinemaVersePreviewPopup. Cards = wrapping dark boxes (flex flex-wrap); the snippet uses a cinema-local cinemaSnippet (match-LEADING, small lead) NOT the wide-page snippetAroundMatch (preContext 60) β€” in the narrow w-[300px] line-clamp-2 card the wide pre-context pushed the highlight past the 2 visible lines; leading "…" dropped at a clean word boundary. arrow keys rove the current page (column count from DOM offsetTop); click/ENTER opens the preview (found verse in chapter context, search term highlighted via the query prop + buildHighlightRegex, per-verse Film button β†’ TopicVerseCinema). "Aja jae-cinema" runs the full carousel startβ†’finish. Internal layers (list β†’ preview β†’ verseCinema, plus runAll) + useCinemaBackHandler β€” same model as ReadingCinemaApp, NO new app.

Prayer Room is now an in-shell app (prayer-room, done β€” was previously a separate-destination exclusion). PrayerRoomScreen is dual-mode: an embedded flag switches between mounting its own CinemaShell (standalone /rukoushuone) and consuming useCinemaShell() (under the host). It owns its chrome (ownsChrome: true on the registry entry β†’ CinemaOSChrome returns null for it; PrayerRoomHeader/BottomBar replace the OS pills, header X = nav.back, ESC closes an open invite/setup modal first via useCinemaBackHandler). Payload {roomId?/startCalendarId?/create?} is resolved by PrayerRoomApp (usePrayerRooms + resolveTodayPrayerRoomConfig/normalizeLoadedRoom); setup is an internal layer. Realtime/PTT/WebRTC are mount-bound (never keep-alive) β€” leaving the app leaves the room (channel removeChannel). The pattern (dual-mode

  • ownsChrome) is the template for migrating any other own-shell feature (e.g. the deferred Reader).

Cinema Mode Components

CinemaShell

Reusable base layer at src/components/cinema/CinemaShell.tsx (~438 lines):

// Provides:
// 1. Fullscreen modal shell (useCinemaFullscreen)
// 2. Animated background visuals (CinemaBackground + GSAP Ken Burns)
// 3. Background music (useCinemaAudio, music only)
// 4. User preferences (useCinemaPreferences)
// 5. Visual/music pickers + keyboard shortcuts (B/N/V/M)
// 6. Render props: CinemaShellContext (audioState, selectedTrack, preferences, toggleMusic, etc.)
// 7. dimControls prop to fade controls when overlays active

CinemaReaderScreen

Content layer at src/features/cinema/CinemaReaderScreen.tsx (~1033 lines):

// Wraps content in CinemaShell, adds:
// 1. Map BundleVerse β†’ CinemaVerse for cinema-reader package
// 2. Bible audio playback (separate from CinemaShell's music)
// 3. Handle verse navigation (index state)
// 4. Auto-advance (WPM-based or audio cue-based)
// 5. Audio sync (manual nav β†’ immediate audio-seek to that verse, no prompt)
// 6. Discipleship overlay rendering (delegates logic to useDiscipleshipOrchestration)
// 7. Compact mode for phones β€” passes compact + sheet callbacks to CinemaReader

Mobile UI (Compact Mode)

On phones (useBreakpoint().isPhone || isCozy, <768px) Cinema Mode collapses the desktop two-row control panel + top-left HUD pills into a single row + two icon-triggered bottom sheets, freeing ~90px for the verse text.

  • πŸ”Š icon β†’ CinemaMusicPopover (Raamatun versio + lukuÀÀni CinemaVersionVoiceSection, track, favorite, volume, favorites-only, change track)
  • βš™ icon β†’ CinemaSettingsSheet (background, completion mode, speed, voice volume + on/off)
  • Adaptive verse typography via CSS clamp() + vw (3 tiers; small-phone ≀380px gets 24-30px to fit Acts 1:14 on iPhone SE)
  • Sheets render inside native fullscreen via getCinemaPortalContainer() (Radix portal targeting fix)

Critical: @media (max-width: 479px) matches iPhone 14 Pro Max (430px). Use max-width: 380px for genuine "small phone" rules.

For full details (component contracts, control distribution table, font tier table, portal pattern, gotchas): See references/mobile-ui.md

useDiscipleshipOrchestration

Task queue hook at src/hooks/useDiscipleshipOrchestration.ts (~792 lines):

// Extracted from CinemaReaderScreen, manages:
// 1. Task queue state (queue, index, completedIds, overlays)
// 2. Auto-open logic (initialTask, autoStart, task selector)
// 3. Reading plan verse loading (loadReadingPlanReferences)
// 4. Kooste references loading (loadKoosteReferences)
// 5. Task completion persistence (persistTaskCompletion helper)
// 6. Quiz insertion after reading plan completion
// 7. Task routing, jumping, skipping
// 8. Mini-task choice routing
// 9. Prayer message handling

Animation Modes

Five animation modes in src/types/cinema.ts: slide, zoom, stack, loopH, loopV

Ken Burns Effect

CinemaBackground.tsx: Random transform over 25s (scale 1.0-1.15, translate Β±5%)

Audio System

ElevenLabs Integration

Voices in src/lib/elevenLabsVoices.ts: Venla (female, T5qAFgaL2uYxoUtojUzQ), Urho (male, 1WVCONUwYGulVaKg4oTr)

Audio Generation Flow

Client β†’ Edge Function β†’ check hash cache β†’ ElevenLabs API (with timestamps)
β†’ parse timestamps β†’ verse cues β†’ store MP3 β†’ save metadata + cues β†’ return

For detailed API reference, see references/elevenlabs-api.md

Audio Cue Format

See references/audio-cue-format.md for full specification.

Auto-Advance (Without Audio)

useAutoAdvance: WPM-based timing (default 150, adjustable 50-400). Min 1.5s per verse. Priority: audio cues > auto-advance timer.

Dual-Track Audio

useCinemaAudio: Bible + background music tracks with independent volume (0-1).

Database Schema

See existing tables: audio_assets, audio_cues, cinema_preferences, background_tracks, background_visuals in bible_schema.

Discipleship Cinema Mode

Immersive full-screen task flow launched from Tanaan page. Guides users through daily reading plans, prayers, practices, and memory quizzes sequentially.

For full details: See references/discipleship-cinema.md

Key Concepts

  • Launched via "Discipleship-tila" button on /tanaan β†’ CinemaReaderScreen(mode="verseList")
  • useTodayTasks normalizes practices + plans + prayers β†’ UnifiedTodayTask[]
  • useDiscipleshipTasks convenience hook composes all data sources
  • Task routing: reading_plan β†’ verse reader, prayer/practice β†’ DiscipleshipInlineTask
  • Quiz content types: verse_memory, read_memory, reading_plan_memory β†’ VerseMemoryQuiz
  • Between tasks: DiscipleshipTransitionOverlay with progress bar + free navigation
  • DiscipleshipVerseBar on pause: Add to Nyt Kooste, Note, Share, Pomodoro Break
  • Nyt Kooste: Auto-created daily verse collection, appended as final task
  • Reading Plan Memory Quiz: Auto-inserted after each reading plan day completion (discipleship mode only, NOT autoStart)
  • initialTask prop: Start specific task immediately, bypass selector
  • autoStart prop: Skip task selector, auto-queue all uncompleted tasks, auto-skip transitions
  • Break duration from app_config.discipleship_break_minutes (default 5 min)

Two Cinema Launch Modes from Tanaan

| Mode | Button | Props | Behavior | |------|--------|-------|----------| | Reading Plan Cinema | Blue Play button | discipleshipTasks={readingPlanCinemaTasks} autoStart | Auto-plays reading plans only. No task selector, no discipleship UI toggle, no memory quizzes, no transition overlays. | | Discipleship Cinema | Amber Clapperboard button | discipleshipTasks={allTasks} | Full discipleship flow: task selector β†’ reading + prayer + practice β†’ quizzes β†’ kooste. |

autoStart behavior:

  • Queues all uncompleted tasks, starts first immediately (line ~1071-1092)
  • Hides discipleship toggle/next button in CinemaReader (discipleshipMode={false}, no onDiscipleshipToggle)
  • Skips reading plan memory quizzes (!props.autoStart guard on quiz insertion)
  • Auto-skips transition overlays between tasks (useEffect at line ~1285-1290)
  • When all tasks done: calls onAllTasksCompleted and closes

Reading Plan Completion

handleComplete calls mark_reading_day_complete RPC when a reading plan finishes. Key details:

  • Completion-type plans: RPC advances current_day immediately (e.g. 5β†’6)
  • Dashboard fix: get_today_dashboard checks completed_at::date = CURRENT_DATE for completion-type plans (not current_day which already advanced)
  • Client-side fix: useTodayTasks checks completed_days.includes(current_day - 1) for completion-type plans as fallback
  • Query invalidation: Must invalidate ["today-dashboard"], ["reading-plan-streaks"], AND ["user-reading-plans"]

Reading Plan Memory Quiz

useReadingPlanQuiz generates a 3-verse quiz after reading plan completion (2 plan verses + 1 decoy). Selection priority: user-marked refs (marked_refs in quiz task metadata, from nytKoosteRefsRef) β†’ preferred books (NT, Psalms, Proverbs) β†’ any plan verse. Full algorithm: references/discipleship-cinema.md.

Tanaan Page & DiscipleshipLandingPage

/tanaan sections (TodayTaskList, TomorrowTasksSection, PermanentTasksSection, RecurringTasksSection, PracticeActivationCard) and the /opetuslapseus marketing landing page are documented in references/discipleship-cinema.md. Locations: Quick Reference β†’ Discipleship Layer / Pages.

Curated Plans (Grand Plans) & Progression Modes

Curated grand plans are a second CinemaShell consumer. The wrapper CuratedPlanCinema routes to one of two flow components based on the plan's progression_type, then renders inside CinemaShell hideControls.

For full details: See references/curated-plans.md

Progression Types

type GrandPlanProgressionType = 'continuous' | 'linear' | 'scrolling';

| Value | Flow Component | UX | |---|---|---| | continuous (default) | CuratedPlanFlow | One mini-task card at a time + countdown timer | | linear | CuratedPlanFlow | Same code path as continuous β€” semantic distinction only | | scrolling | ScrollingPlanFlow | All steps on one snap-scrollable page with auto-advance |

Flow router (CuratedPlanCinema.tsx line ~55):

const FlowComponent = progressionType === "scrolling" ? ScrollingPlanFlow : CuratedPlanFlow;

Both flows share the same props contract: { tasks, onAllTasksCompleted, onClose }.

CuratedPlanFlow (continuous / linear)

  • One mini-task card at a time via AnimatePresence mode="wait" crossfade
  • Scrim bg-black/40 + card bg-black/60 backdrop-blur-md max-w-md
  • Uses <MiniTaskView> (SVG circular countdown timer, pause via DiscipleshipPomodoroButton)
  • completedIds: Set<string> tracks done tasks; advance skips already-done
  • Backward jump via choice β†’ un-completes the target (allows replay)
  • X button = skip WITHOUT marking complete
  • Choice routing: {type:'next'} | {type:'jump_to_sort_order', sort_order} β€” sort_order matched against task.metadata.grand_plan_sort_order

ScrollingPlanFlow (scrolling)

  • All steps rendered on a single snap-scrollable page (snap-y snap-mandatory)
  • Numbered progress pills at top (click-to-jump), amber = active, emerald = past, faint = future
  • IntersectionObserver (threshold: [0, 0.25, 0.5, 0.75, 1], root = container) tracks active step; gates state update at ratio > 0.4 to prevent flicker
  • Per-step auto-advance: duration_seconds (default 30s) β†’ scrollToStep(i+1) unless user is scrolling
  • User-scroll detection: any scroll event flips userScrollingRef = true with 1500ms debounce β€” pauses auto-advance
  • Scrim bg-black/50 (stronger than CuratedPlanFlow)
  • Uses its own inline StepContent (NOT MiniTaskView) β€” no countdown ring, no completedIds
  • JATKA (non-last) = scroll, LOPETA (last) = finish + close

MiniTaskContentConfig (per-step data)

interface MiniTaskContentConfig {
  title: string;
  body: string;
  icon?: string;                // lucide icon via getPracticeIcon
  duration_seconds: number;     // timer (CuratedPlanFlow) OR auto-scroll delay (ScrollingPlanFlow)
  choices?: MiniTaskChoice[];   // if present β†’ hides JATKA in MiniTaskView (choice-to-advance)
  verse_ref?: string;           // resolved via fetchVerseTexts
  closing_text?: string;
  image_url?: string;
  show_break_button?: boolean;  // default true
}

Plan Completion

  • Last task / LOPETA β†’ flow calls onAllTasksCompleted() then onClose() synchronously
  • CuratedPlanCinema.handleAllTasksCompleted:
    1. Sets completedRef.current = true BEFORE the async RPC (close fires synchronously after)
    2. Calls supabase.rpc('complete_grand_plan', { p_grand_plan_id: planId })
    3. Invalidates ["grand-plans"]
  • handleClose β†’ onClose(), then if completedRef && completionRedirectUrl β†’ navigate(url) after 300ms

Critical Gotchas

  • continuous and linear are identical in code. Only "scrolling" branches β€” the other two both fall through to CuratedPlanFlow. Branch INSIDE the flow if you need them to differ, don't split the router.
  • ScrollingPlanFlow reimplements step UI. Inline StepContent is a simpler cousin of MiniTaskView (no timer, no pause). Behavioral changes to MiniTaskView do NOT propagate β€” fix both or refactor to share.
  • Snap-scroll mechanics are now extracted to useSnapScrollSteps, but ScrollingPlanFlow has NOT adopted it. src/hooks/useSnapScrollSteps.ts (active-index tracking, jump, user-scroll detection, per-step auto-advance) was extracted from ScrollingPlanFlow's proven behaviour and is used by InfoFlow. ScrollingPlanFlow still carries its own duplicate inline IntersectionObserver + timer. A snap-scroll fix in one does not reach the other β€” prefer migrating ScrollingPlanFlow onto the hook over editing both. Tuning constants (0.4 threshold, 1500ms user-scroll debounce) are deliberately matched; don't "clean up" either copy.
  • Completion order is load-bearing. completedRef.current = true must run BEFORE the await complete_grand_plan RPC β€” flow calls onAllTasksCompleted then onClose synchronously, and handleClose reads the ref immediately.
  • Two RPCs on mount. useCuratedPlanCinema fires get_grand_plan_items AND get_user_grand_plans (the latter only for progression_type). Consider folding progression_type into items if you refactor.
  • Default progression falls back to continuous. When the user isn't a member yet, get_user_grand_plans has no match β†’ silent continuous default. Worth checking first when a plan "won't scroll".
  • ScrollingPlanFlow doesn't invoke complete_grand_plan. That's CuratedPlanCinema's job. A new flow must call onAllTasksCompleted() at the end, or the plan never marks complete.
  • Intersection threshold 0.4 and user-scroll debounce 1500ms are tuned. Lowering the threshold flickers active-index; shortening the debounce fires auto-advance mid-flick. Don't "clean up" these constants.

Info Cinema (Q&A / Topic Info-palaset)

Third CinemaShell consumer. Renders a question's/topic's published Info-palaset as a one-page vertical snap-scroll β€” one card at a time, top β†’ bottom β€” the same UX as the curated ScrollingPlanFlow but for read-only answer content. This is the "yksi sivu jota mennÀÀn kortti kerrallaan alaspΓ€in" flow.

For full details: See references/info-cinema.md

Key Concepts

  • QuestionAnswersCinema (the Film CTA, 3 variants) β†’ InfoCinema (wrapper: CinemaShell hideControls + music/settings sheets, mirrors CuratedPlanCinema) β†’ InfoFlow (the snap-scroll) β†’ InfoView variant="scroll" (one block card).
  • Mechanics come from the shared useSnapScrollSteps hook β€” InfoFlow does NOT reimplement the IntersectionObserver. Per-card auto-advance from each block's duration_seconds (0 = none); user-scroll pauses it; last card never auto-advances.
  • finalSlide render-prop: optional extra snap-step after the blocks (index blocks.length), e.g. CinemaQuestionWrapUp (feedback bar + related questions). Gets { closeCinema, isActive }. Left-rail marker is a HelpCircle (not a number), auto-advance disabled. onFinalSlideEnter = its Enter-key default action (e.g. "NΓ€ytΓ€ seuraava kysymys"); Enter is reserved for this β€” never add Enter-to-advance on content cards.
  • contentKey remount pattern: InfoCinema passes contentKey (the questionId) to <InfoFlow key={contentKey}> β†’ CinemaShell stays mounted (bg/music continue) while InfoFlow remounts (scroll resets) β†’ seamless "next question" transition without leaving cinema. Same family as reading-plan key={cinema-day-N}.
  • InfoView is dual-variant: variant="scroll" (cinema) vs the page InfoTextCard. Same block, two renderers β€” edit the one the task asks for (see questions_answers manifest domain).
  • Renders nothing with 0 published+non-hidden blocks (CTA hidden, InfoCinema returns null). A "Cinema button missing" report usually means the publish gate, not cinema code.

Prayer Room (Rukoushuone)

Realtime multiplayer full-screen prayer experience built on CinemaShell (not CinemaReader). Canonical example of a CinemaShell-only consumer: reuses fullscreen + Ken Burns + background music + preferences, adds its own content, host-driven realtime sync, push-to-talk, and WebRTC audio mesh on top.

For full details: See references/prayer-room.md

Key Concepts

  • Route: /rukoushuone β†’ PrayerRoomPage β†’ PrayerRoomScreen(initialConfig?, onClose). Passing initialConfig opens straight into "room" view (skips setup) β€” this is how every "launch" entry point works.
  • Entry helpers: useTodayPrayerRoom opens stored room β†’ fallback most recent β†’ fresh "PΓ€ivΓ€n rukoushuone"
  • Prayer-calendar Cinema launch (CalendarDetailView, /rukouskalenterit?cal=<id>): a Film "Cinema" button navigates navigate("/rukoushuone", { state: { config } }) with the full today-room sources on (useMyCalendar/useSubscribedCalendars: true) plus calendarId (guarantees this calendar's prayers are present even if unsubscribed + enables per-day nav) and the transient startCalendarId field. PrayerRoomScreen runs a one-shot effect that jumps currentPrayerIndex to the first prayer whose calendarId === startCalendarId once the aggregated list has loaded it β€” so the room opens on that calendar but the rest stays arrow-navigable. startCalendarId is UI-only β€” NOT persisted (the prayer_rooms mutations whitelist columns), so saved/reopened rooms never re-trigger the jump.
  • Two views: "setup" (form) and "room" (CinemaShell hideControls + render props)
  • isHost = !config.id || !user ? true : config.hostUserId === user.id β€” solo sessions and anonymous users are always host
  • Content sources (merged + deduped by baseId = id.replace(/^(cal-|mine-|sub-)/, "")):
    • Ad-hoc config.prayers (uuid ids)
    • useMyCalendar β†’ user's own + followed active prayers (mine-<id>)
    • useSubscribedCalendars minus mutedCalendarIds (sub-<id>)
    • config.calendarId extra browsing calendar (cal-<id>)
  • Legacy rooms normalize useMyCalendar/useSubscribedCalendars to true; new rooms default toggles to false in setup β€” asymmetric by design
  • Host keyboard: ArrowLeft/Right (prayer nav), ArrowUp/Down (calendar day, only if config.calendarId)
  • Left weekday rail (PrayerRoomCalendarRail + useCalendarRailModel): for prayer-calendar cards (currentPrayer.source === "calendar") a vertical left rail shows the calendar's own scheduled weekdays (Mon–Sun, via getScheduledWeekdays), with the currently-browsed day as the single active marker β€” green, not amber, and no past/future colouring (unlike InfoFlow/ScrollingPlanFlow). Driven by calendarDayOffsets[currentPrayer.calendarId] (works for subscribed calendars too, not just config.calendarId). Moves with ArrowUp/Down + header chevrons; host can click a marker to jump (offset delta β†’ shiftCurrentCalendarDay + setCurrentPrayerIndex(0)). useCalendarRailModel is a pure useMemo placed after the realtime hooks (no hook-order risk).
  • Verses: OSIS is canonical removal key; adds and removes auto-persist when config.id exists

Realtime Multiplayer

Channel: prayer-room:<roomId> via Supabase Realtime.

  • Presence (keyed by user.id): displayName, avatarUrl fetched from profiles before tracking
  • Broadcast "sync": Host sends { currentPrayerIndex, currentVerseIndex, calendarDayOffset } debounced 100ms; participants apply in useEffect gated on !isHost && syncState
  • Push-to-talk (PTT): Single speaker at a time; FIFO handQueue; host grants via avatar click or autoGrantNext on release/leave; events: raise_hand, grant_talk, release_talk
  • WebRTC audio mesh: STUN-only (stun:stun.l.google.com:19302), 2–5 users, one RTCPeerConnection + <audio> per peer, signaling over the same channel; micOpen = activeSpeakerId === userId && pttPressed toggles track enable
  • Channel ownership: usePrayerRoomSync owns subscribe/unsubscribe. usePushToTalk and useWebRTCAudio attach listeners but do NOT unsubscribe.

Invitations

Table public.prayer_room_invitations with invitee_id OR invitee_email, status pending|accepted|declined. Invite flow auto-persists the room first via createRoom.mutateAsync when opening the invite dialog from an unsaved config.

Critical Gotchas

  • Modals inline, not portaled. Setup, invite, verse-full popup, and remove-verse confirmation render as absolute inset-0 z-[10003] children of the CinemaShell subtree. Do NOT use shadcn AlertDialog or Dialog β€” both auto-portal via Radix to document.body and become invisible inside the fullscreen element. asDialog / inline props on child dialogs exist for this. See references/learnings.md β†’ "Radix Dialog/AlertDialog Auto-Portals" for the full pattern.
  • Z-index stack: CinemaShell base < Header/BottomBar z-[10002] < inline modals z-[10003]. Don't invent new values.
  • Title pill is rendered by PrayerRoomHeader, not CinemaShell. Pass NO title prop to <CinemaShell> from PrayerRoomScreen β€” the header renders the pill on its own row 1 alongside the prayer-count nav. Adding the title back to CinemaShell yields a stacked duplicate.
  • calendarName already includes "Rukouskalenteri" prefix. It comes from the DB name column (e.g. "Rukouskalenteri Suomi"). Don't prepend prayerRoom.header.calendarPrefix again β€” that produced the duplicate "Rukouskalenteri Rukouskalenteri Suomi" bug. The Calendar icon next to the name carries the semantics.
  • Prayer typography matches .cinema-verse-text. PrayerRoomContent's prayer body uses clamp(36px, 5vw, 56px) + min(1100px, 88vw) lane β€” exactly the same as cinema-reader's verse text (packages/cinema-reader/src/styles/cinema.css). The middle container must NOT have max-w-2xl mx-auto or any narrower cap that would clip this lane.
  • src/hooks/usePrayerRoom.ts is unused. PrayerRoomScreen manages state inline; do not "refactor to use the hook" without migrating sync/PTT/WebRTC plumbing too.
  • prayer_rooms / prayer_room_invitations not in types.ts β€” hooks still use (supabase as any). When types regenerate, drop the casts (see /supabase-typing-architect).
  • STUN-only mesh fails on symmetric NAT / strict firewalls. Add TURN + SFU if scaling beyond 5 users.

Common Tasks

Add New Voice

  1. Get voice ID from ElevenLabs β†’ add to elevenLabsVoices.ts β†’ add to admin UI β†’ use elevenlabs:{voiceId} format

Debug Audio Sync

Add interval logging in useCinemaAudio to trace findCurrentCue() output.

References

  • references/audio-cue-format.md - Detailed cue timing specification
  • references/elevenlabs-api.md - ElevenLabs API reference
  • references/discipleship-cinema.md - Full discipleship cinema mode documentation (components, hooks, flows, gotchas)
  • references/reading-plan-transition.md - State flow for "Next Day" in reading plan cinema mode
  • references/topic-cinema.md - Topic Cinema (aihe-cinema) full detail: internal view stack (flow β†’ chapter β†’ verse), portal/ESC-pino gotchas, CinemaVerseActions, TopicChapterReader picker gotchas, two presentation models + fixed bottom cluster + suspended, verse-range/verse-order, get_chapter_bundle normalization, TopicVerseCinema auto-advance/start-verse, single global "AIHE: nimi N/M" title, info-palanen card + jae-snapshot popup + author link, author cinema-app, wrap-up layout/continuation paths
  • references/info-cinema.md - Info Cinema (Q&A / topic info-palaset): one-page snap-scroll card flow, shared useSnapScrollSteps, finalSlide wrap-up, contentKey remount, dual-variant InfoView
  • references/prayer-room.md - Prayer Room (Rukoushuone): CinemaShell consumer with realtime sync, PTT, WebRTC audio mesh, invitations
  • references/curated-plans.md - Curated grand plans: progression modes (continuous / linear / scrolling), flow routing, completion RPC, MiniTask data model
  • references/mobile-ui.md - Cinema Mode mobile UI: compact controls (πŸ”Š + βš™ sheets), adaptive verse typography (clamp+vw, ≀380px tier), text-weight (Lihava/Kevyt) toggle, native fullscreen portal pattern, gotchas
  • references/topic-verse-bottom-cluster.md - Aihe-jae-cinema bottom cluster (filmstrip + segmented progress + consolidated pill bar): component contracts, portal/single-bar mechanics, generic-vs-aihe-specific porting analysis for adopting into jae-cinema
  • references/learnings.md - Bug patterns and fixes
  • Docs/cinema/CINEMA-OS.md - Cinema OS / Command Center: persistent shell + Intent/Registry/Navigation, two-level back, deep links, history, KeepAlive/Frame State, Search Cinema, phasing (NOT under references/ β€” it's a shared system doc)

Cross-cutting learnings: See .claude/LEARNINGS.md β†’ "CSS/Layout" section for framer-motion patterns and animation gotchas.