mirror of
https://github.com/Nexoniarz/Niveus.git
synced 2026-11-03 04:34:26 +00:00
Glaze is glass with a dial: --nv-glass-frost (0 clear .. 1 heavy) moves backdrop blur, tint and saturation together, read where the surface is drawn so any subtree or slider can change it. A masked rim and a specular bloom follow the pointer via niveus.js; without it the light sits at its default and the material still reads. Falls back to the elevated solid under reduced transparency or without backdrop-filter. New components: .nv-slider (binds to a token with data-nv-var), .nv-calendar, .nv-timepicker (24-hour by default, optional 24H / AM/PM switch, value always HH:MM), and .nv-picker with .nv-menu--listbox (search, type-ahead, keys). All render without niveus.js except where noted. GUIDELINES.md is the normative rulebook for people and AI agents. check.py, build.py and build.py --check pass. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
215 lines
7.6 KiB
CSS
215 lines
7.6 KiB
CSS
/* ==========================================================================
|
|
Niveus — Surface
|
|
--------------------------------------------------------------------------
|
|
The material vocabulary, expressed as classes. Every other component is
|
|
built from these; applications use them directly for anything the system
|
|
does not ship as a named component.
|
|
|
|
.nv-surface a container using the solid material
|
|
.nv-material-{type} apply a material to any element
|
|
.nv-elevation-{0..5} apply a depth level to any element
|
|
|
|
A material and an elevation are independent: .nv-material-solid
|
|
.nv-elevation-3 is a legitimate combination. What is not legitimate is a
|
|
raw box-shadow that belongs to neither scale.
|
|
========================================================================== */
|
|
|
|
.nv-surface {
|
|
background-color: var(--nv-color-surface);
|
|
color: var(--nv-color-text-primary);
|
|
border: var(--nv-border-width) solid var(--nv-color-border);
|
|
border-radius: var(--nv-radius-lg);
|
|
transition: var(--nv-transition-surface);
|
|
}
|
|
|
|
/* --- Materials ------------------------------------------------------------- */
|
|
|
|
.nv-material-solid {
|
|
background-color: var(--nv-material-solid-bg);
|
|
border: var(--nv-border-width) solid var(--nv-material-solid-border);
|
|
box-shadow: var(--nv-material-solid-shadow);
|
|
backdrop-filter: none;
|
|
}
|
|
|
|
.nv-material-elevated {
|
|
background-color: var(--nv-material-elevated-bg);
|
|
border: var(--nv-border-width) solid var(--nv-material-elevated-border);
|
|
box-shadow: var(--nv-material-elevated-shadow);
|
|
backdrop-filter: none;
|
|
}
|
|
|
|
.nv-material-inset {
|
|
background-color: var(--nv-material-inset-bg);
|
|
border: var(--nv-border-width) solid var(--nv-material-inset-border);
|
|
box-shadow: var(--nv-material-inset-shadow);
|
|
backdrop-filter: none;
|
|
}
|
|
|
|
.nv-material-frosted {
|
|
background-color: var(--nv-material-frosted-bg);
|
|
border: var(--nv-border-width) solid var(--nv-material-frosted-border);
|
|
box-shadow: var(--nv-material-frosted-shadow);
|
|
backdrop-filter: var(--nv-backdrop-frosted);
|
|
-webkit-backdrop-filter: var(--nv-backdrop-frosted);
|
|
}
|
|
|
|
.nv-material-glass {
|
|
background-color: var(--nv-material-glass-bg);
|
|
border: var(--nv-border-width) solid var(--nv-material-glass-border);
|
|
box-shadow: var(--nv-material-glass-shadow);
|
|
backdrop-filter: var(--nv-backdrop-glass);
|
|
-webkit-backdrop-filter: var(--nv-backdrop-glass);
|
|
}
|
|
|
|
/* --- Glaze ------------------------------------------------------------------
|
|
Glass with a dial, and a rim that catches the light.
|
|
|
|
.nv-material-glaze the material
|
|
--nv-glass-frost: 0 … 1 clear glass … heavy frost (set anywhere)
|
|
|
|
Frost is read here, where the surface is drawn, not copied into a token
|
|
upstream: that is what lets a subtree (or a slider the visitor holds) change
|
|
it and have every surface inside follow. It moves three things together —
|
|
how far the backdrop is blurred, how much of the tint is laid over it, and
|
|
how much color the blur gives back — because a pane that changed only one
|
|
of them would stop looking like the same pane.
|
|
|
|
The rim is a 1.5px gradient border made with a masked ::before: bright on
|
|
the side facing the light, faint on the far side. ::after is the specular
|
|
bloom, a soft glow under the pointer. niveus.js sets --nv-light-x, -y and
|
|
-angle on the surface; without it both sit at their defaults (light from the
|
|
top left), so the material still reads as lit, just not as following you.
|
|
|
|
Zero specificity on position: a menu or dialog using it keeps the positioning
|
|
its own component gave it. */
|
|
|
|
:where(.nv-material-glaze) {
|
|
position: relative;
|
|
}
|
|
|
|
.nv-material-glaze {
|
|
--_frost: var(--nv-glass-frost);
|
|
isolation: isolate;
|
|
background-color: color-mix(
|
|
in oklab,
|
|
var(--nv-color-material-glaze)
|
|
calc(var(--nv-glaze-tint-min) + (var(--nv-glaze-tint-max) - var(--nv-glaze-tint-min)) * var(--_frost)),
|
|
transparent
|
|
);
|
|
border: var(--nv-border-width) solid transparent;
|
|
box-shadow:
|
|
var(--nv-highlight-top-strong),
|
|
inset 0 0 1.5rem -0.5rem var(--nv-color-glaze-specular),
|
|
var(--nv-outline),
|
|
var(--nv-elevation-4);
|
|
backdrop-filter:
|
|
blur(calc(4px + var(--_frost) * (var(--nv-glaze-blur-max) - 4px)))
|
|
saturate(calc(140% + var(--_frost) * 60%));
|
|
-webkit-backdrop-filter:
|
|
blur(calc(4px + var(--_frost) * (var(--nv-glaze-blur-max) - 4px)))
|
|
saturate(calc(140% + var(--_frost) * 60%));
|
|
}
|
|
|
|
.nv-material-glaze::before,
|
|
.nv-material-glaze::after {
|
|
content: "";
|
|
position: absolute;
|
|
inset: calc(var(--nv-border-width) * -1);
|
|
border-radius: inherit;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.nv-material-glaze::before {
|
|
padding: var(--nv-glaze-rim);
|
|
background: linear-gradient(
|
|
var(--nv-light-angle, 135deg),
|
|
var(--nv-color-glaze-rim-lit) 0%,
|
|
transparent 38%,
|
|
transparent 62%,
|
|
var(--nv-color-glaze-rim-far) 100%
|
|
);
|
|
mask:
|
|
linear-gradient(currentcolor 0 0) content-box,
|
|
linear-gradient(currentcolor 0 0);
|
|
mask-composite: exclude;
|
|
-webkit-mask-composite: xor;
|
|
}
|
|
|
|
.nv-material-glaze::after {
|
|
z-index: -1;
|
|
background: radial-gradient(
|
|
circle 16rem at var(--nv-light-x, 15%) var(--nv-light-y, 0%),
|
|
var(--nv-color-glaze-specular),
|
|
transparent 70%
|
|
);
|
|
}
|
|
|
|
/* Where translucency cannot be honoured, glaze falls back to the elevated
|
|
solid, as the other glass materials do. */
|
|
@media (prefers-reduced-transparency: reduce) {
|
|
.nv-material-glaze {
|
|
background-color: var(--nv-color-surface-elevated);
|
|
backdrop-filter: none;
|
|
-webkit-backdrop-filter: none;
|
|
}
|
|
.nv-material-glaze::after { display: none; }
|
|
}
|
|
|
|
@supports not (backdrop-filter: blur(1px)) {
|
|
.nv-material-glaze { background-color: var(--nv-color-surface-elevated); }
|
|
.nv-material-glaze::after { display: none; }
|
|
}
|
|
|
|
/* --- Elevation -------------------------------------------------------------
|
|
Elevation utilities carry three things at once, because a lifted surface is
|
|
never only a shadow: the shadow itself, the highlight along its top edge,
|
|
and — on a dark canvas, where a shadow has nothing left to darken — a tint
|
|
that lets more light reach the surface the higher it sits. The tint is
|
|
transparent in light appearance, so the two read as one scale. */
|
|
|
|
.nv-elevation-0 { box-shadow: var(--nv-elevation-0); }
|
|
|
|
.nv-elevation-1 {
|
|
box-shadow: var(--nv-highlight-top), var(--nv-elevation-1);
|
|
background-image: linear-gradient(var(--nv-color-elevation-tint-1) 0 0);
|
|
}
|
|
|
|
.nv-elevation-2 {
|
|
box-shadow: var(--nv-highlight-top), var(--nv-elevation-2);
|
|
background-image: linear-gradient(var(--nv-color-elevation-tint-2) 0 0);
|
|
}
|
|
|
|
.nv-elevation-3 {
|
|
box-shadow: var(--nv-highlight-top), var(--nv-elevation-3);
|
|
background-image: linear-gradient(var(--nv-color-elevation-tint-3) 0 0);
|
|
}
|
|
|
|
.nv-elevation-4 {
|
|
box-shadow: var(--nv-highlight-top), var(--nv-elevation-4);
|
|
background-image: linear-gradient(var(--nv-color-elevation-tint-4) 0 0);
|
|
}
|
|
|
|
.nv-elevation-5 {
|
|
box-shadow: var(--nv-highlight-top-strong), var(--nv-elevation-5);
|
|
background-image: linear-gradient(var(--nv-color-elevation-tint-5) 0 0);
|
|
}
|
|
|
|
.nv-elevation-inset-sm { box-shadow: var(--nv-elevation-inset-sm); }
|
|
.nv-elevation-inset-md { box-shadow: var(--nv-elevation-inset-md); }
|
|
.nv-elevation-inset-lg { box-shadow: var(--nv-elevation-inset-lg); }
|
|
|
|
/* --- Divider ---------------------------------------------------------------- */
|
|
|
|
.nv-divider {
|
|
border: 0;
|
|
border-top: var(--nv-border-width) solid var(--nv-color-border-subtle);
|
|
margin-block: var(--nv-space-4);
|
|
}
|
|
|
|
.nv-divider--vertical {
|
|
border-top: 0;
|
|
border-left: var(--nv-border-width) solid var(--nv-color-border-subtle);
|
|
margin-block: 0;
|
|
margin-inline: var(--nv-space-4);
|
|
align-self: stretch;
|
|
}
|