Frontend Design
Build polished, native-feeling interfaces for Raamattu Nyt using shadcn/ui, Tailwind CSS, and React.
Tech Stack
- Components: shadcn/ui (Radix primitives)
- Styling: Tailwind CSS 3.4 with CSS variables
- Icons: Lucide React
- State: React Query + React Hook Form + Zod
Responsive Design — Non-Negotiable
Every component must work on mobile first. The app is used primarily on phones in portrait orientation. Desktop is a md: upgrade, not the baseline.
Rules:
- Author classes mobile-first; add
md:/lg:only to widen, never to fix mobile - Test at 375px (iPhone SE / 12-13 mini) AND 430px (iPhone 14 Pro Max) widths mentally before declaring done — different sizes need different rules
- No fixed pixel widths on layout containers; use
w-full,max-w-*,flex-1 - Hover-only affordances are forbidden — every action must have a touch path
- Anything fixed at viewport edges must respect safe areas (
env(safe-area-inset-*)) - Text scales down only via
text-sm md:text-basepatterns, never below 14px on mobile body copy - Long labels must
truncateor wrap; never let header titles push off-screen - For oversized text (cinema verses, prayer headlines): use CSS
clamp(min, vw, max)so the screen-size detection happens in CSS, not JS. Addoverflow-wrap: break-word; hyphens: noneso long Finnish names wrap without tavutusviivoja. See references/mobile.md → Adaptive Typography - Avoid
@media (max-width: 479px)for "small phone" rules — it matches iPhone 14 Pro Max (430px). For genuine small-phone tier (iPhone SE / 12-13 mini at 375px), use@media (max-width: 380px)
Verified mobile view types
The skill must produce these view archetypes correctly. See references/mobile-views.md for full compositions:
- Reader view (light theme): top header with breadcrumb + action icons, centered chapter title, inline superscript verse numbers, highlight-able passages, inline images, bottom tab bar
- Cinema/immersive layout (dark, background image + scrim): participant avatar row with active-speaker indicator, step pill nav (1·2·3·4) with progress badge, centered large prayer/verse text, side floating prev/next arrows, circular icon bottom controls
- Participants grid + bottom-sheet menu (dark): 2-col avatar tile grid with states (active-speaker, selected, muted), header with counter, bottom sheet stacked over scrim with toggle row + destructive action
- Room/landing card view (dark): step pill nav, uppercase section label + large heading, avatar row with invite-tile, large verse card with reference pill, mid-screen action bar (icon — primary CTA — icon), bottom tab bar
If asked to build any of these, read references/mobile-views.md for the exact composition. Do not freelance.
Quick Patterns
Responsive Layout
// Mobile-first with desktop override
<div className="flex flex-col md:flex-row gap-4">
<div className="w-full md:w-1/3">Sidebar</div>
<div className="flex-1">Content</div>
</div>
Mobile Bottom Navigation
<nav className="fixed bottom-0 left-0 right-0 z-50 bg-background/95 backdrop-blur-md border-t md:hidden safe-area-bottom">
<div className="flex items-center justify-around h-16">
{/* 44px+ touch targets */}
</div>
</nav>
Sheet for Mobile, Dialog for Desktop
const isMobile = useIsMobile();
return isMobile ? (
<Sheet>
<SheetContent side="bottom" className="h-[85vh]">
{content}
</SheetContent>
</Sheet>
) : (
<Dialog>
<DialogContent className="max-w-lg">
{content}
</DialogContent>
</Dialog>
);
Modals inside Fullscreen Elements (Cinema Mode, Prayer Room, etc.)
shadcn Dialog, AlertDialog, Sheet, Popover and DropdownMenu use Radix Portal → render to document.body. The browser Fullscreen API only shows DOM inside the fullscreen element, so portaled content disappears. Symptom: button does nothing, no visible dialog, no errors in console.
Two valid fixes — pick by case:
A. Portal-container (preferred for shadcn Sheet / Popover / Dialog): keep using shadcn but redirect the portal target to the fullscreen element.
// apps/raamattu-nyt/src/components/cinema/cinemaPortalContainer.ts
export function getCinemaPortalContainer(): HTMLElement | undefined {
return (document.fullscreenElement as HTMLElement | null)
?? ((document as any).webkitFullscreenElement as HTMLElement | null)
?? undefined; // outside fullscreen → Radix falls back to body
}
// In the sheet:
<Sheet open={open} onOpenChange={...}>
<SheetContent
side="bottom"
container={getCinemaPortalContainer()}
className="z-[10002] bg-black/95"
overlayClassName="z-[10002]"
>
...
</SheetContent>
</Sheet>
B. Inline overlay (use for confirmations / one-off prompts when shadcn is overkill):
{showConfirm && (
<div className="absolute inset-0 z-[10003] bg-black/70 backdrop-blur-sm flex items-center justify-center p-6">
<div className="bg-background rounded-2xl p-6 max-w-md w-full">
<h2>{title}</h2>
<p>{description}</p>
<div className="flex gap-2 justify-end mt-4">
<Button variant="outline" onClick={onCancel}>{t("cancel")}</Button>
<Button variant="destructive" onClick={onConfirm}>{t("confirm")}</Button>
</div>
</div>
</div>
)}
z-index ladder inside fullscreen: header 10001, content overlays 10002, modals 10003. See cinema-voice-architect skill for details.
Cinema OS — defer to the architect, don't freelance
Cinema OS (the orchestration layer that runs every cinema inside one
persistent CinemaShell — Topic / Search / Question / Curated / Discipleship /
Prayer Room / Summary as a single session) is owned by the
cinema-voice-architect skill, not this one. Any UI work that lands inside
src/cinema-os/**, src/features/cinema/**, or a CinemaShell consumer is
cinema work: invoke cinema-voice-architect and read Docs/cinema/CINEMA-OS.md
first. This skill builds the pixels; the architect owns the contracts.
Load-bearing UI invariants you must NOT break when styling/structuring inside Cinema OS:
- Apps are shell-less. A Cinema OS app (
src/cinema-os/apps/*.tsx) consumesuseCinemaShell()— it must never render its ownCinemaShell,CinemaPreferencesProvider, or fullscreen wrapper. Two shells = double music + double fullscreen + auto-close race. - Never add your own portal.
CinemaShellalready portals todocument.body. For modals inside fullscreen, redirect Radix togetCinemaPortalContainer()(pattern above) — do notcreatePortal(document.body)from an app. - Respect the z-index ladder (header
10001/ overlays10002/ modals10003) andownsChromeapps (PrayerRoom/Discipleship/Summary) that replace OS pills with their own chrome — don't double up nav. - Two-level back / ESC is wired by the host. Internal layers register
useCinemaBackHandler; don't add competing ESC/back listeners.
If a request is purely "make this cinema screen look/feel better" (spacing, typography,
touch targets, animation polish) you can do it here — but check the invariants above and
hand any structural/navigation/audio change to cinema-voice-architect.
Touch-Optimized Button
<Button
className="h-12 px-6 active:scale-95 transition-transform"
variant="default"
>
Touch Me
</Button>
Mobile-Native Feel Checklist
- [ ] Bottom nav visible only on mobile (
md:hidden) - [ ] Touch targets min 44px (h-11/h-12)
- [ ]
active:scale-95on tappable elements - [ ] Safe area padding (
safe-area-bottomclass) - [ ] Backdrop blur on overlays (
bg-background/95 backdrop-blur-md) - [ ] Swipe gestures where appropriate
- [ ] No hover-only interactions on mobile
Design Tokens
/* Use semantic colors */
--background, --foreground
--primary, --primary-foreground
--muted, --muted-foreground
--destructive
--border, --ring
/* Border radius */
--radius: 0.5rem (8px)
When Building UI
- Read image inputs - Analyze screenshots/mockups for layout and style
- Mobile-first - Start with mobile, add
md:breakpoint for desktop - Match an archetype - If the request matches a Verified mobile view type, read
references/mobile-views.mdfirst and reuse its composition - Use shadcn - Import from
@ui/not raw Radix - Native feel - Add touch feedback, proper spacing, animations
- Accessibility - ARIA labels, focus states, keyboard nav
- Verify responsive - Mentally check at 375px width: nothing overflows, all touch targets ≥44px, no hover-only actions
References
- Mobile view archetypes: See references/mobile-views.md — full compositions for reader, cinema, participants, room views
- Component patterns: See references/components.md
- Mobile patterns: See references/mobile.md — primitives (bottom nav, sheets, gestures)
- Forms & validation: See references/forms.md