/* ========================================================================== 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; }