mirror of
https://github.com/Nexoniarz/Niveus.git
synced 2026-11-03 04:34:26 +00:00
Merge pull request
Depth and light: glow, tiles, avatars, stats, steps, decks, skeletons, reveal
This commit is contained in:
commit
f340d566b8
19 changed files with 1619 additions and 14 deletions
34
README.md
34
README.md
|
|
@ -149,6 +149,27 @@ answers how far that surface is from the canvas. They are independent.
|
||||||
Applied as classes: `.nv-material-frosted`, `.nv-elevation-3`. A shadow that is
|
Applied as classes: `.nv-material-frosted`, `.nv-elevation-3`. A shadow that is
|
||||||
on neither scale is a shadow that means nothing.
|
on neither scale is a shadow that means nothing.
|
||||||
|
|
||||||
|
### Light
|
||||||
|
|
||||||
|
Elevation is the shade a surface casts. **Glow** is the light it gives off —
|
||||||
|
and it is reserved for things that are *on*: a live status, the plan you are
|
||||||
|
on, a finished step, the selected item in a showcase. Glow never replaces an
|
||||||
|
elevation; a glowing surface still rests on something.
|
||||||
|
|
||||||
|
| Where | How |
|
||||||
|
| ----- | --- |
|
||||||
|
| A card that belongs to something with a color | `.nv-card--lit` — a lit top edge in `--nv-card-tint` |
|
||||||
|
| The one card that is on | `.nv-card--glow` |
|
||||||
|
| A showcase card | `.nv-card--spotlight` — light follows the pointer |
|
||||||
|
| A feature icon or product mark | `.nv-tile`, `--glow` to light it |
|
||||||
|
| A person | `.nv-avatar--glow` |
|
||||||
|
| A display line | `.nv-text-lit` — type cut from the same lit material |
|
||||||
|
|
||||||
|
Glows come in the same steps as everything else (`--nv-glow-sm/md/lg`), and a
|
||||||
|
tinted surface glows in its own tint, mixed down to `--nv-glow-strength`. Two
|
||||||
|
glowing things side by side cancel each other out: if everything is lit,
|
||||||
|
nothing is.
|
||||||
|
|
||||||
## Continuity
|
## Continuity
|
||||||
|
|
||||||
Every interaction should read as a continuation of the state before it. When
|
Every interaction should read as a continuation of the state before it. When
|
||||||
|
|
@ -182,6 +203,11 @@ works; changes simply arrive rather than travel. It also wires
|
||||||
`[data-nv-theme]`, `[data-nv-dialog]`, `[data-nv-tabs]` and `[data-nv-close]`
|
`[data-nv-theme]`, `[data-nv-dialog]`, `[data-nv-tabs]` and `[data-nv-close]`
|
||||||
so the common cases need no code at all.
|
so the common cases need no code at all.
|
||||||
|
|
||||||
|
Long pages can arrive in the order they were written: `.nv-reveal` rises into
|
||||||
|
place once as it scrolls into view, and `.nv-reveal-group` brings its children
|
||||||
|
in one after another, `--nv-stagger` apart. Only the script marks anything as
|
||||||
|
waiting, so without it — and under reduced motion — nothing is ever hidden.
|
||||||
|
|
||||||
### One rule worth knowing
|
### One rule worth knowing
|
||||||
|
|
||||||
A `view-transition-name` must be unique among all rendered elements, or the
|
A `view-transition-name` must be unique among all rendered elements, or the
|
||||||
|
|
@ -391,7 +417,7 @@ probably not the problem the system has.
|
||||||
| -------------- | --------------------------------------------------------- |
|
| -------------- | --------------------------------------------------------- |
|
||||||
| `.nv-button` | `--primary` `--subtle` `--ghost` `--danger` `--frosted` `--float`, `--sm`/`--lg`, `--icon`, `--pill`, `--block`, `.nv-button-group` |
|
| `.nv-button` | `--primary` `--subtle` `--ghost` `--danger` `--frosted` `--float`, `--sm`/`--lg`, `--icon`, `--pill`, `--block`, `.nv-button-group` |
|
||||||
| `.nv-input` | with `.nv-field`, `.nv-input-group`, `.nv-select`, `.nv-checkbox`, `.nv-radio`, `.nv-switch`, `.nv-choice` |
|
| `.nv-input` | with `.nv-field`, `.nv-input-group`, `.nv-select`, `.nv-checkbox`, `.nv-radio`, `.nv-switch`, `.nv-choice` |
|
||||||
| `.nv-card` | `--elevated` `--floating` `--inset` `--frosted` `--interactive`, `.nv-card-grid` |
|
| `.nv-card` | `--elevated` `--floating` `--inset` `--frosted` `--interactive`, light: `--lit` `--glow` `--spotlight`, `.nv-card-grid` |
|
||||||
| `.nv-dialog` | native `<dialog>`; `--sheet` `--compact` `--wide` |
|
| `.nv-dialog` | native `<dialog>`; `--sheet` `--compact` `--wide` |
|
||||||
| `.nv-nav` | `--vertical` `--plain` `--inline`, plus `.nv-nav-tabs`, `.nv-tab-panel` and `.nv-breadcrumbs` |
|
| `.nv-nav` | `--vertical` `--plain` `--inline`, plus `.nv-nav-tabs`, `.nv-tab-panel` and `.nv-breadcrumbs` |
|
||||||
| `.nv-menu` | anchored popover with `.nv-menu-trigger`, `__item`, `__separator`, `__label`, `__shortcut` |
|
| `.nv-menu` | anchored popover with `.nv-menu-trigger`, `__item`, `__separator`, `__label`, `__shortcut` |
|
||||||
|
|
@ -400,6 +426,12 @@ probably not the problem the system has.
|
||||||
| `.nv-bed` | ground that is visibly not ordinary background; `--quiet` `--vivid` `--single` `--flush` |
|
| `.nv-bed` | ground that is visibly not ordinary background; `--quiet` `--vivid` `--single` `--flush` |
|
||||||
| `.nv-badge` | state, not an action; `--accent` `--success` `--warning` `--danger`, `--solid` `--count` `--square` |
|
| `.nv-badge` | state, not an action; `--accent` `--success` `--warning` `--danger`, `--solid` `--count` `--square` |
|
||||||
| `.nv-table` | inside `.nv-table-frame`; `__number` `__actions`, `--compact` `--interactive` |
|
| `.nv-table` | inside `.nv-table-frame`; `__number` `__actions`, `--compact` `--interactive` |
|
||||||
|
| `.nv-tile` | a small raised key holding one glyph; `--solid` `--glow`, `--sm` `--lg` `--xl` |
|
||||||
|
| `.nv-avatar` | round raised disc; `--ring` `--glow`, sizes, `__status`, `.nv-avatar-group` |
|
||||||
|
| `.nv-stat` | readings set into one recessed well with `.nv-stat-group`; `__label` `__value` `__delta` |
|
||||||
|
| `.nv-steps` | markers set into a rail; `data-state="done"` lights the way, `--vertical` |
|
||||||
|
| `.nv-deck` | one surface in front of others of its kind, with `__layer` |
|
||||||
|
| `.nv-skeleton` | a waiting well with a slow sweep of light; `--text` `--circle` |
|
||||||
| `.nv-surface` | materials and elevation as utilities |
|
| `.nv-surface` | materials and elevation as utilities |
|
||||||
|
|
||||||
Variants are defined by reassigning private `--_*` properties. Interaction —
|
Variants are defined by reassigning private `--_*` properties. Interaction —
|
||||||
|
|
|
||||||
92
REFERENCE.md
92
REFERENCE.md
|
|
@ -76,6 +76,9 @@ States come from the element: `disabled`, `aria-disabled`, `aria-busy="true"`
|
||||||
| `.nv-card--frosted` | glass card | Translucent, for use over imagery or a bed. |
|
| `.nv-card--frosted` | glass card | Translucent, for use over imagery or a bed. |
|
||||||
| `.nv-card--interactive` | clickable card | Lifts on hover, sinks on press. |
|
| `.nv-card--interactive` | clickable card | Lifts on hover, sinks on press. |
|
||||||
| `.nv-card--flush` | edge-to-edge card | Removes part padding, for media or tables. |
|
| `.nv-card--flush` | edge-to-edge card | Removes part padding, for media or tables. |
|
||||||
|
| `.nv-card--lit` | accent-edge card, colored top border | A strip of light along the top edge in the card's tint, spilling a little down the face. For a card that belongs to something with a color. |
|
||||||
|
| `.nv-card--glow` | glowing card, highlighted card | Gives off its tint. For the one card that is *on* — the current plan, the live item. |
|
||||||
|
| `.nv-card--spotlight` | spotlight card, cursor glow | A soft light follows the pointer across the face (needs `niveus.js`). Showcases only. |
|
||||||
| `.nv-card__header` | card header | |
|
| `.nv-card__header` | card header | |
|
||||||
| `.nv-card__title` | card title | |
|
| `.nv-card__title` | card title | |
|
||||||
| `.nv-card__subtitle` | card subtitle, supporting text | |
|
| `.nv-card__subtitle` | card subtitle, supporting text | |
|
||||||
|
|
@ -86,6 +89,19 @@ States come from the element: `disabled`, `aria-disabled`, `aria-busy="true"`
|
||||||
| `.nv-card__link` | stretched link | Expands one link's hit area over the whole card. |
|
| `.nv-card__link` | stretched link | Expands one link's hit area over the whole card. |
|
||||||
| `.nv-card-grid` | responsive card grid | Auto-fitting, equal-height columns. |
|
| `.nv-card-grid` | responsive card grid | Auto-fitting, equal-height columns. |
|
||||||
|
|
||||||
|
`--lit`, `--glow` and `--spotlight` combine with every elevation and with each
|
||||||
|
other. They take `--nv-card-tint` (the accent unless set on the card).
|
||||||
|
|
||||||
|
## Deck
|
||||||
|
|
||||||
|
| Class | Known as | Does |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `.nv-deck` | card stack, stacked cards | One surface in front of others of its kind. Every child shares one grid cell. |
|
||||||
|
| `.nv-deck__layer` | stack layer | A decorative card behind the front one, stepping back and dimming with depth. Up to two; mark them `aria-hidden="true"`. |
|
||||||
|
|
||||||
|
Hidden `.nv-tab-panel`s can live in a deck together: the front surface is
|
||||||
|
whichever one is showing.
|
||||||
|
|
||||||
## Dialog
|
## Dialog
|
||||||
|
|
||||||
Built on native `<dialog>`; open with `showModal()` or `nv.openDialog()`.
|
Built on native `<dialog>`; open with `showModal()` or `nv.openDialog()`.
|
||||||
|
|
@ -173,6 +189,61 @@ Set `--nv-scroller-item` for item width, `--nv-scroller-fade` for the edge fade.
|
||||||
| `.nv-badge--square` | square badge | |
|
| `.nv-badge--square` | square badge | |
|
||||||
| `.nv-badge__dot` | status dot | |
|
| `.nv-badge__dot` | status dot | |
|
||||||
|
|
||||||
|
## Tile
|
||||||
|
|
||||||
|
| Class | Known as | Does |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `.nv-tile` | icon tile, icon badge, feature icon | A small raised key holding one glyph: tinted face, glyph in its tint. Not interactive. |
|
||||||
|
| `.nv-tile--solid` | filled icon tile | Saturated face with a light glyph. |
|
||||||
|
| `.nv-tile--glow` | glowing icon tile | Lit from inside. |
|
||||||
|
| `.nv-tile--sm` `.nv-tile--lg` `.nv-tile--xl` | tile sizes | 32 / 56 / 72px; default 44px. |
|
||||||
|
|
||||||
|
`--nv-tile-tint` picks the color. An `<img>` inside fills the key, an `<svg>`
|
||||||
|
takes half of it.
|
||||||
|
|
||||||
|
## Avatar
|
||||||
|
|
||||||
|
| Class | Known as | Does |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `.nv-avatar` | avatar, profile picture | Round raised disc holding an `<img>` or initials. |
|
||||||
|
| `.nv-avatar--ring` | avatar ring, story ring | A ring in `--nv-avatar-tint`, set off by a gap of surface. |
|
||||||
|
| `.nv-avatar--glow` | glowing avatar | The ring, giving off its tint. |
|
||||||
|
| `.nv-avatar--sm` `.nv-avatar--lg` `.nv-avatar--xl` | avatar sizes | 28 / 64 / 96px; default 40px. |
|
||||||
|
| `.nv-avatar__status` | presence dot | Grey at rest. |
|
||||||
|
| `.nv-avatar__status--online` | online indicator | Lit green. |
|
||||||
|
| `.nv-avatar-group` | avatar stack, facepile | Overlapping row. |
|
||||||
|
|
||||||
|
## Stat
|
||||||
|
|
||||||
|
| Class | Known as | Does |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `.nv-stat-group` | stats row, KPI strip, readout | One recessed well holding several readings, split by hairlines. Wraps on its own. Use a `<dl>`. |
|
||||||
|
| `.nv-stat` | stat, metric, KPI | One reading: label, value, optional delta, in that order. |
|
||||||
|
| `.nv-stat__label` | metric label | `<dt>` inside a group. |
|
||||||
|
| `.nv-stat__value` | metric value, big number | `<dd>`; tabular numerals. |
|
||||||
|
| `.nv-stat__delta` | change, trend | |
|
||||||
|
| `.nv-stat__delta--up` `.nv-stat__delta--down` | positive / negative trend | Success and danger text. |
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
| Class | Known as | Does |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `.nv-steps` | stepper, timeline, process, progress steps | Markers set into a rail cut into the surface. Horizontal, vertical below 40rem. Use an `<ol>`. |
|
||||||
|
| `.nv-steps--vertical` | vertical stepper, timeline | Always vertical. |
|
||||||
|
| `.nv-steps__item` | step | `data-state="done"` lights it and its stretch of rail; `aria-current="step"` lights the marker only. |
|
||||||
|
| `.nv-steps__marker` | step number, step knob | |
|
||||||
|
| `.nv-steps__body` | step content | |
|
||||||
|
| `.nv-steps__title` | step title | |
|
||||||
|
| `.nv-steps__text` | step description | |
|
||||||
|
|
||||||
|
## Skeleton
|
||||||
|
|
||||||
|
| Class | Known as | Does |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `.nv-skeleton` | skeleton, loading placeholder, shimmer | A shallow well where content will be, with a slow band of light crossing it. Still under reduced motion. |
|
||||||
|
| `.nv-skeleton--text` | text line placeholder | The last of several lines ends short. |
|
||||||
|
| `.nv-skeleton--circle` | avatar placeholder | |
|
||||||
|
|
||||||
## Table
|
## Table
|
||||||
|
|
||||||
| Class | Known as | Does |
|
| Class | Known as | Does |
|
||||||
|
|
@ -256,6 +327,15 @@ var(--nv-sheen-raised)` over its role color.
|
||||||
| `.nv-code` | code, monospace | 13px |
|
| `.nv-code` | code, monospace | 13px |
|
||||||
| `.nv-text-primary` `.nv-text-secondary` `.nv-text-tertiary` | text color roles | Emphasis, not size. |
|
| `.nv-text-primary` `.nv-text-secondary` `.nv-text-tertiary` | text color roles | Emphasis, not size. |
|
||||||
| `.nv-text-accent` `.nv-text-success` `.nv-text-warning` `.nv-text-danger` | semantic text colors | |
|
| `.nv-text-accent` `.nv-text-success` `.nv-text-warning` `.nv-text-danger` | semantic text colors | |
|
||||||
|
| `.nv-text-lit` | gradient text, metallic text | Lit from above, settling into the accent. One display line per page. |
|
||||||
|
| `.nv-text-lit--accent` | accent gradient text | The accent alone, lit from above — for a phrase inside a display line. |
|
||||||
|
|
||||||
|
## Reveal
|
||||||
|
|
||||||
|
| Class | Known as | Does |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `.nv-reveal` | scroll reveal, animate on scroll | Rises and fades in once, as it scrolls into view. Needs `niveus.js`; without it, or under reduced motion, nothing is hidden. |
|
||||||
|
| `.nv-reveal-group` | staggered reveal | Its children arrive one after another, `--nv-stagger` apart. |
|
||||||
|
|
||||||
## Layout utilities
|
## Layout utilities
|
||||||
|
|
||||||
|
|
@ -309,6 +389,7 @@ material-frosted material-glass material-border
|
||||||
window-close window-minimize window-zoom window-inactive
|
window-close window-minimize window-zoom window-inactive
|
||||||
window-control-ring window-glyph
|
window-control-ring window-glyph
|
||||||
focus-ring focus-ring-contrast selection
|
focus-ring focus-ring-contrast selection
|
||||||
|
glow
|
||||||
```
|
```
|
||||||
|
|
||||||
`on-*` is the text that goes on top of that color, and is derived — never set
|
`on-*` is the text that goes on top of that color, and is derived — never set
|
||||||
|
|
@ -348,15 +429,17 @@ never reference them.
|
||||||
| `--nv-sheen-` | `raised filled surface knob` — gradients laid over a role color as `background-image` |
|
| `--nv-sheen-` | `raised filled surface knob` — gradients laid over a role color as `background-image` |
|
||||||
| `--nv-blur-` | `sm md lg xl` — also `backdrop-frosted` `backdrop-glass` `backdrop-scrim` |
|
| `--nv-blur-` | `sm md lg xl` — also `backdrop-frosted` `backdrop-glass` `backdrop-scrim` |
|
||||||
| `--nv-material-` | `{solid,elevated,inset,frosted,glass}-{bg,border,shadow}` |
|
| `--nv-material-` | `{solid,elevated,inset,frosted,glass}-{bg,border,shadow}` |
|
||||||
|
| `--nv-glow-` | `sm md lg` (accent halos) · `blur-sm/md/lg` `spread-sm/md/lg` (geometry for a tinted glow) · `strength` (how much tint a glow carries) |
|
||||||
|
|
||||||
### Motion
|
### Motion
|
||||||
|
|
||||||
| Family | Steps |
|
| Family | Steps |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `--nv-duration-` | `instant fast quick normal slow slower travel expand theme` |
|
| `--nv-duration-` | `instant fast quick normal slow slower travel expand theme shimmer` |
|
||||||
| `--nv-ease-` | `standard entrance exit spring emphasized linear` |
|
| `--nv-ease-` | `standard entrance exit spring emphasized linear` |
|
||||||
| `--nv-transition-` | `colors depth control surface expand` (composites) |
|
| `--nv-transition-` | `colors depth control surface expand` (composites) |
|
||||||
| displacement | `--nv-lift-hover` `--nv-press-depth` `--nv-travel-sm/md/lg` |
|
| displacement | `--nv-lift-hover` `--nv-press-depth` `--nv-travel-sm/md/lg` |
|
||||||
|
| sequence | `--nv-stagger` — the gap between siblings arriving one after another |
|
||||||
|
|
||||||
`prefers-reduced-motion` collapses every duration and displacement at the token
|
`prefers-reduced-motion` collapses every duration and displacement at the token
|
||||||
level. Components never opt in individually.
|
level. Components never opt in individually.
|
||||||
|
|
@ -367,7 +450,9 @@ level. Components never opt in individually.
|
||||||
`--nv-border-width-strong` · `--nv-target-min` · `--nv-layout-gutter` ·
|
`--nv-border-width-strong` · `--nv-target-min` · `--nv-layout-gutter` ·
|
||||||
`--nv-layout-max-width` · `--nv-selection-name` · `--nv-anchor` ·
|
`--nv-layout-max-width` · `--nv-selection-name` · `--nv-anchor` ·
|
||||||
`--nv-button-offset` · `--nv-bed-tint` · `--nv-scroller-item` ·
|
`--nv-button-offset` · `--nv-bed-tint` · `--nv-scroller-item` ·
|
||||||
`--nv-scroller-fade` · `--nv-content-name`
|
`--nv-scroller-fade` · `--nv-content-name` · `--nv-card-tint` ·
|
||||||
|
`--nv-tile-tint` · `--nv-avatar-tint` · `--nv-pointer-x` / `--nv-pointer-y`
|
||||||
|
(set by `niveus.js`) · `--nv-reveal-index` (set by `niveus.js`)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -385,7 +470,7 @@ everything still renders — state changes simply arrive instead of travelling.
|
||||||
| `nv.selectTab` | `(tablist, tab) => Promise` | Moves selection and its panel, with direction. |
|
| `nv.selectTab` | `(tablist, tab) => Promise` | Moves selection and its panel, with direction. |
|
||||||
| `nv.initScroller` | `(frame) => void` | Wires one scroller's controls. |
|
| `nv.initScroller` | `(frame) => void` | Wires one scroller's controls. |
|
||||||
| `nv.liftLabels` | `(scope) => Element[]` | Names the navigation items in `scope` so a travelling marker passes behind them, and returns them. `nv.transition` does this for its `scope` and removes the names afterwards. |
|
| `nv.liftLabels` | `(scope) => Element[]` | Names the navigation items in `scope` so a travelling marker passes behind them, and returns them. `nv.transition` does this for its `scope` and removes the names afterwards. |
|
||||||
| `nv.wire` | `(scope = document) => void` | Applies all of the below. Runs once on load; call again after injecting markup. |
|
| `nv.wire` | `(scope = document) => void` | Applies all of the below, and starts watching `.nv-reveal` / `.nv-reveal-group`. Runs once on load; call again after injecting markup. |
|
||||||
| `nv.prefersReducedMotion` | `() => boolean` | |
|
| `nv.prefersReducedMotion` | `() => boolean` | |
|
||||||
|
|
||||||
### Data attributes
|
### Data attributes
|
||||||
|
|
@ -402,6 +487,7 @@ everything still renders — state changes simply arrive instead of travelling.
|
||||||
| `data-scrolled` | `.nv-nav` | Set by the application on scroll; gives the bar its shadow. |
|
| `data-scrolled` | `.nv-nav` | Set by the application on scroll; gives the bar its shadow. |
|
||||||
| `data-nv-direction` | `<html>` | Set during a transition by `nv.transition`. |
|
| `data-nv-direction` | `<html>` | Set during a transition by `nv.transition`. |
|
||||||
| `data-nv-transition` | `<html>` | `"theme"` during an appearance change. |
|
| `data-nv-transition` | `<html>` | `"theme"` during an appearance change. |
|
||||||
|
| `data-nv-reveal` | revealed element | `pending` while waiting to scroll into view, `shown` while arriving; removed once it has. Set by `niveus.js`. |
|
||||||
| `data-nv-travelling` | selection group | Set by `nv.transition` on each `scope` for the length of the change; names its marker. |
|
| `data-nv-travelling` | selection group | Set by `nv.transition` on each `scope` for the length of the change; names its marker. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
726
dist/niveus.css
vendored
726
dist/niveus.css
vendored
|
|
@ -302,6 +302,11 @@
|
||||||
--nv-color-sheen-strong-top: light-dark(var(--nv-light-sheen-strong-top), var(--nv-dark-sheen-strong-top));
|
--nv-color-sheen-strong-top: light-dark(var(--nv-light-sheen-strong-top), var(--nv-dark-sheen-strong-top));
|
||||||
--nv-color-sheen-strong-bottom: light-dark(var(--nv-light-sheen-strong-bottom), var(--nv-dark-sheen-strong-bottom));
|
--nv-color-sheen-strong-bottom: light-dark(var(--nv-light-sheen-strong-bottom), var(--nv-dark-sheen-strong-bottom));
|
||||||
|
|
||||||
|
/* Glow: light a surface gives off, as opposed to the shade it casts. It is
|
||||||
|
the accent seen through air — strong enough in dark to read as a lit
|
||||||
|
edge, faint in light, where paper does not emit. */
|
||||||
|
--nv-color-glow: light-dark(var(--nv-light-glow), var(--nv-dark-glow));
|
||||||
|
|
||||||
/* A knob: the thumb of a switch or a slider. It stays light in both
|
/* A knob: the thumb of a switch or a slider. It stays light in both
|
||||||
appearances, the way a physical knob does not change color with the room. */
|
appearances, the way a physical knob does not change color with the room. */
|
||||||
--nv-color-knob: light-dark(var(--nv-light-knob), var(--nv-dark-knob));
|
--nv-color-knob: light-dark(var(--nv-light-knob), var(--nv-dark-knob));
|
||||||
|
|
@ -598,6 +603,32 @@
|
||||||
--nv-focus-ring:
|
--nv-focus-ring:
|
||||||
0 0 0 1px var(--nv-color-focus-ring-contrast),
|
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);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@layer niveus.tokens {
|
@layer niveus.tokens {
|
||||||
|
|
@ -685,6 +716,15 @@
|
||||||
--nv-travel-sm: 4px;
|
--nv-travel-sm: 4px;
|
||||||
--nv-travel-md: 8px;
|
--nv-travel-md: 8px;
|
||||||
--nv-travel-lg: 16px;
|
--nv-travel-lg: 16px;
|
||||||
|
|
||||||
|
/* --- Sequence ---------------------------------------------------------------
|
||||||
|
The gap between siblings arriving one after another. Short enough that a
|
||||||
|
group reads as one movement, long enough that the eye sees an order. */
|
||||||
|
--nv-stagger: 60ms;
|
||||||
|
|
||||||
|
/* A loading shimmer is a slow, constant sweep — slow enough to read as
|
||||||
|
"waiting", never fast enough to read as an alert. */
|
||||||
|
--nv-duration-shimmer: 1600ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reduced motion is a system-wide contract, not a per-component opt-in: cut
|
/* Reduced motion is a system-wide contract, not a per-component opt-in: cut
|
||||||
|
|
@ -706,6 +746,7 @@
|
||||||
--nv-travel-sm: 0px;
|
--nv-travel-sm: 0px;
|
||||||
--nv-travel-md: 0px;
|
--nv-travel-md: 0px;
|
||||||
--nv-travel-lg: 0px;
|
--nv-travel-lg: 0px;
|
||||||
|
--nv-stagger: 0ms;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -965,6 +1006,7 @@
|
||||||
--nv-light-sheen-bottom: rgb(40 32 20 / 0.07);
|
--nv-light-sheen-bottom: rgb(40 32 20 / 0.07);
|
||||||
--nv-light-sheen-strong-top: rgb(255 255 255 / 0.32);
|
--nv-light-sheen-strong-top: rgb(255 255 255 / 0.32);
|
||||||
--nv-light-sheen-strong-bottom: rgb(20 16 10 / 0.16);
|
--nv-light-sheen-strong-bottom: rgb(20 16 10 / 0.16);
|
||||||
|
--nv-light-glow: oklch(from var(--nv-light-accent) l c h / 0.3);
|
||||||
|
|
||||||
--nv-light-knob: var(--nv-palette-neutral-0);
|
--nv-light-knob: var(--nv-palette-neutral-0);
|
||||||
--nv-light-knob-shade: rgb(40 32 20 / 0.13);
|
--nv-light-knob-shade: rgb(40 32 20 / 0.13);
|
||||||
|
|
@ -1126,6 +1168,7 @@
|
||||||
--nv-dark-sheen-bottom: rgb(0 0 0 / 0.18);
|
--nv-dark-sheen-bottom: rgb(0 0 0 / 0.18);
|
||||||
--nv-dark-sheen-strong-top: rgb(255 255 255 / 0.3);
|
--nv-dark-sheen-strong-top: rgb(255 255 255 / 0.3);
|
||||||
--nv-dark-sheen-strong-bottom: rgb(0 0 0 / 0.2);
|
--nv-dark-sheen-strong-bottom: rgb(0 0 0 / 0.2);
|
||||||
|
--nv-dark-glow: oklch(from var(--nv-dark-accent) l c h / 0.5);
|
||||||
|
|
||||||
--nv-dark-knob: #f4f3ef;
|
--nv-dark-knob: #f4f3ef;
|
||||||
--nv-dark-knob-shade: rgb(40 32 20 / 0.3);
|
--nv-dark-knob-shade: rgb(40 32 20 / 0.3);
|
||||||
|
|
@ -1364,6 +1407,35 @@
|
||||||
.nv-text-success { color: var(--nv-color-success-text); }
|
.nv-text-success { color: var(--nv-color-success-text); }
|
||||||
.nv-text-warning { color: var(--nv-color-warning-text); }
|
.nv-text-warning { color: var(--nv-color-warning-text); }
|
||||||
.nv-text-danger { color: var(--nv-color-danger-text); }
|
.nv-text-danger { color: var(--nv-color-danger-text); }
|
||||||
|
|
||||||
|
/* Lit text: type cut from the same material as the surfaces — bright along
|
||||||
|
the top, warming towards the accent below, the way light falls across a
|
||||||
|
raised face. Each line is lit on its own. For one display line on a page,
|
||||||
|
not for running text. */
|
||||||
|
.nv-text-lit {
|
||||||
|
background-image: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
var(--nv-color-text-primary) 30%,
|
||||||
|
color-mix(in oklab, var(--nv-color-accent-text) 30%, var(--nv-color-text-primary))
|
||||||
|
);
|
||||||
|
-webkit-box-decoration-break: clone;
|
||||||
|
box-decoration-break: clone;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
color: var(--nv-color-accent-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The accent alone, lit from above: for an accented phrase inside a display
|
||||||
|
line, where flat accent text would read as a link. */
|
||||||
|
.nv-text-lit--accent {
|
||||||
|
background-image: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
color-mix(in oklab, var(--nv-color-accent-text) 55%, var(--nv-color-text-primary)),
|
||||||
|
var(--nv-color-accent-text) 60%,
|
||||||
|
var(--nv-color-accent)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Continuity: how one state becomes the next ----------------------------- */
|
/* --- Continuity: how one state becomes the next ----------------------------- */
|
||||||
|
|
@ -1570,6 +1642,33 @@
|
||||||
view-transition-class: nv-content;
|
view-transition-class: nv-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Reveal ------------------------------------------------------------------------
|
||||||
|
Content arriving as it scrolls into view: it rises a short way and fades in,
|
||||||
|
once, so a long page is read in the order it was written instead of all at
|
||||||
|
once. .nv-reveal-group does the same for its children one after another,
|
||||||
|
--nv-stagger apart.
|
||||||
|
|
||||||
|
niveus.js marks what is waiting. Without the script, and under reduced
|
||||||
|
motion, nothing is ever marked — so nothing is ever hidden. */
|
||||||
|
|
||||||
|
.nv-reveal[data-nv-reveal="pending"],
|
||||||
|
.nv-reveal-group > [data-nv-reveal="pending"] {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-reveal[data-nv-reveal="shown"],
|
||||||
|
.nv-reveal-group > [data-nv-reveal="shown"] {
|
||||||
|
animation: nv-arrive var(--nv-duration-slower) var(--nv-ease-entrance)
|
||||||
|
calc(var(--nv-reveal-index, 0) * var(--nv-stagger)) both;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes nv-arrive {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
translate: 0 var(--nv-travel-lg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Collapsible region for markup that is not a <details>. The 0fr -> 1fr grid
|
/* Collapsible region for markup that is not a <details>. The 0fr -> 1fr grid
|
||||||
row is the one technique that animates to an unknown content height in every
|
row is the one technique that animates to an unknown content height in every
|
||||||
browser, with no measurement and no script. */
|
browser, with no measurement and no script. */
|
||||||
|
|
@ -2473,6 +2572,9 @@
|
||||||
.nv-card--inset recessed into its parent
|
.nv-card--inset recessed into its parent
|
||||||
.nv-card--frosted over imagery or scrolling content
|
.nv-card--frosted over imagery or scrolling content
|
||||||
.nv-card--interactive the whole card is one target
|
.nv-card--interactive the whole card is one target
|
||||||
|
.nv-card--lit lit along its top edge in its tint
|
||||||
|
.nv-card--glow gives off its tint — the one card that is on
|
||||||
|
.nv-card--spotlight catches a light that follows the pointer
|
||||||
.nv-card__header / __body / __footer / __media
|
.nv-card__header / __body / __footer / __media
|
||||||
|
|
||||||
A card is a container, not a look. If a card needs a shadow that is not on
|
A card is a container, not a look. If a card needs a shadow that is not on
|
||||||
|
|
@ -2490,6 +2592,11 @@
|
||||||
--_shadow: var(--nv-bevel), var(--nv-outline), var(--nv-elevation-1);
|
--_shadow: var(--nv-bevel), var(--nv-outline), var(--nv-elevation-1);
|
||||||
--_sheen: var(--nv-sheen-surface);
|
--_sheen: var(--nv-sheen-surface);
|
||||||
--_padding: var(--nv-space-5);
|
--_padding: var(--nv-space-5);
|
||||||
|
/* Light layers the variants switch on. Kept separate from --_shadow so a
|
||||||
|
lit or glowing card can still be elevated, floating or interactive. */
|
||||||
|
--_lit: 0 0 transparent;
|
||||||
|
--_glow: 0 0 transparent;
|
||||||
|
--_spot: linear-gradient(transparent, transparent);
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -2497,11 +2604,11 @@
|
||||||
min-inline-size: 0;
|
min-inline-size: 0;
|
||||||
|
|
||||||
background-color: var(--_bg);
|
background-color: var(--_bg);
|
||||||
background-image: var(--_sheen);
|
background-image: var(--_spot), var(--_sheen);
|
||||||
color: var(--nv-color-text-primary);
|
color: var(--nv-color-text-primary);
|
||||||
border: var(--nv-border-width) solid var(--_border);
|
border: var(--nv-border-width) solid var(--_border);
|
||||||
border-radius: var(--nv-radius-lg);
|
border-radius: var(--nv-radius-lg);
|
||||||
box-shadow: var(--_shadow);
|
box-shadow: var(--_lit), var(--_shadow), var(--_glow);
|
||||||
backdrop-filter: var(--nv-backdrop-glass);
|
backdrop-filter: var(--nv-backdrop-glass);
|
||||||
-webkit-backdrop-filter: var(--nv-backdrop-glass);
|
-webkit-backdrop-filter: var(--nv-backdrop-glass);
|
||||||
|
|
||||||
|
|
@ -2553,20 +2660,75 @@
|
||||||
|
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
.nv-card--interactive:hover {
|
.nv-card--interactive:hover {
|
||||||
box-shadow: var(--nv-bevel), var(--nv-outline), var(--nv-elevation-3);
|
box-shadow: var(--_lit), var(--nv-bevel), var(--nv-outline), var(--nv-elevation-3), var(--_glow);
|
||||||
translate: 0 calc(var(--nv-lift-hover) * 2);
|
translate: 0 calc(var(--nv-lift-hover) * 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nv-card--interactive:active {
|
.nv-card--interactive:active {
|
||||||
box-shadow: var(--nv-bevel), var(--nv-outline), var(--nv-elevation-1);
|
box-shadow: var(--_lit), var(--nv-bevel), var(--nv-outline), var(--nv-elevation-1), var(--_glow);
|
||||||
translate: 0 var(--nv-press-depth);
|
translate: 0 var(--nv-press-depth);
|
||||||
transition-duration: var(--nv-duration-instant);
|
transition-duration: var(--nv-duration-instant);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nv-card--interactive:focus-visible {
|
.nv-card--interactive:focus-visible {
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: var(--_shadow), var(--nv-focus-ring);
|
box-shadow: var(--_lit), var(--_shadow), var(--_glow), var(--nv-focus-ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Light ------------------------------------------------------------------
|
||||||
|
Three ways a card can carry light, each stated once and combinable with
|
||||||
|
every elevation above. They all take the card's tint — the accent unless
|
||||||
|
--nv-card-tint names another color, the way --nv-bed-tint does for a bed.
|
||||||
|
|
||||||
|
Lit: a strip of light along the top edge, spilling a little way down
|
||||||
|
the face. For a card that belongs to something with a color: a
|
||||||
|
project, a status, a category.
|
||||||
|
Glow: the card gives off its tint. For the one card that is on — the
|
||||||
|
current plan, the live item. Two glowing cards side by side
|
||||||
|
cancel each other out.
|
||||||
|
Spotlight: a soft light follows the pointer across the face (niveus.js
|
||||||
|
tracks it). A showcase effect; a list of work items does not
|
||||||
|
need one. */
|
||||||
|
|
||||||
|
.nv-card--lit,
|
||||||
|
.nv-card--glow,
|
||||||
|
.nv-card--spotlight {
|
||||||
|
--nv-card-tint: var(--nv-color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-card--lit {
|
||||||
|
--_lit:
|
||||||
|
inset 0 2px 0 color-mix(in oklab, var(--nv-card-tint) 85%, transparent),
|
||||||
|
inset 0 18px 28px -22px color-mix(in oklab, var(--nv-card-tint) 70%, transparent);
|
||||||
|
--_sheen:
|
||||||
|
radial-gradient(
|
||||||
|
120% 70% at 0% 0%,
|
||||||
|
color-mix(in oklab, var(--nv-card-tint) 12%, transparent),
|
||||||
|
transparent 70%
|
||||||
|
),
|
||||||
|
var(--nv-sheen-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-card--glow {
|
||||||
|
--_border: color-mix(in oklab, var(--nv-card-tint) 40%, var(--nv-color-border-subtle));
|
||||||
|
--_glow: 0 0 var(--nv-glow-blur-md) var(--nv-glow-spread-md)
|
||||||
|
color-mix(in oklab, var(--nv-card-tint) var(--nv-glow-strength), transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: hover) {
|
||||||
|
.nv-card--glow.nv-card--interactive:hover {
|
||||||
|
--_glow: 0 0 var(--nv-glow-blur-lg) var(--nv-glow-spread-md)
|
||||||
|
color-mix(in oklab, var(--nv-card-tint) var(--nv-glow-strength), transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-card--spotlight:hover {
|
||||||
|
--_spot: radial-gradient(
|
||||||
|
22rem circle at var(--nv-pointer-x, 50%) var(--nv-pointer-y, 0%),
|
||||||
|
color-mix(in oklab, var(--nv-card-tint) 14%, var(--nv-color-sheen-top)),
|
||||||
|
transparent 70%
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Cards whose whole area is clickable via a link inside: the link's target is
|
/* Cards whose whole area is clickable via a link inside: the link's target is
|
||||||
|
|
@ -4164,6 +4326,560 @@
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@layer niveus.components {
|
||||||
|
/* ==========================================================================
|
||||||
|
Niveus — Tile
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
<span class="nv-tile"><svg …></svg></span>
|
||||||
|
|
||||||
|
A tile is a small raised key that holds one glyph: the icon beside a
|
||||||
|
feature, the mark of a product, the symbol on a stat. It is the physical
|
||||||
|
version of an icon — something with an edge you could press — which is why
|
||||||
|
a row of features reads as a set of objects rather than as clip art.
|
||||||
|
|
||||||
|
.nv-tile soft key: tinted face, the glyph in its tint
|
||||||
|
.nv-tile--solid filled key: saturated face, a light glyph
|
||||||
|
.nv-tile--glow the key is lit from inside
|
||||||
|
.nv-tile--sm / --lg / --xl
|
||||||
|
|
||||||
|
Everything derives from --nv-tile-tint, the accent unless set. A tile is
|
||||||
|
not a button: if it does something, it is an .nv-button--icon.
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
.nv-tile {
|
||||||
|
--nv-tile-tint: var(--nv-color-accent);
|
||||||
|
--_size: 2.75rem;
|
||||||
|
--_radius: var(--nv-radius-md);
|
||||||
|
--_bg: color-mix(in oklab, var(--nv-tile-tint) 16%, var(--nv-color-surface-elevated));
|
||||||
|
--_ink: color-mix(in oklab, var(--nv-tile-tint) 70%, var(--nv-color-text-primary));
|
||||||
|
--_sheen: var(--nv-sheen-raised);
|
||||||
|
--_glow: 0 0 transparent;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
display: inline-grid;
|
||||||
|
place-items: center;
|
||||||
|
flex: none;
|
||||||
|
inline-size: var(--_size);
|
||||||
|
block-size: var(--_size);
|
||||||
|
|
||||||
|
background-color: var(--_bg);
|
||||||
|
background-image: var(--_sheen);
|
||||||
|
color: var(--_ink);
|
||||||
|
border: var(--nv-border-width) solid color-mix(in oklab, var(--nv-tile-tint) 30%, var(--nv-color-border-subtle));
|
||||||
|
border-radius: var(--_radius);
|
||||||
|
box-shadow: var(--nv-bevel), var(--nv-outline), var(--nv-elevation-1), var(--_glow);
|
||||||
|
transition: var(--nv-transition-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Glyphs take half the key; a mark that is itself a picture (an image, a
|
||||||
|
logo) fills it, clipped to the key's corners. */
|
||||||
|
.nv-tile > svg {
|
||||||
|
inline-size: 50%;
|
||||||
|
block-size: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-tile > img {
|
||||||
|
inline-size: 100%;
|
||||||
|
block-size: 100%;
|
||||||
|
border-radius: inherit;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-tile--solid {
|
||||||
|
--_bg: var(--nv-tile-tint);
|
||||||
|
--_ink: var(--nv-color-on-accent);
|
||||||
|
--_sheen: var(--nv-sheen-filled);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-tile--glow {
|
||||||
|
--_glow: 0 0 var(--nv-glow-blur-md) var(--nv-glow-spread-sm)
|
||||||
|
color-mix(in oklab, var(--nv-tile-tint) var(--nv-glow-strength), transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-tile--sm {
|
||||||
|
--_size: 2rem;
|
||||||
|
--_radius: var(--nv-radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-tile--lg {
|
||||||
|
--_size: 3.5rem;
|
||||||
|
--_radius: var(--nv-radius-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-tile--xl {
|
||||||
|
--_size: 4.5rem;
|
||||||
|
--_radius: var(--nv-radius-xl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@layer niveus.components {
|
||||||
|
/* ==========================================================================
|
||||||
|
Niveus — Avatar
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
<span class="nv-avatar"><img src="…" alt=""></span>
|
||||||
|
<span class="nv-avatar">NX</span>
|
||||||
|
|
||||||
|
A person, as a round raised disc: an image when there is one, initials when
|
||||||
|
there is not. The disc is lit like a knob — highlight above, shade below —
|
||||||
|
so a face reads as something set into the interface, not pasted onto it.
|
||||||
|
|
||||||
|
.nv-avatar--ring a ring in the tint, set off by a gap of surface
|
||||||
|
.nv-avatar--glow the ring gives off its tint
|
||||||
|
.nv-avatar--sm / --lg / --xl
|
||||||
|
.nv-avatar__status presence dot; --online lights it
|
||||||
|
.nv-avatar-group overlapping row of avatars
|
||||||
|
|
||||||
|
--nv-avatar-tint picks the ring color; the accent unless set.
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
.nv-avatar {
|
||||||
|
--nv-avatar-tint: var(--nv-color-accent);
|
||||||
|
--_size: 2.5rem;
|
||||||
|
--_ring: 0 0 transparent;
|
||||||
|
--_glow: 0 0 transparent;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
display: inline-grid;
|
||||||
|
place-items: center;
|
||||||
|
flex: none;
|
||||||
|
inline-size: var(--_size);
|
||||||
|
block-size: var(--_size);
|
||||||
|
|
||||||
|
background-color: var(--nv-color-surface-elevated);
|
||||||
|
background-image: var(--nv-sheen-raised);
|
||||||
|
color: var(--nv-color-text-secondary);
|
||||||
|
font: var(--nv-font-label);
|
||||||
|
font-weight: var(--nv-font-weight-semibold);
|
||||||
|
border-radius: var(--nv-radius-full);
|
||||||
|
box-shadow: var(--_ring), var(--nv-bevel), var(--nv-outline), var(--nv-elevation-1), var(--_glow);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-avatar > img {
|
||||||
|
inline-size: 100%;
|
||||||
|
block-size: 100%;
|
||||||
|
border-radius: inherit;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The gap is drawn in the surface color, so the ring reads as a separate band
|
||||||
|
sitting around the disc rather than as a thick border painted onto it. */
|
||||||
|
.nv-avatar--ring,
|
||||||
|
.nv-avatar--glow {
|
||||||
|
--_ring:
|
||||||
|
0 0 0 2px var(--nv-color-surface),
|
||||||
|
0 0 0 4px var(--nv-avatar-tint);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-avatar--glow {
|
||||||
|
--_glow: 0 0 var(--nv-glow-blur-md) var(--nv-glow-spread-sm)
|
||||||
|
color-mix(in oklab, var(--nv-avatar-tint) var(--nv-glow-strength), transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-avatar--sm {
|
||||||
|
--_size: 1.75rem;
|
||||||
|
font: var(--nv-font-caption);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-avatar--lg {
|
||||||
|
--_size: 4rem;
|
||||||
|
font: var(--nv-font-title);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-avatar--xl {
|
||||||
|
--_size: 6rem;
|
||||||
|
font: var(--nv-font-heading);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Status ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
.nv-avatar__status {
|
||||||
|
position: absolute;
|
||||||
|
inset-block-end: 4%;
|
||||||
|
inset-inline-end: 4%;
|
||||||
|
inline-size: max(0.625rem, 24%);
|
||||||
|
block-size: max(0.625rem, 24%);
|
||||||
|
border-radius: var(--nv-radius-full);
|
||||||
|
background-color: var(--nv-color-text-tertiary);
|
||||||
|
box-shadow: 0 0 0 2px var(--nv-color-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-avatar__status--online {
|
||||||
|
background-color: var(--nv-color-success);
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 2px var(--nv-color-surface),
|
||||||
|
0 0 var(--nv-glow-blur-sm) var(--nv-glow-spread-sm)
|
||||||
|
color-mix(in oklab, var(--nv-color-success) var(--nv-glow-strength), transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Group ----------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
.nv-avatar-group {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-avatar-group > .nv-avatar {
|
||||||
|
box-shadow: 0 0 0 2px var(--nv-color-surface), var(--nv-bevel), var(--nv-elevation-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-avatar-group > .nv-avatar + .nv-avatar {
|
||||||
|
margin-inline-start: calc(var(--_size) * -0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@layer niveus.components {
|
||||||
|
/* ==========================================================================
|
||||||
|
Niveus — Stat
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
<dl class="nv-stat-group">
|
||||||
|
<div class="nv-stat">
|
||||||
|
<dt class="nv-stat__label">Servers</dt>
|
||||||
|
<dd class="nv-stat__value">1,204</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
A number the user is meant to read at a glance. A group of stats is set into
|
||||||
|
one shallow well, like the readout panel of an instrument: the figures sit
|
||||||
|
behind the surface, separated by hairlines, so a row of numbers reads as one
|
||||||
|
display instead of as four loose labels.
|
||||||
|
|
||||||
|
.nv-stat-group the readout well; wraps without a media query
|
||||||
|
.nv-stat one reading
|
||||||
|
.nv-stat__label what is measured
|
||||||
|
.nv-stat__value the figure, in tabular numerals
|
||||||
|
.nv-stat__delta change since last time; --up / --down
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
.nv-stat-group {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(min(8rem, 100%), 1fr));
|
||||||
|
/* The hairlines between readings are the well itself showing through a
|
||||||
|
one-pixel gap, so they stay continuous however the row wraps. */
|
||||||
|
gap: var(--nv-border-width);
|
||||||
|
margin: 0;
|
||||||
|
background-color: var(--nv-color-border-subtle);
|
||||||
|
border: var(--nv-border-width) solid var(--nv-color-border-subtle);
|
||||||
|
border-radius: var(--nv-radius-lg);
|
||||||
|
box-shadow: var(--nv-elevation-inset-md), var(--nv-ledge);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-stat {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--nv-space-1);
|
||||||
|
min-inline-size: 0;
|
||||||
|
padding: var(--nv-space-4) var(--nv-space-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-stat-group > .nv-stat {
|
||||||
|
background-color: var(--nv-color-surface-inset);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-stat__label {
|
||||||
|
font: var(--nv-font-caption);
|
||||||
|
color: var(--nv-color-text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-stat__value {
|
||||||
|
margin: 0;
|
||||||
|
font: var(--nv-font-heading);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
letter-spacing: var(--nv-letter-spacing-tight);
|
||||||
|
color: var(--nv-color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-stat__delta {
|
||||||
|
font: var(--nv-font-caption);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
color: var(--nv-color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-stat__delta--up {
|
||||||
|
color: var(--nv-color-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-stat__delta--down {
|
||||||
|
color: var(--nv-color-danger);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@layer niveus.components {
|
||||||
|
/* ==========================================================================
|
||||||
|
Niveus — Steps
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
<ol class="nv-steps">
|
||||||
|
<li class="nv-steps__item" data-state="done">
|
||||||
|
<span class="nv-steps__marker">1</span>
|
||||||
|
<div class="nv-steps__body">
|
||||||
|
<h3 class="nv-steps__title">Listen</h3>
|
||||||
|
<p class="nv-steps__text">…</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
A sequence the user moves through: a process, an onboarding, an order's
|
||||||
|
progress. The steps sit on a rail — a groove cut into the surface — with
|
||||||
|
each marker a raised knob set into it. Finished steps light their stretch
|
||||||
|
of the rail, so progress reads as something filling up rather than as a
|
||||||
|
row of ticks.
|
||||||
|
|
||||||
|
.nv-steps horizontal; becomes vertical on narrow screens
|
||||||
|
.nv-steps--vertical always vertical
|
||||||
|
data-state="done" the step and its stretch of rail are lit
|
||||||
|
aria-current="step" the step in progress: lit marker, rail still dark
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
.nv-steps {
|
||||||
|
--_marker: 2.25rem;
|
||||||
|
--_rail: 6px;
|
||||||
|
--_gap: var(--nv-space-4);
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-auto-flow: column;
|
||||||
|
grid-auto-columns: minmax(0, 1fr);
|
||||||
|
gap: var(--_gap);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-steps__item {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--nv-space-3);
|
||||||
|
min-inline-size: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Each step draws the rail from its own marker to the next one, so a finished
|
||||||
|
step can light exactly its stretch and the rail never runs past the end. */
|
||||||
|
.nv-steps__item:not(:last-child)::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset-block-start: calc((var(--_marker) - var(--_rail)) / 2);
|
||||||
|
inset-inline-start: calc(var(--_marker) / 2);
|
||||||
|
inline-size: calc(100% + var(--_gap));
|
||||||
|
block-size: var(--_rail);
|
||||||
|
border-radius: var(--nv-radius-full);
|
||||||
|
background-color: var(--nv-color-surface-inset);
|
||||||
|
box-shadow: var(--nv-elevation-inset-sm), var(--nv-ledge);
|
||||||
|
transition: var(--nv-transition-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-steps__item[data-state="done"]:not(:last-child)::before {
|
||||||
|
background-color: var(--nv-color-accent);
|
||||||
|
background-image: var(--nv-sheen-filled);
|
||||||
|
box-shadow: var(--nv-glow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-steps__marker {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
flex: none;
|
||||||
|
inline-size: var(--_marker);
|
||||||
|
block-size: var(--_marker);
|
||||||
|
|
||||||
|
background-color: var(--nv-color-surface-elevated);
|
||||||
|
background-image: var(--nv-sheen-raised);
|
||||||
|
color: var(--nv-color-text-secondary);
|
||||||
|
font: var(--nv-font-label);
|
||||||
|
font-weight: var(--nv-font-weight-semibold);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
border-radius: var(--nv-radius-full);
|
||||||
|
box-shadow: var(--nv-bevel), var(--nv-outline), var(--nv-elevation-2);
|
||||||
|
transition: var(--nv-transition-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-steps__item[data-state="done"] > .nv-steps__marker,
|
||||||
|
.nv-steps__item[aria-current="step"] > .nv-steps__marker {
|
||||||
|
background-color: var(--nv-color-accent);
|
||||||
|
background-image: var(--nv-sheen-filled);
|
||||||
|
color: var(--nv-color-on-accent);
|
||||||
|
box-shadow: var(--nv-bevel), var(--nv-outline), var(--nv-elevation-2), var(--nv-glow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-steps__body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--nv-space-1);
|
||||||
|
min-inline-size: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-steps__title {
|
||||||
|
margin: 0;
|
||||||
|
font: var(--nv-font-title);
|
||||||
|
letter-spacing: var(--nv-letter-spacing-tight);
|
||||||
|
color: var(--nv-color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-steps__text {
|
||||||
|
margin: 0;
|
||||||
|
font: var(--nv-font-caption);
|
||||||
|
color: var(--nv-color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Vertical -------------------------------------------------------------------
|
||||||
|
Marker beside the text and the rail running down between markers. Also what
|
||||||
|
a horizontal sequence becomes once its columns are too narrow to read. */
|
||||||
|
|
||||||
|
.nv-steps--vertical {
|
||||||
|
grid-auto-flow: row;
|
||||||
|
grid-auto-columns: auto;
|
||||||
|
--_gap: var(--nv-space-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-steps--vertical > .nv-steps__item {
|
||||||
|
flex-direction: row;
|
||||||
|
gap: var(--nv-space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-steps--vertical > .nv-steps__item:not(:last-child)::before {
|
||||||
|
inset-block-start: calc(var(--_marker) / 2);
|
||||||
|
inset-inline-start: calc((var(--_marker) - var(--_rail)) / 2);
|
||||||
|
inline-size: var(--_rail);
|
||||||
|
block-size: calc(100% + var(--_gap));
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 40rem) {
|
||||||
|
.nv-steps {
|
||||||
|
grid-auto-flow: row;
|
||||||
|
grid-auto-columns: auto;
|
||||||
|
--_gap: var(--nv-space-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-steps > .nv-steps__item {
|
||||||
|
flex-direction: row;
|
||||||
|
gap: var(--nv-space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-steps > .nv-steps__item:not(:last-child)::before {
|
||||||
|
inset-block-start: calc(var(--_marker) / 2);
|
||||||
|
inset-inline-start: calc((var(--_marker) - var(--_rail)) / 2);
|
||||||
|
inline-size: var(--_rail);
|
||||||
|
block-size: calc(100% + var(--_gap));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@layer niveus.components {
|
||||||
|
/* ==========================================================================
|
||||||
|
Niveus — Deck
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
<div class="nv-deck">
|
||||||
|
<article class="nv-card">…</article>
|
||||||
|
<div class="nv-deck__layer" aria-hidden="true"></div>
|
||||||
|
<div class="nv-deck__layer" aria-hidden="true"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
One surface in front of others of its kind: the current item of a set that
|
||||||
|
has more behind it. The layers are cards seen edge-on, stepping back and
|
||||||
|
dimming with distance, so the set has depth without anything having to
|
||||||
|
move. Use it where "there are more of these" is part of the message — a
|
||||||
|
showcase of products, a stack of notifications, a carousel at rest.
|
||||||
|
|
||||||
|
Everything in the deck shares one grid cell. The front surface is any
|
||||||
|
element; the layers are decorative and hidden from assistive technology.
|
||||||
|
Panels that swap (an .nv-tab-panel set) can live in the deck together:
|
||||||
|
the hidden ones take no space.
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
.nv-deck {
|
||||||
|
--_step: var(--nv-space-3);
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
position: relative;
|
||||||
|
isolation: isolate;
|
||||||
|
/* Room above the front surface for the layers that peek out behind it. */
|
||||||
|
padding-block-start: calc(var(--_step) * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-deck > * {
|
||||||
|
grid-area: 1 / 1;
|
||||||
|
min-inline-size: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-deck__layer {
|
||||||
|
--_depth: 1;
|
||||||
|
|
||||||
|
z-index: -1;
|
||||||
|
pointer-events: none;
|
||||||
|
background-color: color-mix(in oklab, var(--nv-color-surface) 90%, transparent);
|
||||||
|
background-image: var(--nv-sheen-surface);
|
||||||
|
border: var(--nv-border-width) solid var(--nv-color-border-subtle);
|
||||||
|
border-radius: var(--nv-radius-lg);
|
||||||
|
box-shadow: var(--nv-bevel), var(--nv-outline), var(--nv-elevation-1);
|
||||||
|
translate: 0 calc(var(--_step) * var(--_depth) * -1);
|
||||||
|
scale: calc(1 - var(--_depth) * 0.05) 1;
|
||||||
|
opacity: calc(1 - var(--_depth) * 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-deck__layer + .nv-deck__layer {
|
||||||
|
--_depth: 2;
|
||||||
|
z-index: -2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@layer niveus.components {
|
||||||
|
/* ==========================================================================
|
||||||
|
Niveus — Skeleton
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
<span class="nv-skeleton nv-skeleton--text"></span>
|
||||||
|
|
||||||
|
The shape of content that has not arrived yet. A skeleton is a shallow well
|
||||||
|
cut where the content will sit, with a slow band of light crossing it — the
|
||||||
|
surface is recessed and waiting, not broken.
|
||||||
|
|
||||||
|
.nv-skeleton a block; size it with the layout
|
||||||
|
.nv-skeleton--text one line of body text
|
||||||
|
.nv-skeleton--circle an avatar or a tile
|
||||||
|
|
||||||
|
Under reduced motion the light stops crossing and the well stays: the state
|
||||||
|
is still legible, it just does not move.
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
.nv-skeleton {
|
||||||
|
display: block;
|
||||||
|
min-block-size: var(--nv-space-4);
|
||||||
|
border-radius: var(--nv-radius-md);
|
||||||
|
background-color: var(--nv-color-surface-inset);
|
||||||
|
background-image: linear-gradient(
|
||||||
|
100deg,
|
||||||
|
transparent 30%,
|
||||||
|
var(--nv-color-sheen-top) 50%,
|
||||||
|
transparent 70%
|
||||||
|
);
|
||||||
|
background-size: 220% 100%;
|
||||||
|
box-shadow: var(--nv-elevation-inset-sm), var(--nv-ledge);
|
||||||
|
animation: nv-shimmer var(--nv-duration-shimmer) var(--nv-ease-linear) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-skeleton--text {
|
||||||
|
inline-size: 100%;
|
||||||
|
min-block-size: 0;
|
||||||
|
block-size: 0.75em;
|
||||||
|
border-radius: var(--nv-radius-full);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A paragraph of skeleton lines ends short, like the paragraph it stands for. */
|
||||||
|
.nv-skeleton--text:last-child:not(:first-child) {
|
||||||
|
inline-size: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-skeleton--circle {
|
||||||
|
aspect-ratio: 1;
|
||||||
|
border-radius: var(--nv-radius-full);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes nv-shimmer {
|
||||||
|
from { background-position: 100% 0; }
|
||||||
|
to { background-position: -120% 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.nv-skeleton {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* --- Utilities ----------------------------------------------------------------- */
|
/* --- Utilities ----------------------------------------------------------------- */
|
||||||
@layer niveus.utilities {
|
@layer niveus.utilities {
|
||||||
|
|
|
||||||
|
|
@ -204,3 +204,32 @@ textarea {
|
||||||
.nv-text-success { color: var(--nv-color-success-text); }
|
.nv-text-success { color: var(--nv-color-success-text); }
|
||||||
.nv-text-warning { color: var(--nv-color-warning-text); }
|
.nv-text-warning { color: var(--nv-color-warning-text); }
|
||||||
.nv-text-danger { color: var(--nv-color-danger-text); }
|
.nv-text-danger { color: var(--nv-color-danger-text); }
|
||||||
|
|
||||||
|
/* Lit text: type cut from the same material as the surfaces — bright along
|
||||||
|
the top, warming towards the accent below, the way light falls across a
|
||||||
|
raised face. Each line is lit on its own. For one display line on a page,
|
||||||
|
not for running text. */
|
||||||
|
.nv-text-lit {
|
||||||
|
background-image: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
var(--nv-color-text-primary) 30%,
|
||||||
|
color-mix(in oklab, var(--nv-color-accent-text) 30%, var(--nv-color-text-primary))
|
||||||
|
);
|
||||||
|
-webkit-box-decoration-break: clone;
|
||||||
|
box-decoration-break: clone;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
color: var(--nv-color-accent-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The accent alone, lit from above: for an accented phrase inside a display
|
||||||
|
line, where flat accent text would read as a link. */
|
||||||
|
.nv-text-lit--accent {
|
||||||
|
background-image: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
color-mix(in oklab, var(--nv-color-accent-text) 55%, var(--nv-color-text-primary)),
|
||||||
|
var(--nv-color-accent-text) 60%,
|
||||||
|
var(--nv-color-accent)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
113
niveus/components/avatar.css
Normal file
113
niveus/components/avatar.css
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
/* ==========================================================================
|
||||||
|
Niveus — Avatar
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
<span class="nv-avatar"><img src="…" alt=""></span>
|
||||||
|
<span class="nv-avatar">NX</span>
|
||||||
|
|
||||||
|
A person, as a round raised disc: an image when there is one, initials when
|
||||||
|
there is not. The disc is lit like a knob — highlight above, shade below —
|
||||||
|
so a face reads as something set into the interface, not pasted onto it.
|
||||||
|
|
||||||
|
.nv-avatar--ring a ring in the tint, set off by a gap of surface
|
||||||
|
.nv-avatar--glow the ring gives off its tint
|
||||||
|
.nv-avatar--sm / --lg / --xl
|
||||||
|
.nv-avatar__status presence dot; --online lights it
|
||||||
|
.nv-avatar-group overlapping row of avatars
|
||||||
|
|
||||||
|
--nv-avatar-tint picks the ring color; the accent unless set.
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
.nv-avatar {
|
||||||
|
--nv-avatar-tint: var(--nv-color-accent);
|
||||||
|
--_size: 2.5rem;
|
||||||
|
--_ring: 0 0 transparent;
|
||||||
|
--_glow: 0 0 transparent;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
display: inline-grid;
|
||||||
|
place-items: center;
|
||||||
|
flex: none;
|
||||||
|
inline-size: var(--_size);
|
||||||
|
block-size: var(--_size);
|
||||||
|
|
||||||
|
background-color: var(--nv-color-surface-elevated);
|
||||||
|
background-image: var(--nv-sheen-raised);
|
||||||
|
color: var(--nv-color-text-secondary);
|
||||||
|
font: var(--nv-font-label);
|
||||||
|
font-weight: var(--nv-font-weight-semibold);
|
||||||
|
border-radius: var(--nv-radius-full);
|
||||||
|
box-shadow: var(--_ring), var(--nv-bevel), var(--nv-outline), var(--nv-elevation-1), var(--_glow);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-avatar > img {
|
||||||
|
inline-size: 100%;
|
||||||
|
block-size: 100%;
|
||||||
|
border-radius: inherit;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The gap is drawn in the surface color, so the ring reads as a separate band
|
||||||
|
sitting around the disc rather than as a thick border painted onto it. */
|
||||||
|
.nv-avatar--ring,
|
||||||
|
.nv-avatar--glow {
|
||||||
|
--_ring:
|
||||||
|
0 0 0 2px var(--nv-color-surface),
|
||||||
|
0 0 0 4px var(--nv-avatar-tint);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-avatar--glow {
|
||||||
|
--_glow: 0 0 var(--nv-glow-blur-md) var(--nv-glow-spread-sm)
|
||||||
|
color-mix(in oklab, var(--nv-avatar-tint) var(--nv-glow-strength), transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-avatar--sm {
|
||||||
|
--_size: 1.75rem;
|
||||||
|
font: var(--nv-font-caption);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-avatar--lg {
|
||||||
|
--_size: 4rem;
|
||||||
|
font: var(--nv-font-title);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-avatar--xl {
|
||||||
|
--_size: 6rem;
|
||||||
|
font: var(--nv-font-heading);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Status ------------------------------------------------------------------ */
|
||||||
|
|
||||||
|
.nv-avatar__status {
|
||||||
|
position: absolute;
|
||||||
|
inset-block-end: 4%;
|
||||||
|
inset-inline-end: 4%;
|
||||||
|
inline-size: max(0.625rem, 24%);
|
||||||
|
block-size: max(0.625rem, 24%);
|
||||||
|
border-radius: var(--nv-radius-full);
|
||||||
|
background-color: var(--nv-color-text-tertiary);
|
||||||
|
box-shadow: 0 0 0 2px var(--nv-color-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-avatar__status--online {
|
||||||
|
background-color: var(--nv-color-success);
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 2px var(--nv-color-surface),
|
||||||
|
0 0 var(--nv-glow-blur-sm) var(--nv-glow-spread-sm)
|
||||||
|
color-mix(in oklab, var(--nv-color-success) var(--nv-glow-strength), transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Group ----------------------------------------------------------------------- */
|
||||||
|
|
||||||
|
.nv-avatar-group {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-avatar-group > .nv-avatar {
|
||||||
|
box-shadow: 0 0 0 2px var(--nv-color-surface), var(--nv-bevel), var(--nv-elevation-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-avatar-group > .nv-avatar + .nv-avatar {
|
||||||
|
margin-inline-start: calc(var(--_size) * -0.3);
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,9 @@
|
||||||
.nv-card--inset recessed into its parent
|
.nv-card--inset recessed into its parent
|
||||||
.nv-card--frosted over imagery or scrolling content
|
.nv-card--frosted over imagery or scrolling content
|
||||||
.nv-card--interactive the whole card is one target
|
.nv-card--interactive the whole card is one target
|
||||||
|
.nv-card--lit lit along its top edge in its tint
|
||||||
|
.nv-card--glow gives off its tint — the one card that is on
|
||||||
|
.nv-card--spotlight catches a light that follows the pointer
|
||||||
.nv-card__header / __body / __footer / __media
|
.nv-card__header / __body / __footer / __media
|
||||||
|
|
||||||
A card is a container, not a look. If a card needs a shadow that is not on
|
A card is a container, not a look. If a card needs a shadow that is not on
|
||||||
|
|
@ -23,6 +26,11 @@
|
||||||
--_shadow: var(--nv-bevel), var(--nv-outline), var(--nv-elevation-1);
|
--_shadow: var(--nv-bevel), var(--nv-outline), var(--nv-elevation-1);
|
||||||
--_sheen: var(--nv-sheen-surface);
|
--_sheen: var(--nv-sheen-surface);
|
||||||
--_padding: var(--nv-space-5);
|
--_padding: var(--nv-space-5);
|
||||||
|
/* Light layers the variants switch on. Kept separate from --_shadow so a
|
||||||
|
lit or glowing card can still be elevated, floating or interactive. */
|
||||||
|
--_lit: 0 0 transparent;
|
||||||
|
--_glow: 0 0 transparent;
|
||||||
|
--_spot: linear-gradient(transparent, transparent);
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -30,11 +38,11 @@
|
||||||
min-inline-size: 0;
|
min-inline-size: 0;
|
||||||
|
|
||||||
background-color: var(--_bg);
|
background-color: var(--_bg);
|
||||||
background-image: var(--_sheen);
|
background-image: var(--_spot), var(--_sheen);
|
||||||
color: var(--nv-color-text-primary);
|
color: var(--nv-color-text-primary);
|
||||||
border: var(--nv-border-width) solid var(--_border);
|
border: var(--nv-border-width) solid var(--_border);
|
||||||
border-radius: var(--nv-radius-lg);
|
border-radius: var(--nv-radius-lg);
|
||||||
box-shadow: var(--_shadow);
|
box-shadow: var(--_lit), var(--_shadow), var(--_glow);
|
||||||
backdrop-filter: var(--nv-backdrop-glass);
|
backdrop-filter: var(--nv-backdrop-glass);
|
||||||
-webkit-backdrop-filter: var(--nv-backdrop-glass);
|
-webkit-backdrop-filter: var(--nv-backdrop-glass);
|
||||||
|
|
||||||
|
|
@ -86,20 +94,75 @@
|
||||||
|
|
||||||
@media (hover: hover) {
|
@media (hover: hover) {
|
||||||
.nv-card--interactive:hover {
|
.nv-card--interactive:hover {
|
||||||
box-shadow: var(--nv-bevel), var(--nv-outline), var(--nv-elevation-3);
|
box-shadow: var(--_lit), var(--nv-bevel), var(--nv-outline), var(--nv-elevation-3), var(--_glow);
|
||||||
translate: 0 calc(var(--nv-lift-hover) * 2);
|
translate: 0 calc(var(--nv-lift-hover) * 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.nv-card--interactive:active {
|
.nv-card--interactive:active {
|
||||||
box-shadow: var(--nv-bevel), var(--nv-outline), var(--nv-elevation-1);
|
box-shadow: var(--_lit), var(--nv-bevel), var(--nv-outline), var(--nv-elevation-1), var(--_glow);
|
||||||
translate: 0 var(--nv-press-depth);
|
translate: 0 var(--nv-press-depth);
|
||||||
transition-duration: var(--nv-duration-instant);
|
transition-duration: var(--nv-duration-instant);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nv-card--interactive:focus-visible {
|
.nv-card--interactive:focus-visible {
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: var(--_shadow), var(--nv-focus-ring);
|
box-shadow: var(--_lit), var(--_shadow), var(--_glow), var(--nv-focus-ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Light ------------------------------------------------------------------
|
||||||
|
Three ways a card can carry light, each stated once and combinable with
|
||||||
|
every elevation above. They all take the card's tint — the accent unless
|
||||||
|
--nv-card-tint names another color, the way --nv-bed-tint does for a bed.
|
||||||
|
|
||||||
|
Lit: a strip of light along the top edge, spilling a little way down
|
||||||
|
the face. For a card that belongs to something with a color: a
|
||||||
|
project, a status, a category.
|
||||||
|
Glow: the card gives off its tint. For the one card that is on — the
|
||||||
|
current plan, the live item. Two glowing cards side by side
|
||||||
|
cancel each other out.
|
||||||
|
Spotlight: a soft light follows the pointer across the face (niveus.js
|
||||||
|
tracks it). A showcase effect; a list of work items does not
|
||||||
|
need one. */
|
||||||
|
|
||||||
|
.nv-card--lit,
|
||||||
|
.nv-card--glow,
|
||||||
|
.nv-card--spotlight {
|
||||||
|
--nv-card-tint: var(--nv-color-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-card--lit {
|
||||||
|
--_lit:
|
||||||
|
inset 0 2px 0 color-mix(in oklab, var(--nv-card-tint) 85%, transparent),
|
||||||
|
inset 0 18px 28px -22px color-mix(in oklab, var(--nv-card-tint) 70%, transparent);
|
||||||
|
--_sheen:
|
||||||
|
radial-gradient(
|
||||||
|
120% 70% at 0% 0%,
|
||||||
|
color-mix(in oklab, var(--nv-card-tint) 12%, transparent),
|
||||||
|
transparent 70%
|
||||||
|
),
|
||||||
|
var(--nv-sheen-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-card--glow {
|
||||||
|
--_border: color-mix(in oklab, var(--nv-card-tint) 40%, var(--nv-color-border-subtle));
|
||||||
|
--_glow: 0 0 var(--nv-glow-blur-md) var(--nv-glow-spread-md)
|
||||||
|
color-mix(in oklab, var(--nv-card-tint) var(--nv-glow-strength), transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (hover: hover) {
|
||||||
|
.nv-card--glow.nv-card--interactive:hover {
|
||||||
|
--_glow: 0 0 var(--nv-glow-blur-lg) var(--nv-glow-spread-md)
|
||||||
|
color-mix(in oklab, var(--nv-card-tint) var(--nv-glow-strength), transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-card--spotlight:hover {
|
||||||
|
--_spot: radial-gradient(
|
||||||
|
22rem circle at var(--nv-pointer-x, 50%) var(--nv-pointer-y, 0%),
|
||||||
|
color-mix(in oklab, var(--nv-card-tint) 14%, var(--nv-color-sheen-top)),
|
||||||
|
transparent 70%
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Cards whose whole area is clickable via a link inside: the link's target is
|
/* Cards whose whole area is clickable via a link inside: the link's target is
|
||||||
|
|
|
||||||
55
niveus/components/deck.css
Normal file
55
niveus/components/deck.css
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
/* ==========================================================================
|
||||||
|
Niveus — Deck
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
<div class="nv-deck">
|
||||||
|
<article class="nv-card">…</article>
|
||||||
|
<div class="nv-deck__layer" aria-hidden="true"></div>
|
||||||
|
<div class="nv-deck__layer" aria-hidden="true"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
One surface in front of others of its kind: the current item of a set that
|
||||||
|
has more behind it. The layers are cards seen edge-on, stepping back and
|
||||||
|
dimming with distance, so the set has depth without anything having to
|
||||||
|
move. Use it where "there are more of these" is part of the message — a
|
||||||
|
showcase of products, a stack of notifications, a carousel at rest.
|
||||||
|
|
||||||
|
Everything in the deck shares one grid cell. The front surface is any
|
||||||
|
element; the layers are decorative and hidden from assistive technology.
|
||||||
|
Panels that swap (an .nv-tab-panel set) can live in the deck together:
|
||||||
|
the hidden ones take no space.
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
.nv-deck {
|
||||||
|
--_step: var(--nv-space-3);
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
position: relative;
|
||||||
|
isolation: isolate;
|
||||||
|
/* Room above the front surface for the layers that peek out behind it. */
|
||||||
|
padding-block-start: calc(var(--_step) * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-deck > * {
|
||||||
|
grid-area: 1 / 1;
|
||||||
|
min-inline-size: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-deck__layer {
|
||||||
|
--_depth: 1;
|
||||||
|
|
||||||
|
z-index: -1;
|
||||||
|
pointer-events: none;
|
||||||
|
background-color: color-mix(in oklab, var(--nv-color-surface) 90%, transparent);
|
||||||
|
background-image: var(--nv-sheen-surface);
|
||||||
|
border: var(--nv-border-width) solid var(--nv-color-border-subtle);
|
||||||
|
border-radius: var(--nv-radius-lg);
|
||||||
|
box-shadow: var(--nv-bevel), var(--nv-outline), var(--nv-elevation-1);
|
||||||
|
translate: 0 calc(var(--_step) * var(--_depth) * -1);
|
||||||
|
scale: calc(1 - var(--_depth) * 0.05) 1;
|
||||||
|
opacity: calc(1 - var(--_depth) * 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-deck__layer + .nv-deck__layer {
|
||||||
|
--_depth: 2;
|
||||||
|
z-index: -2;
|
||||||
|
}
|
||||||
60
niveus/components/skeleton.css
vendored
Normal file
60
niveus/components/skeleton.css
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
/* ==========================================================================
|
||||||
|
Niveus — Skeleton
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
<span class="nv-skeleton nv-skeleton--text"></span>
|
||||||
|
|
||||||
|
The shape of content that has not arrived yet. A skeleton is a shallow well
|
||||||
|
cut where the content will sit, with a slow band of light crossing it — the
|
||||||
|
surface is recessed and waiting, not broken.
|
||||||
|
|
||||||
|
.nv-skeleton a block; size it with the layout
|
||||||
|
.nv-skeleton--text one line of body text
|
||||||
|
.nv-skeleton--circle an avatar or a tile
|
||||||
|
|
||||||
|
Under reduced motion the light stops crossing and the well stays: the state
|
||||||
|
is still legible, it just does not move.
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
.nv-skeleton {
|
||||||
|
display: block;
|
||||||
|
min-block-size: var(--nv-space-4);
|
||||||
|
border-radius: var(--nv-radius-md);
|
||||||
|
background-color: var(--nv-color-surface-inset);
|
||||||
|
background-image: linear-gradient(
|
||||||
|
100deg,
|
||||||
|
transparent 30%,
|
||||||
|
var(--nv-color-sheen-top) 50%,
|
||||||
|
transparent 70%
|
||||||
|
);
|
||||||
|
background-size: 220% 100%;
|
||||||
|
box-shadow: var(--nv-elevation-inset-sm), var(--nv-ledge);
|
||||||
|
animation: nv-shimmer var(--nv-duration-shimmer) var(--nv-ease-linear) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-skeleton--text {
|
||||||
|
inline-size: 100%;
|
||||||
|
min-block-size: 0;
|
||||||
|
block-size: 0.75em;
|
||||||
|
border-radius: var(--nv-radius-full);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A paragraph of skeleton lines ends short, like the paragraph it stands for. */
|
||||||
|
.nv-skeleton--text:last-child:not(:first-child) {
|
||||||
|
inline-size: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-skeleton--circle {
|
||||||
|
aspect-ratio: 1;
|
||||||
|
border-radius: var(--nv-radius-full);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes nv-shimmer {
|
||||||
|
from { background-position: 100% 0; }
|
||||||
|
to { background-position: -120% 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.nv-skeleton {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
74
niveus/components/stat.css
Normal file
74
niveus/components/stat.css
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
/* ==========================================================================
|
||||||
|
Niveus — Stat
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
<dl class="nv-stat-group">
|
||||||
|
<div class="nv-stat">
|
||||||
|
<dt class="nv-stat__label">Servers</dt>
|
||||||
|
<dd class="nv-stat__value">1,204</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
A number the user is meant to read at a glance. A group of stats is set into
|
||||||
|
one shallow well, like the readout panel of an instrument: the figures sit
|
||||||
|
behind the surface, separated by hairlines, so a row of numbers reads as one
|
||||||
|
display instead of as four loose labels.
|
||||||
|
|
||||||
|
.nv-stat-group the readout well; wraps without a media query
|
||||||
|
.nv-stat one reading
|
||||||
|
.nv-stat__label what is measured
|
||||||
|
.nv-stat__value the figure, in tabular numerals
|
||||||
|
.nv-stat__delta change since last time; --up / --down
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
.nv-stat-group {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(min(8rem, 100%), 1fr));
|
||||||
|
/* The hairlines between readings are the well itself showing through a
|
||||||
|
one-pixel gap, so they stay continuous however the row wraps. */
|
||||||
|
gap: var(--nv-border-width);
|
||||||
|
margin: 0;
|
||||||
|
background-color: var(--nv-color-border-subtle);
|
||||||
|
border: var(--nv-border-width) solid var(--nv-color-border-subtle);
|
||||||
|
border-radius: var(--nv-radius-lg);
|
||||||
|
box-shadow: var(--nv-elevation-inset-md), var(--nv-ledge);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-stat {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--nv-space-1);
|
||||||
|
min-inline-size: 0;
|
||||||
|
padding: var(--nv-space-4) var(--nv-space-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-stat-group > .nv-stat {
|
||||||
|
background-color: var(--nv-color-surface-inset);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-stat__label {
|
||||||
|
font: var(--nv-font-caption);
|
||||||
|
color: var(--nv-color-text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-stat__value {
|
||||||
|
margin: 0;
|
||||||
|
font: var(--nv-font-heading);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
letter-spacing: var(--nv-letter-spacing-tight);
|
||||||
|
color: var(--nv-color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-stat__delta {
|
||||||
|
font: var(--nv-font-caption);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
color: var(--nv-color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-stat__delta--up {
|
||||||
|
color: var(--nv-color-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-stat__delta--down {
|
||||||
|
color: var(--nv-color-danger);
|
||||||
|
}
|
||||||
157
niveus/components/steps.css
Normal file
157
niveus/components/steps.css
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
/* ==========================================================================
|
||||||
|
Niveus — Steps
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
<ol class="nv-steps">
|
||||||
|
<li class="nv-steps__item" data-state="done">
|
||||||
|
<span class="nv-steps__marker">1</span>
|
||||||
|
<div class="nv-steps__body">
|
||||||
|
<h3 class="nv-steps__title">Listen</h3>
|
||||||
|
<p class="nv-steps__text">…</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
A sequence the user moves through: a process, an onboarding, an order's
|
||||||
|
progress. The steps sit on a rail — a groove cut into the surface — with
|
||||||
|
each marker a raised knob set into it. Finished steps light their stretch
|
||||||
|
of the rail, so progress reads as something filling up rather than as a
|
||||||
|
row of ticks.
|
||||||
|
|
||||||
|
.nv-steps horizontal; becomes vertical on narrow screens
|
||||||
|
.nv-steps--vertical always vertical
|
||||||
|
data-state="done" the step and its stretch of rail are lit
|
||||||
|
aria-current="step" the step in progress: lit marker, rail still dark
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
.nv-steps {
|
||||||
|
--_marker: 2.25rem;
|
||||||
|
--_rail: 6px;
|
||||||
|
--_gap: var(--nv-space-4);
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
grid-auto-flow: column;
|
||||||
|
grid-auto-columns: minmax(0, 1fr);
|
||||||
|
gap: var(--_gap);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-steps__item {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--nv-space-3);
|
||||||
|
min-inline-size: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Each step draws the rail from its own marker to the next one, so a finished
|
||||||
|
step can light exactly its stretch and the rail never runs past the end. */
|
||||||
|
.nv-steps__item:not(:last-child)::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset-block-start: calc((var(--_marker) - var(--_rail)) / 2);
|
||||||
|
inset-inline-start: calc(var(--_marker) / 2);
|
||||||
|
inline-size: calc(100% + var(--_gap));
|
||||||
|
block-size: var(--_rail);
|
||||||
|
border-radius: var(--nv-radius-full);
|
||||||
|
background-color: var(--nv-color-surface-inset);
|
||||||
|
box-shadow: var(--nv-elevation-inset-sm), var(--nv-ledge);
|
||||||
|
transition: var(--nv-transition-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-steps__item[data-state="done"]:not(:last-child)::before {
|
||||||
|
background-color: var(--nv-color-accent);
|
||||||
|
background-image: var(--nv-sheen-filled);
|
||||||
|
box-shadow: var(--nv-glow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-steps__marker {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
flex: none;
|
||||||
|
inline-size: var(--_marker);
|
||||||
|
block-size: var(--_marker);
|
||||||
|
|
||||||
|
background-color: var(--nv-color-surface-elevated);
|
||||||
|
background-image: var(--nv-sheen-raised);
|
||||||
|
color: var(--nv-color-text-secondary);
|
||||||
|
font: var(--nv-font-label);
|
||||||
|
font-weight: var(--nv-font-weight-semibold);
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
border-radius: var(--nv-radius-full);
|
||||||
|
box-shadow: var(--nv-bevel), var(--nv-outline), var(--nv-elevation-2);
|
||||||
|
transition: var(--nv-transition-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-steps__item[data-state="done"] > .nv-steps__marker,
|
||||||
|
.nv-steps__item[aria-current="step"] > .nv-steps__marker {
|
||||||
|
background-color: var(--nv-color-accent);
|
||||||
|
background-image: var(--nv-sheen-filled);
|
||||||
|
color: var(--nv-color-on-accent);
|
||||||
|
box-shadow: var(--nv-bevel), var(--nv-outline), var(--nv-elevation-2), var(--nv-glow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-steps__body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--nv-space-1);
|
||||||
|
min-inline-size: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-steps__title {
|
||||||
|
margin: 0;
|
||||||
|
font: var(--nv-font-title);
|
||||||
|
letter-spacing: var(--nv-letter-spacing-tight);
|
||||||
|
color: var(--nv-color-text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-steps__text {
|
||||||
|
margin: 0;
|
||||||
|
font: var(--nv-font-caption);
|
||||||
|
color: var(--nv-color-text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Vertical -------------------------------------------------------------------
|
||||||
|
Marker beside the text and the rail running down between markers. Also what
|
||||||
|
a horizontal sequence becomes once its columns are too narrow to read. */
|
||||||
|
|
||||||
|
.nv-steps--vertical {
|
||||||
|
grid-auto-flow: row;
|
||||||
|
grid-auto-columns: auto;
|
||||||
|
--_gap: var(--nv-space-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-steps--vertical > .nv-steps__item {
|
||||||
|
flex-direction: row;
|
||||||
|
gap: var(--nv-space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-steps--vertical > .nv-steps__item:not(:last-child)::before {
|
||||||
|
inset-block-start: calc(var(--_marker) / 2);
|
||||||
|
inset-inline-start: calc((var(--_marker) - var(--_rail)) / 2);
|
||||||
|
inline-size: var(--_rail);
|
||||||
|
block-size: calc(100% + var(--_gap));
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 40rem) {
|
||||||
|
.nv-steps {
|
||||||
|
grid-auto-flow: row;
|
||||||
|
grid-auto-columns: auto;
|
||||||
|
--_gap: var(--nv-space-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-steps > .nv-steps__item {
|
||||||
|
flex-direction: row;
|
||||||
|
gap: var(--nv-space-4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-steps > .nv-steps__item:not(:last-child)::before {
|
||||||
|
inset-block-start: calc(var(--_marker) / 2);
|
||||||
|
inset-inline-start: calc((var(--_marker) - var(--_rail)) / 2);
|
||||||
|
inline-size: var(--_rail);
|
||||||
|
block-size: calc(100% + var(--_gap));
|
||||||
|
}
|
||||||
|
}
|
||||||
83
niveus/components/tile.css
Normal file
83
niveus/components/tile.css
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
/* ==========================================================================
|
||||||
|
Niveus — Tile
|
||||||
|
--------------------------------------------------------------------------
|
||||||
|
<span class="nv-tile"><svg …></svg></span>
|
||||||
|
|
||||||
|
A tile is a small raised key that holds one glyph: the icon beside a
|
||||||
|
feature, the mark of a product, the symbol on a stat. It is the physical
|
||||||
|
version of an icon — something with an edge you could press — which is why
|
||||||
|
a row of features reads as a set of objects rather than as clip art.
|
||||||
|
|
||||||
|
.nv-tile soft key: tinted face, the glyph in its tint
|
||||||
|
.nv-tile--solid filled key: saturated face, a light glyph
|
||||||
|
.nv-tile--glow the key is lit from inside
|
||||||
|
.nv-tile--sm / --lg / --xl
|
||||||
|
|
||||||
|
Everything derives from --nv-tile-tint, the accent unless set. A tile is
|
||||||
|
not a button: if it does something, it is an .nv-button--icon.
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
.nv-tile {
|
||||||
|
--nv-tile-tint: var(--nv-color-accent);
|
||||||
|
--_size: 2.75rem;
|
||||||
|
--_radius: var(--nv-radius-md);
|
||||||
|
--_bg: color-mix(in oklab, var(--nv-tile-tint) 16%, var(--nv-color-surface-elevated));
|
||||||
|
--_ink: color-mix(in oklab, var(--nv-tile-tint) 70%, var(--nv-color-text-primary));
|
||||||
|
--_sheen: var(--nv-sheen-raised);
|
||||||
|
--_glow: 0 0 transparent;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
display: inline-grid;
|
||||||
|
place-items: center;
|
||||||
|
flex: none;
|
||||||
|
inline-size: var(--_size);
|
||||||
|
block-size: var(--_size);
|
||||||
|
|
||||||
|
background-color: var(--_bg);
|
||||||
|
background-image: var(--_sheen);
|
||||||
|
color: var(--_ink);
|
||||||
|
border: var(--nv-border-width) solid color-mix(in oklab, var(--nv-tile-tint) 30%, var(--nv-color-border-subtle));
|
||||||
|
border-radius: var(--_radius);
|
||||||
|
box-shadow: var(--nv-bevel), var(--nv-outline), var(--nv-elevation-1), var(--_glow);
|
||||||
|
transition: var(--nv-transition-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Glyphs take half the key; a mark that is itself a picture (an image, a
|
||||||
|
logo) fills it, clipped to the key's corners. */
|
||||||
|
.nv-tile > svg {
|
||||||
|
inline-size: 50%;
|
||||||
|
block-size: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-tile > img {
|
||||||
|
inline-size: 100%;
|
||||||
|
block-size: 100%;
|
||||||
|
border-radius: inherit;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-tile--solid {
|
||||||
|
--_bg: var(--nv-tile-tint);
|
||||||
|
--_ink: var(--nv-color-on-accent);
|
||||||
|
--_sheen: var(--nv-sheen-filled);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-tile--glow {
|
||||||
|
--_glow: 0 0 var(--nv-glow-blur-md) var(--nv-glow-spread-sm)
|
||||||
|
color-mix(in oklab, var(--nv-tile-tint) var(--nv-glow-strength), transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-tile--sm {
|
||||||
|
--_size: 2rem;
|
||||||
|
--_radius: var(--nv-radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-tile--lg {
|
||||||
|
--_size: 3.5rem;
|
||||||
|
--_radius: var(--nv-radius-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-tile--xl {
|
||||||
|
--_size: 4.5rem;
|
||||||
|
--_radius: var(--nv-radius-xl);
|
||||||
|
}
|
||||||
|
|
@ -200,6 +200,33 @@
|
||||||
view-transition-class: nv-content;
|
view-transition-class: nv-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Reveal ------------------------------------------------------------------------
|
||||||
|
Content arriving as it scrolls into view: it rises a short way and fades in,
|
||||||
|
once, so a long page is read in the order it was written instead of all at
|
||||||
|
once. .nv-reveal-group does the same for its children one after another,
|
||||||
|
--nv-stagger apart.
|
||||||
|
|
||||||
|
niveus.js marks what is waiting. Without the script, and under reduced
|
||||||
|
motion, nothing is ever marked — so nothing is ever hidden. */
|
||||||
|
|
||||||
|
.nv-reveal[data-nv-reveal="pending"],
|
||||||
|
.nv-reveal-group > [data-nv-reveal="pending"] {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nv-reveal[data-nv-reveal="shown"],
|
||||||
|
.nv-reveal-group > [data-nv-reveal="shown"] {
|
||||||
|
animation: nv-arrive var(--nv-duration-slower) var(--nv-ease-entrance)
|
||||||
|
calc(var(--nv-reveal-index, 0) * var(--nv-stagger)) both;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes nv-arrive {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
translate: 0 var(--nv-travel-lg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Collapsible region for markup that is not a <details>. The 0fr -> 1fr grid
|
/* Collapsible region for markup that is not a <details>. The 0fr -> 1fr grid
|
||||||
row is the one technique that animates to an unknown content height in every
|
row is the one technique that animates to an unknown content height in every
|
||||||
browser, with no measurement and no script. */
|
browser, with no measurement and no script. */
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,12 @@
|
||||||
@import url("components/badge.css") layer(niveus.components);
|
@import url("components/badge.css") layer(niveus.components);
|
||||||
@import url("components/table.css") layer(niveus.components);
|
@import url("components/table.css") layer(niveus.components);
|
||||||
@import url("components/window.css") layer(niveus.components);
|
@import url("components/window.css") layer(niveus.components);
|
||||||
|
@import url("components/tile.css") layer(niveus.components);
|
||||||
|
@import url("components/avatar.css") layer(niveus.components);
|
||||||
|
@import url("components/stat.css") layer(niveus.components);
|
||||||
|
@import url("components/steps.css") layer(niveus.components);
|
||||||
|
@import url("components/deck.css") layer(niveus.components);
|
||||||
|
@import url("components/skeleton.css") layer(niveus.components);
|
||||||
|
|
||||||
/* --- Utilities ----------------------------------------------------------------- */
|
/* --- Utilities ----------------------------------------------------------------- */
|
||||||
@import url("utilities.css") layer(niveus.utilities);
|
@import url("utilities.css") layer(niveus.utilities);
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,9 @@
|
||||||
[data-nv-close] button that closes its dialog or popover
|
[data-nv-close] button that closes its dialog or popover
|
||||||
[data-nv-scroller] frame whose [data-nv-scroll] controls move
|
[data-nv-scroller] frame whose [data-nv-scroll] controls move
|
||||||
its .nv-scroller a page at a time
|
its .nv-scroller a page at a time
|
||||||
|
.nv-reveal / .nv-reveal-group content that arrives as it scrolls into
|
||||||
|
view, once (see continuity.css)
|
||||||
|
.nv-card--spotlight card whose light follows the pointer
|
||||||
|
|
||||||
Only what changes is lifted. A transition names the selection marker and
|
Only what changes is lifted. A transition names the selection marker and
|
||||||
the items of the groups it is given as `scope`, and only for as long as it
|
the items of the groups it is given as `scope`, and only for as long as it
|
||||||
|
|
@ -297,6 +300,61 @@
|
||||||
return named;
|
return named;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reveal content as it scrolls into view. Elements are only marked as
|
||||||
|
* waiting here, so a page without this script — or under reduced motion —
|
||||||
|
* never hides anything. Each element arrives once and is then left alone.
|
||||||
|
*/
|
||||||
|
var revealer = null;
|
||||||
|
|
||||||
|
function arrive(target) {
|
||||||
|
var items = target.classList.contains("nv-reveal-group") ? target.children : [target];
|
||||||
|
for (var i = 0; i < items.length; i++) {
|
||||||
|
var item = items[i];
|
||||||
|
item.style.setProperty("--nv-reveal-index", String(i));
|
||||||
|
item.dataset.nvReveal = "shown";
|
||||||
|
item.addEventListener("animationend", function settle(event) {
|
||||||
|
if (event.target !== this || event.animationName !== "nv-arrive") return;
|
||||||
|
this.removeEventListener("animationend", settle);
|
||||||
|
delete this.dataset.nvReveal;
|
||||||
|
this.style.removeProperty("--nv-reveal-index");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initReveal(scope) {
|
||||||
|
if (prefersReducedMotion() || typeof global.IntersectionObserver !== "function") return;
|
||||||
|
|
||||||
|
revealer = revealer || new IntersectionObserver(function (entries) {
|
||||||
|
entries.forEach(function (entry) {
|
||||||
|
if (!entry.isIntersecting) return;
|
||||||
|
revealer.unobserve(entry.target);
|
||||||
|
arrive(entry.target);
|
||||||
|
});
|
||||||
|
}, { rootMargin: "0px 0px -8% 0px" });
|
||||||
|
|
||||||
|
scope.querySelectorAll(".nv-reveal, .nv-reveal-group").forEach(function (target) {
|
||||||
|
if (target.dataset.nvRevealWired) return;
|
||||||
|
target.dataset.nvRevealWired = "";
|
||||||
|
var items = target.classList.contains("nv-reveal-group") ? target.children : [target];
|
||||||
|
for (var i = 0; i < items.length; i++) items[i].dataset.nvReveal = "pending";
|
||||||
|
revealer.observe(target);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The light on a .nv-card--spotlight sits under the pointer. One listener
|
||||||
|
* for the whole document, so cards added later need no wiring.
|
||||||
|
*/
|
||||||
|
function trackSpotlight(event) {
|
||||||
|
if (event.pointerType && event.pointerType !== "mouse") return;
|
||||||
|
var surface = event.target.closest && event.target.closest(".nv-card--spotlight");
|
||||||
|
if (!surface) return;
|
||||||
|
var box = surface.getBoundingClientRect();
|
||||||
|
surface.style.setProperty("--nv-pointer-x", event.clientX - box.left + "px");
|
||||||
|
surface.style.setProperty("--nv-pointer-y", event.clientY - box.top + "px");
|
||||||
|
}
|
||||||
|
|
||||||
function wire(scope) {
|
function wire(scope) {
|
||||||
scope = scope || document;
|
scope = scope || document;
|
||||||
|
|
||||||
|
|
@ -314,6 +372,7 @@
|
||||||
|
|
||||||
scope.querySelectorAll("[data-nv-tabs]").forEach(initTabs);
|
scope.querySelectorAll("[data-nv-tabs]").forEach(initTabs);
|
||||||
scope.querySelectorAll("[data-nv-scroller]").forEach(initScroller);
|
scope.querySelectorAll("[data-nv-scroller]").forEach(initScroller);
|
||||||
|
initReveal(scope);
|
||||||
|
|
||||||
scope.querySelectorAll("[data-nv-close]").forEach(function (control) {
|
scope.querySelectorAll("[data-nv-close]").forEach(function (control) {
|
||||||
control.addEventListener("click", function () {
|
control.addEventListener("click", function () {
|
||||||
|
|
@ -345,6 +404,8 @@
|
||||||
prefersReducedMotion: prefersReducedMotion,
|
prefersReducedMotion: prefersReducedMotion,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
document.addEventListener("pointermove", trackSpotlight, { passive: true });
|
||||||
|
|
||||||
if (document.readyState === "loading") {
|
if (document.readyState === "loading") {
|
||||||
document.addEventListener("DOMContentLoaded", function () { wire(); });
|
document.addEventListener("DOMContentLoaded", function () { wire(); });
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,7 @@
|
||||||
--nv-dark-sheen-bottom: rgb(0 0 0 / 0.18);
|
--nv-dark-sheen-bottom: rgb(0 0 0 / 0.18);
|
||||||
--nv-dark-sheen-strong-top: rgb(255 255 255 / 0.3);
|
--nv-dark-sheen-strong-top: rgb(255 255 255 / 0.3);
|
||||||
--nv-dark-sheen-strong-bottom: rgb(0 0 0 / 0.2);
|
--nv-dark-sheen-strong-bottom: rgb(0 0 0 / 0.2);
|
||||||
|
--nv-dark-glow: oklch(from var(--nv-dark-accent) l c h / 0.5);
|
||||||
|
|
||||||
--nv-dark-knob: #f4f3ef;
|
--nv-dark-knob: #f4f3ef;
|
||||||
--nv-dark-knob-shade: rgb(40 32 20 / 0.3);
|
--nv-dark-knob-shade: rgb(40 32 20 / 0.3);
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,7 @@
|
||||||
--nv-light-sheen-bottom: rgb(40 32 20 / 0.07);
|
--nv-light-sheen-bottom: rgb(40 32 20 / 0.07);
|
||||||
--nv-light-sheen-strong-top: rgb(255 255 255 / 0.32);
|
--nv-light-sheen-strong-top: rgb(255 255 255 / 0.32);
|
||||||
--nv-light-sheen-strong-bottom: rgb(20 16 10 / 0.16);
|
--nv-light-sheen-strong-bottom: rgb(20 16 10 / 0.16);
|
||||||
|
--nv-light-glow: oklch(from var(--nv-light-accent) l c h / 0.3);
|
||||||
|
|
||||||
--nv-light-knob: var(--nv-palette-neutral-0);
|
--nv-light-knob: var(--nv-palette-neutral-0);
|
||||||
--nv-light-knob-shade: rgb(40 32 20 / 0.13);
|
--nv-light-knob-shade: rgb(40 32 20 / 0.13);
|
||||||
|
|
|
||||||
|
|
@ -227,6 +227,11 @@
|
||||||
--nv-color-sheen-strong-top: light-dark(var(--nv-light-sheen-strong-top), var(--nv-dark-sheen-strong-top));
|
--nv-color-sheen-strong-top: light-dark(var(--nv-light-sheen-strong-top), var(--nv-dark-sheen-strong-top));
|
||||||
--nv-color-sheen-strong-bottom: light-dark(var(--nv-light-sheen-strong-bottom), var(--nv-dark-sheen-strong-bottom));
|
--nv-color-sheen-strong-bottom: light-dark(var(--nv-light-sheen-strong-bottom), var(--nv-dark-sheen-strong-bottom));
|
||||||
|
|
||||||
|
/* Glow: light a surface gives off, as opposed to the shade it casts. It is
|
||||||
|
the accent seen through air — strong enough in dark to read as a lit
|
||||||
|
edge, faint in light, where paper does not emit. */
|
||||||
|
--nv-color-glow: light-dark(var(--nv-light-glow), var(--nv-dark-glow));
|
||||||
|
|
||||||
/* A knob: the thumb of a switch or a slider. It stays light in both
|
/* A knob: the thumb of a switch or a slider. It stays light in both
|
||||||
appearances, the way a physical knob does not change color with the room. */
|
appearances, the way a physical knob does not change color with the room. */
|
||||||
--nv-color-knob: light-dark(var(--nv-light-knob), var(--nv-dark-knob));
|
--nv-color-knob: light-dark(var(--nv-light-knob), var(--nv-dark-knob));
|
||||||
|
|
|
||||||
|
|
@ -95,4 +95,30 @@
|
||||||
--nv-focus-ring:
|
--nv-focus-ring:
|
||||||
0 0 0 1px var(--nv-color-focus-ring-contrast),
|
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);
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,15 @@
|
||||||
--nv-travel-sm: 4px;
|
--nv-travel-sm: 4px;
|
||||||
--nv-travel-md: 8px;
|
--nv-travel-md: 8px;
|
||||||
--nv-travel-lg: 16px;
|
--nv-travel-lg: 16px;
|
||||||
|
|
||||||
|
/* --- Sequence ---------------------------------------------------------------
|
||||||
|
The gap between siblings arriving one after another. Short enough that a
|
||||||
|
group reads as one movement, long enough that the eye sees an order. */
|
||||||
|
--nv-stagger: 60ms;
|
||||||
|
|
||||||
|
/* A loading shimmer is a slow, constant sweep — slow enough to read as
|
||||||
|
"waiting", never fast enough to read as an alert. */
|
||||||
|
--nv-duration-shimmer: 1600ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Reduced motion is a system-wide contract, not a per-component opt-in: cut
|
/* Reduced motion is a system-wide contract, not a per-component opt-in: cut
|
||||||
|
|
@ -103,5 +112,6 @@
|
||||||
--nv-travel-sm: 0px;
|
--nv-travel-sm: 0px;
|
||||||
--nv-travel-md: 0px;
|
--nv-travel-md: 0px;
|
||||||
--nv-travel-lg: 0px;
|
--nv-travel-lg: 0px;
|
||||||
|
--nv-stagger: 0ms;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue