Niveus is the interface language the project uses from here on -- this site
first, applications after it. Rebuilding the site on it is what proves the
system is one, rather than a folder of CSS with a README.
**`style.css` is gone.** 21 KB that transcribed include/theme.h by hand:
palette, type scale, shadows, spacing, every component. The intent was right --
"a site describing an operating system should look like the operating system"
-- and the mechanism was a copy, which means two files that have to be kept in
step by remembering to. What replaced it is `app.css`, which is layout and
nothing else, and one declaration in each page:
:root { --nv-brand: #7C5CFF; }
That is SCH_ACCENT. Niveus derives the accent and its states, the link colour,
the focus ring, the tint in the greys and how far the status colours lean, for
light and dark both. One number to keep true instead of forty.
**The site is now adaptive.** Dark by default, because the OS is dark, with a
Light/Dark/Auto group in the bar that is real. The choice is kept in
localStorage and re-applied by an inline script before first paint, so the page
never renders in one appearance and corrects itself while you watch.
**PSOGM keeps its one purple, and now says so structurally.** #4B1D9A is
spelled the way Niveus spells a subtree with its own colour -- `data-nv-brand`
with its own `--nv-brand` on that section -- so every role inside re-derives
from it rather than being painted on. Still exactly one place, which is what
branding/README.md asks for.
Content is unchanged; what changed is what it is made of. Cards are `.nv-card`,
tags are `.nv-badge`, the support table is `.nv-table`, the crash section is a
`.nv-bed`, and the docs reader's two rails are `.nv-nav--vertical` with the
current document marked by `aria-current` rather than a class of our own.
`docs.js` emits `.nv-table` and `.nv-code`, so a change to the system reaches
the documentation too.
Three things were found by looking at it in a browser rather than by reading
it, and all three are the same mistake: **a `.nv-card` carries no padding of
its own -- its parts do.** The stat tiles, the "One ISO, one stick" band and
the card headers were all built from bare children, so numbers ran to the card
edge, the USB mark hung off it, and a title and its badge sat at opposite ends
of a row meant for a title and a trailing slot. Where the component had a right
way to say it, the markup now says it that way; where it did not -- a tile has
no parts -- `app.css` supplies the padding and says why.
Checked: every nv- class used across the three pages and docs.js exists in the
CSS, every token referenced by app.css is defined, and app.css contains no hex
value at all. Niveus's own tools/check.py passes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
190 lines
5.6 KiB
CSS
190 lines
5.6 KiB
CSS
/* ==========================================================================
|
|
Niveus — Dialog
|
|
--------------------------------------------------------------------------
|
|
Built on the native <dialog> element, so focus trapping, the top layer,
|
|
Escape-to-close and inertness come from the platform rather than from
|
|
script.
|
|
|
|
<dialog class="nv-dialog"> … </dialog> opened with .showModal()
|
|
.nv-dialog--sheet enters from the block end
|
|
.nv-dialog--wide | --compact width
|
|
.nv-dialog__header / __body / __footer / __close
|
|
|
|
The dialog is the system's highest material: elevation 4 over a blurred
|
|
scrim. It enters by rising and settling, and leaves the way it came — the
|
|
motion is what tells the user the surface was always there, above the page.
|
|
========================================================================== */
|
|
|
|
.nv-dialog {
|
|
--_width: 30rem;
|
|
--_padding: var(--nv-space-6);
|
|
|
|
inline-size: min(var(--_width), calc(100vw - var(--nv-space-8)));
|
|
max-block-size: min(42rem, calc(100svh - var(--nv-space-12)));
|
|
padding: 0;
|
|
|
|
background-color: var(--nv-color-surface-elevated);
|
|
color: var(--nv-color-text-primary);
|
|
border: var(--nv-border-width) solid var(--nv-color-border-subtle);
|
|
border-radius: var(--nv-radius-xl);
|
|
box-shadow: var(--nv-highlight-top), var(--nv-elevation-4);
|
|
|
|
overflow: hidden;
|
|
|
|
/* Closed state. [open] below is the resting state; @starting-style replays
|
|
these values for the entrance. */
|
|
/* A dialog should look as though it came from the control that opened it.
|
|
nv.openDialog() sets --nv-origin to that control's position relative to
|
|
the dialog, so the entrance scale expands away from the trigger rather
|
|
than from the middle of the screen. Centred when nothing sets it. */
|
|
transform-origin: var(--nv-origin, 50% 50%);
|
|
|
|
opacity: 0;
|
|
scale: 0.97;
|
|
translate: 0 var(--nv-travel-md);
|
|
|
|
transition:
|
|
opacity var(--nv-duration-normal) var(--nv-ease-standard),
|
|
scale var(--nv-duration-normal) var(--nv-ease-standard),
|
|
translate var(--nv-duration-normal) var(--nv-ease-standard),
|
|
display var(--nv-duration-normal) allow-discrete,
|
|
overlay var(--nv-duration-normal) allow-discrete;
|
|
}
|
|
|
|
.nv-dialog[open] {
|
|
opacity: 1;
|
|
scale: 1;
|
|
translate: 0;
|
|
transition-timing-function: var(--nv-ease-entrance);
|
|
}
|
|
|
|
@starting-style {
|
|
.nv-dialog[open] {
|
|
opacity: 0;
|
|
scale: 0.97;
|
|
translate: 0 var(--nv-travel-md);
|
|
}
|
|
}
|
|
|
|
.nv-dialog::backdrop {
|
|
background-color: var(--nv-color-scrim);
|
|
backdrop-filter: var(--nv-backdrop-scrim);
|
|
-webkit-backdrop-filter: var(--nv-backdrop-scrim);
|
|
opacity: 0;
|
|
transition:
|
|
opacity var(--nv-duration-normal) var(--nv-ease-standard),
|
|
display var(--nv-duration-normal) allow-discrete,
|
|
overlay var(--nv-duration-normal) allow-discrete;
|
|
}
|
|
|
|
.nv-dialog[open]::backdrop {
|
|
opacity: 1;
|
|
}
|
|
|
|
@starting-style {
|
|
.nv-dialog[open]::backdrop {
|
|
opacity: 0;
|
|
}
|
|
}
|
|
|
|
/* showModal() does not stop the page behind from scrolling. */
|
|
html:has(.nv-dialog:modal) {
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* --- Widths -------------------------------------------------------------------- */
|
|
|
|
.nv-dialog--compact { --_width: 24rem; }
|
|
.nv-dialog--wide { --_width: 44rem; }
|
|
|
|
/* --- Sheet ----------------------------------------------------------------------
|
|
Anchored to the block end and full width. On small screens this is usually
|
|
the right form for anything with more than two actions. */
|
|
|
|
.nv-dialog--sheet {
|
|
--_width: 34rem;
|
|
|
|
/* Flush to the viewport edges — a sheet with side gaps and square bottom
|
|
corners reads as a mistake rather than as a sheet. */
|
|
inline-size: min(var(--_width), 100vw);
|
|
max-block-size: min(46rem, calc(100svh - var(--nv-space-10)));
|
|
margin-block-end: 0;
|
|
margin-block-start: auto;
|
|
border-start-start-radius: var(--nv-radius-xl);
|
|
border-start-end-radius: var(--nv-radius-xl);
|
|
border-end-start-radius: 0;
|
|
border-end-end-radius: 0;
|
|
scale: 1;
|
|
translate: 0 var(--nv-travel-lg);
|
|
}
|
|
|
|
@starting-style {
|
|
.nv-dialog--sheet[open] {
|
|
scale: 1;
|
|
translate: 0 var(--nv-travel-lg);
|
|
}
|
|
}
|
|
|
|
/* --- Parts ------------------------------------------------------------------------ */
|
|
|
|
.nv-dialog__header {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: var(--nv-space-4);
|
|
padding: var(--_padding);
|
|
padding-block-end: var(--nv-space-3);
|
|
}
|
|
|
|
.nv-dialog__title {
|
|
font: var(--nv-font-heading);
|
|
font-size: var(--nv-font-size-xl);
|
|
letter-spacing: var(--nv-letter-spacing-tight);
|
|
margin: 0;
|
|
}
|
|
|
|
.nv-dialog__description {
|
|
font: var(--nv-font-body);
|
|
color: var(--nv-color-text-secondary);
|
|
margin-block-start: var(--nv-space-2);
|
|
}
|
|
|
|
.nv-dialog__body {
|
|
padding: var(--_padding);
|
|
padding-block: var(--nv-space-2);
|
|
overflow-y: auto;
|
|
overscroll-behavior: contain;
|
|
color: var(--nv-color-text-secondary);
|
|
}
|
|
|
|
.nv-dialog__footer {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: flex-end;
|
|
gap: var(--nv-space-2);
|
|
padding: var(--_padding);
|
|
padding-block-start: var(--nv-space-4);
|
|
}
|
|
|
|
/* A footer over scrollable content needs its own ground: the inset tint and
|
|
hairline tell the user there is more above it. */
|
|
.nv-dialog__footer--divided {
|
|
background-color: var(--nv-color-background-subtle);
|
|
border-block-start: var(--nv-border-width) solid var(--nv-color-border-subtle);
|
|
}
|
|
|
|
.nv-dialog__close {
|
|
flex: none;
|
|
margin: calc(var(--nv-space-1) * -1);
|
|
}
|
|
|
|
/* A dialog laid out as a column keeps its header and footer fixed while only
|
|
the body scrolls. */
|
|
.nv-dialog:has(.nv-dialog__body) {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.nv-dialog:not([open]) {
|
|
display: none;
|
|
}
|