diff --git a/.claude/skills/apple-design/SKILL.md b/.claude/skills/apple-design/SKILL.md index 66f5680..ee910bf 100644 --- a/.claude/skills/apple-design/SKILL.md +++ b/.claude/skills/apple-design/SKILL.md @@ -5,7 +5,7 @@ description: Apple's approach to interface design and fluid, physical motion, tr # Apple Design -How Apple builds interfaces that stop feeling like a computer and start feeling like an extension of you. This knowledge comes from Apple's WWDC design talks — chiefly *Designing Fluid Interfaces* (WWDC 2018) — distilled and translated into the web platform (CSS, Pointer Events, `requestAnimationFrame`, spring libraries like Motion/Framer Motion). +How Apple builds interfaces that stop feeling like a computer and start feeling like an extension of you. This knowledge comes from Apple's WWDC design talks — chiefly _Designing Fluid Interfaces_ (WWDC 2018) — distilled and translated into the web platform (CSS, Pointer Events, `requestAnimationFrame`, spring libraries like Motion/Framer Motion). The through-line: **an interface feels alive when motion starts from the current on-screen value, inherits the user's velocity, projects momentum forward, and can be grabbed and reversed at any instant.** Springs are the tool that makes all of this natural, because they are inherently interruptible and velocity-aware. @@ -23,7 +23,7 @@ The moment lag appears, the feeling of directness "falls off a cliff." Response - **Respond on pointer-down, not on release.** Highlight a button the instant it's pressed. Waiting for `click`/touch-up to show feedback feels dead. - **Be vigilant about every latency.** Audit debounces, artificial timers, transition waits, and the ~300ms tap delay. Anything on the input path that isn't essential is a regression. -- **Feedback must be continuous *during* the interaction, not just at the end.** For a drag, slider, or drawer, update the UI 1:1 with the pointer the whole way through — never animate only when the gesture completes. +- **Feedback must be continuous _during_ the interaction, not just at the end.** For a drag, slider, or drawer, update the UI 1:1 with the pointer the whole way through — never animate only when the gesture completes. ```css /* Feedback lives on the press, and it's instant */ @@ -37,13 +37,13 @@ The moment lag appears, the feeling of directness "falls off a cliff." Response > "Touch and content should move together." -When the user drags something, it must stay glued to the finger — and respect the offset from *where they grabbed it*. Snapping to the element's center on grab breaks the illusion immediately. +When the user drags something, it must stay glued to the finger — and respect the offset from _where they grabbed it_. Snapping to the element's center on grab breaks the illusion immediately. - Use Pointer Events with `setPointerCapture` so tracking continues even when the pointer leaves the element's bounds. - Track a short **velocity/position history** (last few `pointermove` events), not just the current point — you'll need velocity at release. ```js -el.addEventListener('pointerdown', (e) => { +el.addEventListener("pointerdown", (e) => { el.setPointerCapture(e.pointerId); const grabOffset = e.clientY - el.getBoundingClientRect().top; // respect where they grabbed // ...track position + timestamp history for velocity @@ -57,9 +57,9 @@ el.addEventListener('pointerdown', (e) => { Every animation must be interruptible and redirectable at any moment. A user must be able to grab a moving element mid-flight and reverse it without waiting for the animation to finish. A closing modal the user grabs again should follow the finger — not finish closing first, then reopen. - **Never lock out input during a transition.** -- **Always animate from the *presentation* (current) value, never the target value.** On interrupt, read the element's live on-screen transform and start the new animation from there. Starting from the logical/target value causes a visible jump. +- **Always animate from the _presentation_ (current) value, never the target value.** On interrupt, read the element's live on-screen transform and start the new animation from there. Starting from the logical/target value causes a visible jump. - **Avoid CSS transitions and `@keyframes` for anything gesture-driven** — they can't be smoothly grabbed and reversed mid-flight. Springs animate from the current value by default, which is exactly what interruption needs. -- **When a gesture reverses, blend velocity — don't hard-cut it.** Replacing one animation with another at a reversal creates a velocity discontinuity, a "brick wall." Spring libraries that carry velocity through a re-target avoid it. (This is what iOS's *additive animations* do natively; on the web, choose a spring library that re-targets from the current velocity.) +- **When a gesture reverses, blend velocity — don't hard-cut it.** Replacing one animation with another at a reversal creates a velocity discontinuity, a "brick wall." Spring libraries that carry velocity through a re-target avoid it. (This is what iOS's _additive animations_ do natively; on the web, choose a spring library that re-targets from the current velocity.) - **Decompose 2D motion into independent X and Y springs.** A single spring on a 2D distance desyncs when X and Y have different velocities. ## 4. Behavior over animation — use springs @@ -74,27 +74,28 @@ Apple deliberately replaced the physics triplet (mass/stiffness/damping) with tw - **Response** — how quickly the value reaches the target, in seconds. Lower = snappier. **This is not "duration"** — a spring has no fixed duration; its settle time emerges from the parameters. **Defaults:** + - Start most UI at **damping `1.0`** (critically damped) — graceful and non-distracting. - Add bounce (**damping ~`0.8`**) **only when the gesture itself carried momentum** (a flick, a throw, a drag release). Overshoot on a menu that just faded in feels wrong; overshoot on a card you flicked feels right. **Concrete values Apple ships:** -| Interaction | Damping | Response | -| --- | --- | --- | -| Move / reposition (e.g. PiP) | `1.0` | `0.4` | -| Rotation | `0.8` | `0.4` | -| Drawer / sheet | `0.8` | `0.3` | +| Interaction | Damping | Response | +| ---------------------------- | ------- | -------- | +| Move / reposition (e.g. PiP) | `1.0` | `0.4` | +| Rotation | `0.8` | `0.4` | +| Drawer / sheet | `0.8` | `0.3` | **Web mapping (Motion / Framer Motion):** the `bounce` + `duration` spring API maps closely to Apple's damping + response. A safe house style is `damping: 1.0` springs everywhere by default; reserve bounce for momentum-driven, physical interactions. ```js -import { animate } from 'motion'; +import { animate } from "motion"; // Critically damped default (no overshoot) -animate(el, { y: 0 }, { type: 'spring', bounce: 0, duration: 0.4 }); +animate(el, { y: 0 }, { type: "spring", bounce: 0, duration: 0.4 }); // Momentum interaction — a little bounce, only because a flick preceded it -animate(el, { y: target }, { type: 'spring', bounce: 0.2, duration: 0.4 }); +animate(el, { y: target }, { type: "spring", bounce: 0.2, duration: 0.4 }); ``` ## 5. Velocity handoff — the seam between drag and animation @@ -109,26 +110,26 @@ relativeVelocity = gestureVelocity / (targetValue − currentValue) Example: element at `y=50`, target `y=150` (100px to go), finger moving 50px/s → initial spring velocity = `50 / 100 = 0.5`. Framer Motion / Motion take absolute px/s velocity directly (`velocity` option), so you usually hand it the raw value. -## 6. Momentum projection — animate to where the gesture is *going* +## 6. Momentum projection — animate to where the gesture is _going_ > "Take a small input and make a big output." -Don't snap to the nearest boundary from the *release point*. Use velocity to **project the resting position** — exactly like scroll deceleration — then snap to the target nearest that projected point. This is what makes a flick feel like it throws the element. +Don't snap to the nearest boundary from the _release point_. Use velocity to **project the resting position** — exactly like scroll deceleration — then snap to the target nearest that projected point. This is what makes a flick feel like it throws the element. -Apple's exact projection function (from the *Designing Fluid Interfaces* sample code): +Apple's exact projection function (from the _Designing Fluid Interfaces_ sample code): ```js // decelerationRate ≈ 0.998 for normal scroll feel; 0.99 for snappier function project(initialVelocity /* px/s */, decelerationRate = 0.998) { - return (initialVelocity / 1000) * decelerationRate / (1 - decelerationRate); + return ((initialVelocity / 1000) * decelerationRate) / (1 - decelerationRate); } const projectedEndpoint = currentPosition + project(releaseVelocity); -const target = nearestSnapPoint(projectedEndpoint); // choose target from the projection +const target = nearestSnapPoint(projectedEndpoint); // choose target from the projection animateSpringTo(target, { velocity: releaseVelocity }); // then hand off velocity (§5) ``` -Note: the physics-textbook `v²/(2·decel)` is *not* what Apple ships — use the exponential-decay form above. This is the standard behavior in good bottom-sheets and carousels (Vaul, Embla). +Note: the physics-textbook `v²/(2·decel)` is _not_ what Apple ships — use the exponential-decay form above. This is the standard behavior in good bottom-sheets and carousels (Vaul, Embla). ## 7. Spatial consistency — symmetric paths, anchored origins @@ -155,14 +156,14 @@ function rubberband(overshoot, dimension, constant = 0.55) { ## 10. Gesture design details (the "feel" checklist) -- **Tap:** highlight on touch-*down* (instant), commit on touch-*up*. Add ~10px of hysteresis/hit padding around the target, and allow cancel-by-dragging-away and back. +- **Tap:** highlight on touch-_down_ (instant), commit on touch-_up_. Add ~10px of hysteresis/hit padding around the target, and allow cancel-by-dragging-away and back. - **Drag/swipe:** require a small movement threshold (hysteresis, ~10px) before committing to a direction, then track 1:1. -- **Detect all plausible gestures in parallel from the first move**, then confidently cancel the losers once intent is clear. Avoid recognizers that only report a *final* state (`swipeleft`-type events) — they throw away the continuous tracking you need for feedback. +- **Detect all plausible gestures in parallel from the first move**, then confidently cancel the losers once intent is clear. Avoid recognizers that only report a _final_ state (`swipeleft`-type events) — they throw away the continuous tracking you need for feedback. - **Minimize disambiguation delays.** Double-tap detection unavoidably delays single taps; only pay that cost where double-tap truly exists. ## 11. Frame-level smoothness -Smoothness is about *what's in the frames*, not just the frame rate. +Smoothness is about _what's in the frames_, not just the frame rate. - Keep the per-frame positional change below the perception threshold to avoid strobing. - For very fast motion, a subtle **motion blur / stretch** encodes speed and reads better than a hard sharp streak. @@ -175,7 +176,7 @@ Apple uses translucent materials as a floating functional layer that brings stru - **Build nav/toolbars/sheets as translucent layers** (`backdrop-filter: blur()` + a semi-transparent background) with content scrolling underneath — not opaque bars that consume a fixed strip. - **Material weight encodes hierarchy:** darker/heavier materials separate structural regions (sidebars); lighter materials draw attention to interactive elements (buttons). **Never stack a light translucent surface on another** — legibility collapses. - **Bigger surfaces should read as thicker:** stronger blur + a deeper shadow than small chips. Consider context-aware shadow — heavier over busy/text content for separation, lighter over plain backgrounds. -- **Dim to focus, separate to keep flow.** A modal task pairs the surface with a dimming scrim and pushes the background back/down. A parallel, non-blocking panel uses translucency and offset *without* a scrim so the flow isn't broken. For stacked sheets, progressively dim and push back each parent layer. +- **Dim to focus, separate to keep flow.** A modal task pairs the surface with a dimming scrim and pushes the background back/down. A parallel, non-blocking panel uses translucency and offset _without_ a scrim so the flow isn't broken. For stacked sheets, progressively dim and push back each parent layer. - **Vibrancy keeps text legible over changing backgrounds.** Over blurred/translucent surfaces, don't use flat gray text — use higher-contrast, slightly heavier weight, and a small letter-spacing bump. Put color on a solid layer, not the translucent foreground. - **Scroll edge effects, not hard dividers.** Instead of a 1px border under a sticky header, fade a small blur/gradient mask where content meets floating chrome — only where floating UI actually overlaps content. - **Materialize, don't just fade.** For glass/blur surfaces, animate blur radius and scale together on enter/exit, so the surface reads as a real material arriving rather than a plain opacity fade. @@ -190,7 +191,7 @@ Apple uses translucent materials as a floating functional layer that brings stru ## 13. Multimodal feedback — motion + sound + haptics -Three rules for combining senses (from *Designing Audio-Haptic Experiences*): +Three rules for combining senses (from _Designing Audio-Haptic Experiences_): 1. **Causality** — it must be obvious what caused the feedback. Trigger it on the actual causal event (the toggle flipping, the item snapping home), and match its character to the action's physicality. 2. **Harmony** — the visual, the sound, and the haptic must fire on the **same frame**. Latency between them destroys the illusion. Don't let a CSS transition lag the audio/haptic (Vibration API). @@ -198,7 +199,7 @@ Three rules for combining senses (from *Designing Audio-Haptic Experiences*): ## 14. Reduced motion & accessibility -Reduced motion doesn't mean *no* feedback — it means a gentler, non-vestibular equivalent. Respond to three independent signals and bake them into your components: +Reduced motion doesn't mean _no_ feedback — it means a gentler, non-vestibular equivalent. Respond to three independent signals and bake them into your components: - **`prefers-reduced-motion: reduce`** — replace slides/springs/parallax with short opacity **cross-fades or static transitions**. Drop elastic/overshoot. Keep opacity/color changes that aid comprehension. - **`prefers-reduced-transparency: reduce`** — make translucent surfaces frostier/solid: raise background opacity, drop the blur. @@ -208,44 +209,54 @@ Also: avoid full-viewport moving backgrounds, slow looping oscillations (near 0. ```css @media (prefers-reduced-motion: reduce) { - .sheet { transition: opacity 200ms ease; transform: none !important; } + .sheet { + transition: opacity 200ms ease; + transform: none !important; + } } @media (prefers-reduced-transparency: reduce) { - .toolbar { background: white; backdrop-filter: none; } + .toolbar { + background: white; + backdrop-filter: none; + } } ``` ## 15. Typography — optical sizing, tracking, leading -Apple designs type to change shape with size; the same discipline applies on the web. (From *The Details of UI Typography*, WWDC 2020.) +Apple designs type to change shape with size; the same discipline applies on the web. (From _The Details of UI Typography_, WWDC 2020.) -- **Tracking (letter-spacing) is size-specific — never one value for all sizes.** Large display text wants *negative* tracking (letters read too far apart as they grow); small text wants slightly *positive* tracking for legibility. A fixed `letter-spacing` is wrong somewhere. Tighten headings, leave body near `0`. +- **Tracking (letter-spacing) is size-specific — never one value for all sizes.** Large display text wants _negative_ tracking (letters read too far apart as they grow); small text wants slightly _positive_ tracking for legibility. A fixed `letter-spacing` is wrong somewhere. Tighten headings, leave body near `0`. - **Leading (line-height) tracks size inversely.** Tight on large headings, looser on body copy. Increase it for scripts with tall ascenders/descenders; tighten it for dense, information-heavy UI. - **Build hierarchy from weight + size + leading as a set,** not size alone. Emphasize with weight — it adds presence without taking more space. -- **Respect the user's text-size setting** (Dynamic Type). Scale layout *with* the text — spacing in `rem`/`em`, not fixed px — so a larger font doesn't break the layout. +- **Respect the user's text-size setting** (Dynamic Type). Scale layout _with_ the text — spacing in `rem`/`em`, not fixed px — so a larger font doesn't break the layout. - **Default to the platform's system font** before a custom face; it already ships optical sizing, tracking tables, and legibility tuning. Override only with a reason. ```css -:root { font: 100%/1.5 system-ui, sans-serif; } /* body: system font, comfortable leading */ +:root { + font: + 100%/1.5 system-ui, + sans-serif; +} /* body: system font, comfortable leading */ .display { font-size: clamp(2rem, 5vw, 4rem); - line-height: 1.05; /* tight leading for large text */ - letter-spacing: -0.02em; /* negative tracking as it grows */ + line-height: 1.05; /* tight leading for large text */ + letter-spacing: -0.02em; /* negative tracking as it grows */ font-optical-sizing: auto; } ``` ## 16. Design foundations — the eight principles -The motion and craft above serve Apple's eight design principles (*Principles of Great Design*, WWDC 2026). Use these as the names you reason with: +The motion and craft above serve Apple's eight design principles (_Principles of Great Design_, WWDC 2026). Use these as the names you reason with: -1. **Purpose.** Make with intention; decide what *not* to build. Every feature asks for the user's time, attention, and trust — spend that budget only where it pays off. +1. **Purpose.** Make with intention; decide what _not_ to build. Every feature asks for the user's time, attention, and trust — spend that budget only where it pays off. 2. **Agency.** Keep people in control: offer choices, don't force a single path. Back it with forgiveness — easy undo for slips, a confirmation dialog only for genuinely destructive, irreversible actions (use sparingly; overusing it trains people to click through). 3. **Responsibility.** Act in the user's interest. Privacy: ask at the right moment, only for what's needed, transparently. Safety: anticipate misuse and harm — especially with AI (an allergy-aware recipe app must not suggest a harmful ingredient). Add previews, confirmations, disclaimers; cut a feature whose risk outweighs its value. 4. **Familiarity.** Build on what people already know. Use metaphors that are neither too literal nor too abstract (a trash can means delete), and honor their physics. Be consistent: things that look the same must behave the same and live in the same place (close is always top-left on macOS) so people can predict what happens next. Only break a familiar pattern if you can prove it's better — then test it, don't assume. 5. **Flexibility.** Design for different contexts, devices, and the full range of abilities. Adapt to the platform (iPhone = quick touch; desktop = deep workflows with precise pointer control) and to the situation. Design inclusively (age, language, expertise, accessibility). When no single layout fits everyone, let people personalize — rearrange controls, hide what they don't use. -6. **Simplicity — not minimalism.** Strip the unnecessary so the core purpose shines; burying everything in one place looks minimal but isn't simple. Be concise (plain language, no jargon, fewer steps) and clear (use hierarchy — order, spacing, contrast — so the most important thing is the most obvious). Every element earns its place; sometimes *adding* context simplifies (a video scrubber that shows time remaining). Show the common path first, advanced options one level deeper. +6. **Simplicity — not minimalism.** Strip the unnecessary so the core purpose shines; burying everything in one place looks minimal but isn't simple. Be concise (plain language, no jargon, fewer steps) and clear (use hierarchy — order, spacing, contrast — so the most important thing is the most obvious). Every element earns its place; sometimes _adding_ context simplifies (a video scrubber that shows time remaining). Show the common path first, advanced options one level deeper. 7. **Craft.** Uncompromising attention to detail builds trust. Beautiful typography, colors that adapt to light/dark, clear iconography, and responsive animations that give immediate, natural feedback. Nothing is random — every spacing, timing, and alignment value is a deliberate choice you can defend. Jittery scroll, misaligned icons, and layouts that break on rotation read as carelessness. Craft needs iteration and longevity — keep evolving the design as features and hardware change. 8. **Delight.** The result of getting the other seven right, not confetti tacked on top. Decide the emotion you want people to feel (calm, confident, excited) and reinforce it in every decision. @@ -264,19 +275,19 @@ Tactical rules that serve these: ## Quick Reference -| Need | Technique | Concrete value | -| --- | --- | --- | -| Default UI spring | Critically damped, no overshoot | `damping 1.0`, `response 0.3–0.4` | -| Momentum / flick spring | Under-damped, slight bounce | `damping ~0.8`, `response 0.3–0.4` | -| Gesture → spring velocity | Hand off release velocity | `gestureVelocity / (target − current)` if normalized | -| Flick landing point | Project momentum | `current + (v/1000)·d/(1−d)`, `d ≈ 0.998` | -| Interrupt cleanly | Start from presentation (live) value | read the on-screen transform | -| Avoid reversal "brick wall" | Carry velocity through re-target | spring that blends velocity | -| Reversible transition | Mirror the easing curve | inverse cubic-bézier | -| Decide reverse vs. commit | Use velocity **sign**, not position | at release | -| 1:1 drag | Pointer Events + capture | respect the grab offset | -| Feedback | On pointer-down, continuous | never only at the end | -| Boundary | Rubber-band, don't hard-stop | progressive resistance | -| Translucent chrome | `backdrop-filter` layer | content scrolls under | -| Type tracking | Size-specific, never fixed | tighten large text (`-0.02em`), body near `0` | -| Reduced motion | Cross-fade, not slide/spring | `@media (prefers-reduced-motion)` | +| Need | Technique | Concrete value | +| --------------------------- | ------------------------------------ | ---------------------------------------------------- | +| Default UI spring | Critically damped, no overshoot | `damping 1.0`, `response 0.3–0.4` | +| Momentum / flick spring | Under-damped, slight bounce | `damping ~0.8`, `response 0.3–0.4` | +| Gesture → spring velocity | Hand off release velocity | `gestureVelocity / (target − current)` if normalized | +| Flick landing point | Project momentum | `current + (v/1000)·d/(1−d)`, `d ≈ 0.998` | +| Interrupt cleanly | Start from presentation (live) value | read the on-screen transform | +| Avoid reversal "brick wall" | Carry velocity through re-target | spring that blends velocity | +| Reversible transition | Mirror the easing curve | inverse cubic-bézier | +| Decide reverse vs. commit | Use velocity **sign**, not position | at release | +| 1:1 drag | Pointer Events + capture | respect the grab offset | +| Feedback | On pointer-down, continuous | never only at the end | +| Boundary | Rubber-band, don't hard-stop | progressive resistance | +| Translucent chrome | `backdrop-filter` layer | content scrolls under | +| Type tracking | Size-specific, never fixed | tighten large text (`-0.02em`), body near `0` | +| Reduced motion | Cross-fade, not slide/spring | `@media (prefers-reduced-motion)` | diff --git a/src/components/crapp/BottomNav.tsx b/src/components/crapp/BottomNav.tsx index 8e7913a..6eb7bfb 100644 --- a/src/components/crapp/BottomNav.tsx +++ b/src/components/crapp/BottomNav.tsx @@ -15,9 +15,15 @@ export function BottomNav() { aria-label="Navigazione principale" // `materiale` + `bordo-sfumato`: la barra è un vetro sotto cui il // contenuto scorre, con una sfumatura al posto della riga netta. - className="materiale bordo-sfumato fixed inset-x-0 bottom-0 z-40" + className="materiale bordo-sfumato pad-sicura-fondo fixed inset-x-0 bottom-0 z-40" > -
+ {/* + Altezza fissa dal token, non dedotta dal contenuto: la striscia + toccabile misura uguale su iOS e Android, e sotto varia solo l'inset + di sistema. Prima l'altezza dipendeva dai padding delle voci e nessuno + poteva saperla da fuori. + */} +
{items.map(({ to, label, icon: Icon }) => ( {({ isActive }) => ( <> diff --git a/src/components/crapp/CelebrazioneBadge.tsx b/src/components/crapp/CelebrazioneBadge.tsx index 10907af..e6df35d 100644 --- a/src/components/crapp/CelebrazioneBadge.tsx +++ b/src/components/crapp/CelebrazioneBadge.tsx @@ -32,7 +32,7 @@ export function CelebrazioneBadge() { if (!notifica) return null; return ( -
+
+

404

Pagina non trovata

@@ -50,7 +50,7 @@ function ErrorComponent({ error, reset }: { error: Error; reset: () => void }) { }, [error]); return ( -
+

Questa pagina non si è caricata @@ -187,7 +187,7 @@ function AppShell() { if (!mounted || !pronta) { return ( -
+
); @@ -195,7 +195,7 @@ function AppShell() { return ( <> -
+
{/* Required: nested routes render here. Removing breaks all child routes. */}
diff --git a/src/routes/benvenuto.tsx b/src/routes/benvenuto.tsx index f6bd902..24f3760 100644 --- a/src/routes/benvenuto.tsx +++ b/src/routes/benvenuto.tsx @@ -92,7 +92,7 @@ function Benvenuto() { const inAttesaCollegamento = !!utenteId && !mioSlot && !erroreCollegamento; return ( -
+

Benvenuto in CrAPP diff --git a/src/styles.css b/src/styles.css index 9130243..b90f690 100644 --- a/src/styles.css +++ b/src/styles.css @@ -62,6 +62,19 @@ :root { color-scheme: light; --radius: 1rem; + /* + * Geometria della barra di navigazione, unica sorgente di verità: prima + * l'altezza era ricopiata a occhio come `pb-24` nel contenuto e `pb-28` + * nella celebrazione badge, e su iPhone i conti tornavano per 2 px. + * + * `--pad-sicura-fondo` è il motivo per cui la barra su iPhone e su Android + * non può essere alta uguale: `env(safe-area-inset-bottom)` vale ~34 px + * sopra la home indicator e 0 su Android, dove la barra di sistema sta + * fuori dal viewport. Il `max()` dà comunque ad Android un respiro minimo, + * così la differenza è un margine in più, non un allineamento diverso. + */ + --altezza-nav: 3.75rem; + --pad-sicura-fondo: max(env(safe-area-inset-bottom), 0.5rem); --background: oklch(0.985 0.002 240); --foreground: oklch(0.16 0.01 260); --card: oklch(1 0 0); @@ -351,6 +364,27 @@ -webkit-backdrop-filter: blur(20px) saturate(180%); } +/* + * Chrome su Android disattiva `backdrop-filter` quando l'accelerazione + * hardware non è disponibile: senza questo, su quei device la barra resta + * solo semitrasparente e il contenuto si legge attraverso. + */ +@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) { + .materiale { + background-color: var(--color-card); + } +} + +/** Padding inferiore della chrome flottante. */ +@utility pad-sicura-fondo { + padding-bottom: var(--pad-sicura-fondo); +} + +/** Spazio che il contenuto deve lasciare sotto di sé per non finire dietro la barra. */ +@utility spazio-nav { + padding-bottom: calc(var(--altezza-nav) + var(--pad-sicura-fondo) + 1rem); +} + @utility bordo-sfumato { &::before { content: "";