mirror of
https://github.com/Nexoniarz/Niveus.git
synced 2026-11-03 04:34:26 +00:00
Tokens - --nv-color-glow role (light and dark) and a glow scale: --nv-glow-sm/md/lg, --nv-glow-blur-*, --nv-glow-spread-*, --nv-glow-strength - --nv-stagger (0ms under reduced motion) and --nv-duration-shimmer Components - Card: --lit (lit top edge in --nv-card-tint), --glow, --spotlight (light follows the pointer); light layers combine with every elevation - Tile: small raised key for a glyph or product mark; --solid, --glow, sizes - Avatar: raised disc with --ring / --glow, sizes, __status, avatar group - Stat: readings set into one recessed well (.nv-stat-group) - Steps: markers set into a rail; done steps light their stretch; vertical - Deck: one surface in front of stacked layers of its kind - Skeleton: recessed placeholder with a slow sweep, still under reduced motion - Text: .nv-text-lit and .nv-text-lit--accent Continuity - .nv-reveal / .nv-reveal-group: content arrives once as it scrolls into view (nv-arrive keyframe); niveus.js marks pending state, so nothing is hidden without the script or under reduced motion - niveus.js tracks the pointer for .nv-card--spotlight README and REFERENCE document every new class, token and attribute; tools/check.py passes and dist/ is rebuilt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
183 lines
10 KiB
CSS
183 lines
10 KiB
CSS
/* ==========================================================================
|
|
Niveus — Light appearance
|
|
--------------------------------------------------------------------------
|
|
Values for the light side of every semantic role. Nothing here is consumed
|
|
by components directly; tokens/color.css resolves these through light-dark().
|
|
|
|
Almost nothing below is a literal color. Each role is *derived* from
|
|
--nv-brand, so setting one value re-colours the entire system: the accent
|
|
and its states, what text goes on top of it, how the status colors lean, and
|
|
how much of the brand bleeds into the greys. See tokens/color.css for the
|
|
inputs.
|
|
|
|
Light Niveus reads as soft paper and enamel on a warm grey desk: surfaces
|
|
that hold content stay near-white, so documents never look yellowed; the
|
|
canvas around them is a few steps warmer and darker, so a window sits *on*
|
|
something. Raised controls are bevelled — a bright top edge, a soft shade
|
|
along the bottom, light falling across the face — and shadows are warm,
|
|
the color of shade on paper rather than of a grey filter.
|
|
========================================================================== */
|
|
|
|
:root {
|
|
/* Follow the environment unless the application states an appearance. */
|
|
color-scheme: light dark;
|
|
}
|
|
|
|
:root[data-theme="light"] {
|
|
color-scheme: light;
|
|
}
|
|
|
|
/* [data-nv-brand] re-derives the whole set for a subtree, so one region can
|
|
carry a different brand without a second theme. */
|
|
:root,
|
|
[data-nv-brand] {
|
|
/* --- Canvas and surfaces -----------------------------------------------
|
|
Paper stays paper: the two surfaces that hold content are left untinted so
|
|
text keeps its contrast. Everything the surfaces sit *in* picks up a trace
|
|
of the brand, which is what stops an accent from looking stuck on. */
|
|
--nv-light-background: color-mix(in oklab, #e8e5de, var(--nv-brand) var(--nv-brand-tint));
|
|
--nv-light-background-subtle: color-mix(in oklab, #eeece6, var(--nv-brand) var(--nv-brand-tint));
|
|
--nv-light-surface: #fbfbfa;
|
|
--nv-light-surface-elevated: #f8f8f6;
|
|
--nv-light-surface-inset: color-mix(in oklab, #eeede8, var(--nv-brand) var(--nv-brand-tint));
|
|
--nv-light-surface-hover: color-mix(in oklab, #f1f0ec, var(--nv-brand) var(--nv-brand-tint));
|
|
--nv-light-surface-active: color-mix(in oklab, #e6e5e0, var(--nv-brand) var(--nv-brand-tint));
|
|
--nv-light-scrim: rgb(28 24 18 / 0.34);
|
|
|
|
/* --- Text ---------------------------------------------------------------
|
|
Primary text stays neutral — it carries the reading contrast. The quieter
|
|
two lean towards the brand, where a tint reads as warmth rather than as a
|
|
loss of legibility. */
|
|
--nv-light-text-primary: var(--nv-palette-neutral-900);
|
|
--nv-light-text-secondary: color-mix(in oklab, var(--nv-palette-neutral-600), var(--nv-brand) 8%);
|
|
--nv-light-text-tertiary: color-mix(in oklab, var(--nv-palette-neutral-500), var(--nv-brand) 8%);
|
|
--nv-light-text-disabled: var(--nv-palette-neutral-400);
|
|
--nv-light-text-inverted: var(--nv-palette-neutral-0);
|
|
|
|
/* --- Borders ------------------------------------------------------------ */
|
|
--nv-light-border-subtle: rgb(28 24 18 / 0.07);
|
|
--nv-light-border: rgb(28 24 18 / 0.13);
|
|
--nv-light-border-strong: rgb(28 24 18 / 0.22);
|
|
|
|
/* --- Accent --------------------------------------------------------------
|
|
Every filled color is pushed away from the middle of the lightness range
|
|
before it is used. A mid-tone is the one place where neither white nor
|
|
black text reaches 4.5:1, so a brand that lands there is darkened until
|
|
white works — or, if it is already light, lightened until black does. The
|
|
brand's hue and chroma are untouched; only its lightness moves, and only
|
|
as far as it has to. */
|
|
--nv-light-accent: oklch(from var(--nv-brand) calc(
|
|
clamp(0, (var(--nv-contrast-pivot) - l) * 100, 1) * min(l, var(--nv-contrast-dark)) +
|
|
(1 - clamp(0, (var(--nv-contrast-pivot) - l) * 100, 1)) * max(l, var(--nv-contrast-light))
|
|
) clamp(0.02, c, 0.25) h);
|
|
--nv-light-accent-hover: oklch(from var(--nv-light-accent) calc(l - 0.045) c h);
|
|
--nv-light-accent-active: oklch(from var(--nv-light-accent) calc(l - 0.09) c h);
|
|
--nv-light-accent-subtle: oklch(from var(--nv-light-accent) 0.965 calc(c * 0.16) h);
|
|
--nv-light-accent-subtle-hover: oklch(from var(--nv-light-accent) 0.93 calc(c * 0.22) h);
|
|
--nv-light-accent-border: oklch(from var(--nv-light-accent) l c h / 0.32);
|
|
--nv-light-accent-text: oklch(from var(--nv-light-accent) clamp(0.3, calc(l - 0.12), 0.5) c h);
|
|
/* Text that lands on the color itself. The clamp is a step function: below
|
|
the lightness threshold it resolves to 1 and the text goes near-white,
|
|
above it to 0 and the text goes near-black — so a yellow brand gets dark
|
|
labels and a navy one gets light labels, with no second token to set. */
|
|
--nv-light-on-accent: oklch(from var(--nv-light-accent)
|
|
calc(0.14 + clamp(0, (0.68 - l) * 100, 1) * 0.84)
|
|
calc(c * 0.2) h);
|
|
|
|
/* --- Success ------------------------------------------------------------- */
|
|
--nv-light-success: oklch(from
|
|
color-mix(in oklab, var(--nv-palette-success-500), var(--nv-brand) var(--nv-brand-harmony))
|
|
calc(
|
|
clamp(0, (var(--nv-contrast-pivot) - l) * 100, 1) * min(l, var(--nv-contrast-dark)) +
|
|
(1 - clamp(0, (var(--nv-contrast-pivot) - l) * 100, 1)) * max(l, var(--nv-contrast-light))
|
|
) clamp(0.02, c, 0.25) h);
|
|
--nv-light-success-hover: oklch(from var(--nv-light-success) calc(l - 0.05) c h);
|
|
--nv-light-success-subtle: oklch(from var(--nv-light-success) 0.965 calc(c * 0.16) h);
|
|
--nv-light-success-border: oklch(from var(--nv-light-success) l c h / 0.3);
|
|
--nv-light-success-text: oklch(from var(--nv-light-success) clamp(0.3, calc(l - 0.1), 0.48) c h);
|
|
--nv-light-on-success: oklch(from var(--nv-light-success)
|
|
calc(0.14 + clamp(0, (0.68 - l) * 100, 1) * 0.84)
|
|
calc(c * 0.2) h);
|
|
|
|
/* --- Warning -------------------------------------------------------------- */
|
|
/* Amber starts above the pivot on purpose. Pushed the other way it lands as
|
|
brown, which reads as neither a warning nor a color anyone chose. */
|
|
--nv-light-warning: oklch(from
|
|
color-mix(in oklab, var(--nv-palette-warning-400), var(--nv-brand) var(--nv-brand-harmony))
|
|
calc(
|
|
clamp(0, (var(--nv-contrast-pivot) - l) * 100, 1) * min(l, var(--nv-contrast-dark)) +
|
|
(1 - clamp(0, (var(--nv-contrast-pivot) - l) * 100, 1)) * max(l, var(--nv-contrast-light))
|
|
) clamp(0.02, c, 0.25) h);
|
|
--nv-light-warning-hover: oklch(from var(--nv-light-warning) calc(l - 0.05) c h);
|
|
--nv-light-warning-subtle: oklch(from var(--nv-light-warning) 0.965 calc(c * 0.16) h);
|
|
--nv-light-warning-border: oklch(from var(--nv-light-warning) l c h / 0.32);
|
|
--nv-light-warning-text: oklch(from var(--nv-light-warning) clamp(0.3, calc(l - 0.1), 0.48) c h);
|
|
--nv-light-on-warning: oklch(from var(--nv-light-warning)
|
|
calc(0.14 + clamp(0, (0.68 - l) * 100, 1) * 0.84)
|
|
calc(c * 0.2) h);
|
|
|
|
/* --- Danger ---------------------------------------------------------------- */
|
|
--nv-light-danger: oklch(from
|
|
color-mix(in oklab, var(--nv-palette-danger-500), var(--nv-brand) var(--nv-brand-harmony))
|
|
calc(
|
|
clamp(0, (var(--nv-contrast-pivot) - l) * 100, 1) * min(l, var(--nv-contrast-dark)) +
|
|
(1 - clamp(0, (var(--nv-contrast-pivot) - l) * 100, 1)) * max(l, var(--nv-contrast-light))
|
|
) clamp(0.02, c, 0.25) h);
|
|
--nv-light-danger-hover: oklch(from var(--nv-light-danger) calc(l - 0.05) c h);
|
|
--nv-light-danger-active: oklch(from var(--nv-light-danger) calc(l - 0.1) c h);
|
|
--nv-light-danger-subtle: oklch(from var(--nv-light-danger) 0.965 calc(c * 0.16) h);
|
|
--nv-light-danger-border: oklch(from var(--nv-light-danger) l c h / 0.3);
|
|
--nv-light-danger-text: oklch(from var(--nv-light-danger) clamp(0.3, calc(l - 0.1), 0.48) c h);
|
|
--nv-light-on-danger: oklch(from var(--nv-light-danger)
|
|
calc(0.14 + clamp(0, (0.68 - l) * 100, 1) * 0.84)
|
|
calc(c * 0.2) h);
|
|
|
|
/* --- Depth and material -------------------------------------------------
|
|
Three shadow colors build every elevation level: an ambient contact
|
|
shadow, a soft penumbra, and a deeper umbra for high elevations. */
|
|
--nv-light-shadow-ambient: rgb(40 32 20 / 0.08);
|
|
--nv-light-shadow-penumbra: rgb(40 32 20 / 0.1);
|
|
--nv-light-shadow-umbra: rgb(40 32 20 / 0.16);
|
|
--nv-light-shadow-inset: rgb(40 32 20 / 0.14);
|
|
--nv-light-highlight: rgb(255 255 255 / 0.9);
|
|
--nv-light-highlight-strong: rgb(255 255 255 / 1);
|
|
|
|
/* Paper already has a border doing the contact work, so the outline only
|
|
softens it; the bottom shade is faint for the same reason. */
|
|
--nv-light-lowlight: rgb(40 32 20 / 0.09);
|
|
--nv-light-outline: rgb(40 32 20 / 0.05);
|
|
|
|
--nv-light-sheen-top: rgb(255 255 255 / 0.9);
|
|
--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-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-shade: rgb(40 32 20 / 0.13);
|
|
|
|
/* Light surfaces are already lighter than their canvas; the shadow alone
|
|
carries elevation, so there is nothing to add. */
|
|
--nv-light-elevation-tint-1: transparent;
|
|
--nv-light-elevation-tint-2: transparent;
|
|
--nv-light-elevation-tint-3: transparent;
|
|
--nv-light-elevation-tint-4: transparent;
|
|
--nv-light-elevation-tint-5: transparent;
|
|
|
|
--nv-light-material-frosted: rgb(252 252 250 / 0.56);
|
|
--nv-light-material-glass: rgb(252 252 250 / 0.4);
|
|
--nv-light-material-border: rgb(255 255 255 / 0.72);
|
|
|
|
/* --- Window controls --------------------------------------------------------- */
|
|
--nv-light-window-close: var(--nv-palette-window-close);
|
|
--nv-light-window-minimize: var(--nv-palette-window-minimize);
|
|
--nv-light-window-zoom: var(--nv-palette-window-zoom);
|
|
--nv-light-window-inactive: var(--nv-palette-neutral-300);
|
|
--nv-light-window-control-ring: rgb(0 0 0 / 0.16);
|
|
--nv-light-window-glyph: rgb(0 0 0 / 0.55);
|
|
|
|
/* --- Interaction ----------------------------------------------------------- */
|
|
--nv-light-focus-ring: oklch(from var(--nv-light-accent) l c h / 0.4);
|
|
--nv-light-focus-ring-contrast: rgb(255 255 255 / 0.9);
|
|
--nv-light-selection: oklch(from var(--nv-light-accent) l c h / 0.2);
|
|
}
|