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>
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 intheme.csswere 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 oftheme.css.
Setup
-
Copy files
styles/theme.css→ your global stylesheet, imported once in your app root (e.g.main.tsxorapp/layout.tsx), after@tailwind base/components/utilities.lib/cx.ts,components/*.tsx→ into yoursrc/at matching paths, or adjust the relative imports.public/fonts/*.woff2→ your project'spublic/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.
-
Tailwind config — enable class-based dark mode so the
.darkclass intheme.cssworks:// 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 + inlinestylefor 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. -
Toggle dark mode by adding/removing the
darkclass on<html>(or any ancestor — seeComponentShowcase.tsxfor a self-contained example that wraps in adivinstead, 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.6–10.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)
PlateChartgap 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.
BottomNavactive-icon gradient usesbackground-clip: text, which needs-webkit-background-clip: texttoo in some older WebKit builds; not added here since Tailwind'sbg-clip-textutility already handles the prefix.