Niveus/niveus/tokens/elevation.css
Nexoniarz 65d81481bd Fix Firefox: wheels, dark shadows, theme glass, smooth jumps, tri-state
Found by driving Firefox 158 with real pointer, wheel and key input.

- Time picker wheels used padding to reach the last rows. Firefox leaves
  bottom padding out of the scrollable area, so the last rows were
  unreachable and the 24H/AM/PM wheel could not scroll at all. Spacer
  pseudo-elements replace the padding.
- Wheels can now be dragged with a mouse or pen, with inertia, a rubber-band
  at the ends and a spring back. Touch is left to the platform.
- Theme changes no longer use a page-wide view transition. The snapshot
  could not blur what sits behind frosted glass, so every frosted surface
  went clear for the length of the cross-fade.
- Dark shadows were invisible: black on a near-black canvas, with a deep
  negative spread tucking the blur under the element. The dark canvas is
  lifted, the shadows are stronger and the spreads milder.
- Same-page links now travel (scroll-behavior: smooth, motion permitting).
- data-nv-tristate: a checkbox that cycles unchecked, checked, partial; the
  browser can display a partial state but a click can never choose it.
- Button groups show their pressed segment.

GUIDELINES.md gains the cross-engine rules these defects taught, and makes
Firefox the first browser to verify in.

check.py, build.py and build.py --check pass.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 09:50:40 +01:00

130 lines
6.1 KiB
CSS

/* ==========================================================================
Niveus — Elevation
--------------------------------------------------------------------------
Depth is a scale, not a free choice. Each level describes one relationship
between a surface and what sits behind it:
0 flush painted on the canvas, no separation
1 resting cards, list rows, static panels
2 raised buttons, controls, anything that reacts to a pointer
3 floating popovers, menus, dropdowns, tooltips
4 lifted dialogs, sheets, detached panels
5 overlay the topmost layer, used sparingly
Every level is built from the same three shadow colors so depth stays
consistent between appearances: an ambient contact shadow that keeps the
edge crisp, a soft penumbra for the spread, and an umbra that only appears
once a surface is genuinely far from the canvas.
Two things keep a shadow visible, and both have failed before. The soft
layers use only a mild negative spread: a deep one tucks the blur under the
element and leaves almost nothing outside it. And the canvas has to be
lighter than the shadow colour — black cannot darken black — which is why the
dark canvas is not the darkest value it could be.
========================================================================== */
:root {
--nv-elevation-0: none;
--nv-elevation-1:
0 1px 1px var(--nv-color-shadow-ambient),
0 2px 4px -1px var(--nv-color-shadow-penumbra),
0 6px 14px -5px var(--nv-color-shadow-umbra);
--nv-elevation-2:
0 1px 1px var(--nv-color-shadow-ambient),
0 3px 6px -2px var(--nv-color-shadow-penumbra),
0 10px 20px -7px var(--nv-color-shadow-umbra);
--nv-elevation-3:
0 1px 2px var(--nv-color-shadow-ambient),
0 6px 12px -4px var(--nv-color-shadow-penumbra),
0 18px 36px -9px var(--nv-color-shadow-umbra);
--nv-elevation-4:
0 1px 2px var(--nv-color-shadow-ambient),
0 10px 20px -6px var(--nv-color-shadow-penumbra),
0 30px 60px -12px var(--nv-color-shadow-umbra);
--nv-elevation-5:
0 2px 3px var(--nv-color-shadow-ambient),
0 16px 32px -8px var(--nv-color-shadow-penumbra),
0 44px 88px -16px var(--nv-color-shadow-umbra);
/* --- Inset ---------------------------------------------------------------
Recessed surfaces: fields, wells, tracks, pressed controls. */
--nv-elevation-inset-sm: inset 0 1px 2px var(--nv-color-shadow-inset);
--nv-elevation-inset-md:
inset 0 1px 1px var(--nv-color-shadow-inset),
inset 0 3px 6px -2px var(--nv-color-shadow-inset);
--nv-elevation-inset-lg:
inset 0 2px 3px var(--nv-color-shadow-inset),
inset 0 6px 12px -3px var(--nv-color-shadow-inset);
/* --- Highlight -----------------------------------------------------------
The thin catch of light along the top edge of a raised surface. This is
what makes Niveus read as material rather than as a rectangle with a
shadow. Always paired with an elevation, never used alone. */
--nv-highlight-top: inset 0 1px 0 var(--nv-color-highlight);
--nv-highlight-top-strong: inset 0 1px 0 var(--nv-color-highlight-strong);
--nv-highlight-edge:
inset 0 1px 0 var(--nv-color-highlight),
inset 0 0 0 1px var(--nv-color-border-subtle);
/* --- Bevel -----------------------------------------------------------------
The highlight's other half. A raised face is lit along its top edge and
shaded along its bottom one; together they are the bevel, and the bevel is
what makes a Niveus control look like something a finger could press.
Raised controls and surfaces take the full bevel; the highlight alone is
for things that sit flush. */
--nv-lowlight-bottom: inset 0 -1px 0 var(--nv-color-lowlight);
--nv-bevel:
inset 0 1px 0 var(--nv-color-highlight),
inset 0 -1px 0 var(--nv-color-lowlight);
/* --- Outline ---------------------------------------------------------------
The contact line: a hairline just outside a raised surface, where it meets
whatever it rests on. In dark it is near-black and does most of the
separating; in light it only softens the border beside it. */
--nv-outline: 0 0 0 1px var(--nv-color-outline);
/* --- Ledge -----------------------------------------------------------------
A recess catches light on its *lower* lip, the edge facing the viewer.
Paired with an inset shadow, it turns a field from a dark rectangle into
a well cut into the surface. */
--nv-ledge: 0 1px 0 var(--nv-color-highlight);
/* --- Focus ---------------------------------------------------------------
A contrast ring sits under the accent ring so focus stays visible on both
light and dark surfaces, and on accent-filled controls. */
--nv-focus-ring-width: 3px;
--nv-focus-ring:
0 0 0 1px var(--nv-color-focus-ring-contrast),
0 0 0 calc(1px + var(--nv-focus-ring-width)) var(--nv-color-focus-ring);
/* --- Glow ------------------------------------------------------------------
Light a surface emits rather than the shade it casts. Glow is for things
that are *on* — a live status, the selected item in a showcase, a lit key —
and it always sits beside an elevation, never instead of one: a glowing
surface still rests on something.
The geometry is a scale like elevation. The color is the component's: a
tinted surface glows in its own tint, mixed down to --nv-glow-strength. */
--nv-glow-strength: 40%;
--nv-glow-blur-sm: 12px;
--nv-glow-spread-sm: -2px;
--nv-glow-blur-md: 28px;
--nv-glow-spread-md: -6px;
--nv-glow-blur-lg: 64px;
--nv-glow-spread-lg: -12px;
}
/* The accent glows are declared on every branded subtree as well as the root:
a var() inside a custom property resolves where the property is declared, so
a root-only declaration would glow in the root brand everywhere. */
:root,
[data-nv-brand] {
--nv-glow-sm: 0 0 var(--nv-glow-blur-sm) var(--nv-glow-spread-sm) var(--nv-color-glow);
--nv-glow-md: 0 0 var(--nv-glow-blur-md) var(--nv-glow-spread-md) var(--nv-color-glow);
--nv-glow-lg: 0 0 var(--nv-glow-blur-lg) var(--nv-glow-spread-lg) var(--nv-color-glow);
}