Files
pocket-pascal/handover/component-library
Steffi Müller a482bf5b09 Add Phase 1 Pocket Pascal UI: home, pantry, week plan, and plate.
Replace the hello-world shell with the elevated design system, bilingual screens, recipe/extras data, and the interactive Mein Teller view.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-04 12:22:32 +02:00
..

Pocket Pascal — Elevated Surface Component Library

Drop this folder's contents into your Cursor React + TypeScript + Tailwind project. It's framework-agnostic (no Next.js-specific imports), works in Vite or Next.

Pivoted from glassmorphism to elevation (2026-08-03). The first pass used real see-through glass (12% white fill, 16px blur) — over a plain white/dark background it barely reads as anything, which is exactly the feedback that came back: "I don't see the glass effect." Rather than fight a page background that has nothing distinct behind it to refract, the system now uses low-transparency solid surfaces (~90% opaque) with visible elevation shadows, and buttons that visibly press inward on tap (inset shadow + 1px downward shift + slight darkening) instead of just scaling down. The --glass-* variable names in theme.css were kept to minimize the diff across components, but they now mean "surface fill / surface blur," not transparent glass — see the comment block at the top of theme.css.

Setup

  1. Copy files

    • styles/theme.css → your global stylesheet, imported once in your app root (e.g. main.tsx or app/layout.tsx), after @tailwind base/components/utilities.
    • lib/cx.ts, components/*.tsx → into your src/ at matching paths, or adjust the relative imports.
    • public/fonts/*.woff2 → your project's public/fonts/ (already the correct static latin-subset files, no need to re-fetch from Google).
    • ComponentShowcase.tsx → mount on a scratch route to sanity-check everything renders before wiring into real screens.
  2. Tailwind config — enable class-based dark mode so the .dark class in theme.css works:

    // tailwind.config.ts
    export default {
      darkMode: "class",
      content: ["./src/**/*.{ts,tsx}"],
      theme: { extend: {} },
    };
    

    No plugin needed — backdrop-filter, active:, and arbitrary values (bg-[image:var(--x)]) are all core Tailwind v3+. This library leans on CSS variables + inline style for the gradient/blur values rather than baking them into the Tailwind theme, so it works the same whether or not you extend the config further.

  3. Toggle dark mode by adding/removing the dark class on <html> (or any ancestor — see ComponentShowcase.tsx for a self-contained example that wraps in a div instead, for demo purposes only; in a real app put it on <html>).

What's in here

File Purpose
styles/theme.css All design tokens: surface fill/blur/bevel-border, three-state shadows (resting/elevated/pressed), brand colors, surface + action gradients, @font-face, the .glass-border masked-bevel utility, PWA polish resets.
components/GlassCard.tsx Primary container. variant="default" | "interactive". Real elevation shadow, settles down 1px on tap.
components/BottomNav.tsx Floating pill nav, safe-area aware, gradient-on-active-icon, items press inward on tap.
components/Button.tsx variant="primary" | "secondary" | "fab", plus loading/disabled states. Press state = inset shadow + 1px shift + darken, not just scale.
components/PlateChart.tsx SVG donut styled as a ceramic plate (rim + recessed well + gapped gradient slices + center label).
ComponentShowcase.tsx Demo page rendering all of the above over the ambient page gradient, with a light/dark toggle.

Deliberate deviations from the original brief — and why

Text color on gradient buttons/badges is --ink (near-black), not white. The brand's action gradients (lime → teal, coral → amber) are light/high-luminance by design — they're the same tokens already WCAG-audited for the rest of the Pocket Pascal system (see /docs/CONCEPT.html, decision log). White text on them fails AA contrast outright; a text-shadow doesn't fix a 2:1 ratio. Dark --ink text on these gradients measures 5.610.6:1 in both themes. If you introduce a new, genuinely dark gradient (e.g. for a destructive/caution CTA), white text + text-shadow: 0 1px 2px rgba(0,0,0,0.25) is fine there — just check the ratio first.

--gradient-caution was added (coral → deep red) for destructive actions, since the brief's two named gradients (primary, accent) didn't cover that case and the brand already has a --color-caution token.

Fonts are loaded via local @font-face + static .woff2 files, not next/font or a Google Fonts <link>. Keeps theme.css portable across Vite/Next, and avoids a runtime dependency on Google's CDN. The five files in public/fonts/ are the exact latin-subset statics used everywhere else in this project.

.glass-border uses a masked pseudo-gradient, not border-image. border-image ignores border-radius — it would square off every rounded corner. The mask-composite technique in theme.css respects border-radius: inherit, so it works on the 3xl card, the pill nav, and the 2xl buttons without extra per-component CSS.

Pressed state is inset-shadow + 1px shift + darken, applied via active: Tailwind classes referencing CSS vars (active:shadow-[var(--shadow-pressed)]), not JS state or active:scale. Scale alone reads as "shrinking," not "pushed in" — the eye needs the shadow to flip from outside (elevated) to inside (pressed) to sell the depth illusion. Button and the BottomNav items use this; GlassCard's interactive variant deliberately uses a milder version (settle 1px + soften shadow, no inset) since a card is a container you tap into, not a switch you press.

Known follow-ups (not done here)

  • PlateChart gap is computed from a physical pixel gap → angular gap at a fixed average radius; if you resize the chart a lot at runtime, re-check that 2px still reads as a visible gap at very small sizes (below ~120px it may visually disappear).
  • No unit/visual regression tests included — this is a first-pass scaffold for Cursor, not a hardened library.
  • BottomNav active-icon gradient uses background-clip: text, which needs -webkit-background-clip: text too in some older WebKit builds; not added here since Tailwind's bg-clip-text utility already handles the prefix.