site: rebuilt on Niveus, and the palette stops being a copy
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>
This commit is contained in:
parent
9ab8a7a426
commit
6490e5c759
89 changed files with 20804 additions and 1363 deletions
529
docs/Website/app.css
Normal file
529
docs/Website/app.css
Normal file
|
|
@ -0,0 +1,529 @@
|
|||
/* SPDX-License-Identifier: Apache-2.0 */
|
||||
/* ==========================================================================
|
||||
The SchOS site — layout only
|
||||
--------------------------------------------------------------------------
|
||||
Everything here is *arrangement*: where things sit, how wide they get, when
|
||||
they stack. Not one colour, shadow, radius, type size or duration is stated
|
||||
in this file — those come from Niveus, which is the whole point of the
|
||||
split. A hex value in here means the system is missing a token, and the
|
||||
answer is to add one rather than to work around it.
|
||||
|
||||
The one exception is `--nv-brand`, which is declared in the pages: it is
|
||||
SCH_ACCENT from include/theme.h, and it is an input to the system rather
|
||||
than a restatement of it.
|
||||
========================================================================== */
|
||||
|
||||
/* --- The page shell -------------------------------------------------------- */
|
||||
|
||||
.shell {
|
||||
max-inline-size: var(--nv-layout-max-width);
|
||||
margin-inline: auto;
|
||||
padding-inline: var(--nv-layout-gutter);
|
||||
}
|
||||
|
||||
.section {
|
||||
padding-block: var(--nv-space-20);
|
||||
}
|
||||
|
||||
.section__head {
|
||||
max-inline-size: var(--nv-measure);
|
||||
margin-block-end: var(--nv-space-8);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nv-space-3);
|
||||
}
|
||||
|
||||
/* A closing remark under a figure. Narrower than the figure, so it reads as
|
||||
commentary on it rather than as the next section. */
|
||||
.section__note {
|
||||
max-inline-size: var(--nv-measure);
|
||||
margin-block-start: var(--nv-space-5);
|
||||
}
|
||||
|
||||
/* The flag in the bar. Deliberately small: the site is about an operating
|
||||
system, and the flag is a statement, not a subject. */
|
||||
.pride img {
|
||||
display: block;
|
||||
block-size: var(--nv-space-4);
|
||||
inline-size: auto;
|
||||
border-radius: var(--nv-radius-xs);
|
||||
}
|
||||
|
||||
@media (max-width: 60rem) {
|
||||
.appearance { display: none; }
|
||||
}
|
||||
|
||||
/* --- Hero ------------------------------------------------------------------ */
|
||||
|
||||
.hero {
|
||||
display: grid;
|
||||
gap: var(--nv-space-12);
|
||||
padding-block: var(--nv-space-16) var(--nv-space-8);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (min-width: 62rem) {
|
||||
.hero {
|
||||
grid-template-columns: 5fr 7fr;
|
||||
gap: var(--nv-space-14);
|
||||
}
|
||||
}
|
||||
|
||||
.hero__copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nv-space-5);
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.hero__title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--nv-space-4);
|
||||
font-size: clamp(var(--nv-font-size-4xl), 7vw, var(--nv-font-size-5xl));
|
||||
}
|
||||
|
||||
.hero__mark {
|
||||
inline-size: 1.15em;
|
||||
block-size: 1.15em;
|
||||
}
|
||||
|
||||
/* --- The console ----------------------------------------------------------- */
|
||||
/*
|
||||
A terminal is a recess, not a card that happens to hold monospace: the
|
||||
material says "this is a window into the machine" before the text does.
|
||||
*/
|
||||
|
||||
.console {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.console__bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--nv-space-3);
|
||||
padding: var(--nv-space-2) var(--nv-space-3);
|
||||
border-block-end: var(--nv-border-width) solid var(--nv-color-border-subtle);
|
||||
}
|
||||
|
||||
.console__log {
|
||||
margin: 0;
|
||||
padding: var(--nv-space-4);
|
||||
min-block-size: 18rem;
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
line-height: var(--nv-line-height-relaxed);
|
||||
background: var(--nv-color-surface-inset);
|
||||
box-shadow: var(--nv-elevation-inset-sm);
|
||||
}
|
||||
|
||||
/* The cursor the boot log types behind. */
|
||||
.console__log::after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
inline-size: 0.6em;
|
||||
block-size: 1em;
|
||||
vertical-align: text-bottom;
|
||||
background: var(--nv-color-accent);
|
||||
animation: console-blink 1.1s steps(2, start) infinite;
|
||||
}
|
||||
|
||||
@keyframes console-blink { 50% { opacity: 0; } }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.console__log::after { animation: none; }
|
||||
}
|
||||
|
||||
/* --- Origin, as a timeline -------------------------------------------------- */
|
||||
/*
|
||||
The rail is not decoration: these steps happened in this order, and the
|
||||
order is the claim the section makes. A grid would lose it.
|
||||
*/
|
||||
|
||||
.timeline {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nv-space-8);
|
||||
border-inline-start: var(--nv-border-width) solid var(--nv-color-border);
|
||||
padding-inline-start: var(--nv-space-6);
|
||||
}
|
||||
|
||||
.timeline__step {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nv-space-2);
|
||||
max-inline-size: var(--nv-measure);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.timeline__step::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inline-size: var(--nv-space-2);
|
||||
block-size: var(--nv-space-2);
|
||||
border-radius: var(--nv-radius-full);
|
||||
background: var(--nv-color-accent);
|
||||
inset-inline-start: calc(var(--nv-space-6) * -1 - var(--nv-space-1));
|
||||
inset-block-start: var(--nv-space-1);
|
||||
}
|
||||
|
||||
.timeline__when {
|
||||
color: var(--nv-color-text-tertiary);
|
||||
}
|
||||
|
||||
/* --- The stack -------------------------------------------------------------- */
|
||||
|
||||
.stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stack__layer {
|
||||
display: grid;
|
||||
grid-template-columns: 12rem 1fr;
|
||||
gap: var(--nv-space-4);
|
||||
align-items: baseline;
|
||||
padding: var(--nv-space-3) var(--nv-space-5);
|
||||
border-block-end: var(--nv-border-width) solid var(--nv-color-border-subtle);
|
||||
}
|
||||
|
||||
.stack__layer:last-child { border-block-end: 0; }
|
||||
|
||||
.stack__name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--nv-space-2);
|
||||
font-family: var(--nv-font-family-mono);
|
||||
color: var(--nv-color-text-primary);
|
||||
}
|
||||
|
||||
/* The three seams the section is about. They are the argument, so they carry
|
||||
the accent ground; everything else is the arrangement between them. */
|
||||
.stack__layer--seam {
|
||||
background: var(--nv-color-accent-subtle);
|
||||
box-shadow: inset var(--nv-border-width-strong) 0 0 var(--nv-color-accent);
|
||||
}
|
||||
|
||||
@media (max-width: 48rem) {
|
||||
.stack__layer {
|
||||
grid-template-columns: 1fr;
|
||||
gap: var(--nv-space-1);
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Hybrid: what a driver needs -------------------------------------------- */
|
||||
|
||||
.needs {
|
||||
display: grid;
|
||||
gap: var(--nv-space-4);
|
||||
margin-block-end: var(--nv-space-8);
|
||||
}
|
||||
|
||||
@media (min-width: 48rem) {
|
||||
.needs { grid-template-columns: repeat(2, 1fr); }
|
||||
}
|
||||
|
||||
/* --- Numbers ---------------------------------------------------------------- */
|
||||
|
||||
.figures {
|
||||
display: grid;
|
||||
gap: var(--nv-space-4);
|
||||
grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
|
||||
}
|
||||
|
||||
/* A card carries no padding of its own -- its parts do -- and a stat tile has
|
||||
no parts. */
|
||||
.figure {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nv-space-1);
|
||||
align-items: flex-start;
|
||||
padding: var(--nv-space-5);
|
||||
}
|
||||
|
||||
.figure__value {
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: var(--nv-letter-spacing-tight);
|
||||
}
|
||||
|
||||
/* --- PSOGM ------------------------------------------------------------------ */
|
||||
|
||||
.crash {
|
||||
display: grid;
|
||||
gap: var(--nv-space-8);
|
||||
align-items: center;
|
||||
padding: var(--nv-space-12);
|
||||
}
|
||||
|
||||
@media (min-width: 48rem) {
|
||||
.crash { grid-template-columns: auto 1fr; }
|
||||
}
|
||||
|
||||
/* The bed's label is a caption for the whole region, not the first cell of
|
||||
its grid. */
|
||||
.crash .nv-bed__label {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.crash__mark {
|
||||
inline-size: var(--nv-space-20);
|
||||
block-size: auto;
|
||||
}
|
||||
|
||||
.crash__copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nv-space-4);
|
||||
}
|
||||
|
||||
/* --- The closing band -------------------------------------------------------- */
|
||||
|
||||
/* Padded here for the same reason .figure is: a card's padding lives on its
|
||||
parts, and this band has none -- it is a mark, some copy and two buttons. */
|
||||
.getband {
|
||||
display: grid;
|
||||
gap: var(--nv-space-8);
|
||||
align-items: center;
|
||||
padding: var(--nv-space-8);
|
||||
}
|
||||
|
||||
@media (min-width: 48rem) {
|
||||
.getband { grid-template-columns: auto 1fr; }
|
||||
}
|
||||
|
||||
.getband__mark {
|
||||
inline-size: var(--nv-space-20);
|
||||
block-size: auto;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.getband__copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nv-space-4);
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
/* --- Cards ------------------------------------------------------------------- */
|
||||
|
||||
/* Niveus's reset makes images block-level, which is right nearly everywhere
|
||||
and wrong for a mark that belongs beside a title. A flex row rather than
|
||||
`display: inline`, so the two stay aligned whatever the title does. */
|
||||
.nv-card__title:has(.cardmark) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--nv-space-2);
|
||||
}
|
||||
|
||||
.cardmark {
|
||||
inline-size: 1.2em;
|
||||
block-size: 1.2em;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
/* --- Footer ------------------------------------------------------------------ */
|
||||
|
||||
.site-footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nv-space-5);
|
||||
padding-block: var(--nv-space-8) var(--nv-space-16);
|
||||
}
|
||||
|
||||
.site-footer__row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--nv-space-2) var(--nv-space-6);
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
padding-block-start: var(--nv-space-6);
|
||||
}
|
||||
|
||||
.pride-note {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--nv-space-3);
|
||||
}
|
||||
|
||||
.pride-note img {
|
||||
block-size: var(--nv-space-4);
|
||||
inline-size: auto;
|
||||
border-radius: var(--nv-radius-xs);
|
||||
}
|
||||
|
||||
/* --- Reveal on scroll -------------------------------------------------------- */
|
||||
/*
|
||||
app.js adds .is-revealed. Without the script every section is simply
|
||||
visible, which is the only acceptable failure mode for decoration.
|
||||
*/
|
||||
|
||||
.section[data-reveal] {
|
||||
opacity: 0;
|
||||
translate: 0 var(--nv-travel-md);
|
||||
transition:
|
||||
opacity var(--nv-duration-slow) var(--nv-ease-entrance),
|
||||
translate var(--nv-duration-slow) var(--nv-ease-entrance);
|
||||
}
|
||||
|
||||
.section[data-reveal].is-revealed {
|
||||
opacity: 1;
|
||||
translate: 0 0;
|
||||
}
|
||||
|
||||
/* --- Cards that are entirely a link ------------------------------------------ */
|
||||
/*
|
||||
The whole card is the target -- `.nv-card--interactive` says so by lifting
|
||||
under the pointer -- so an underline on the title is a second affordance for
|
||||
the same thing, and it reads as a link *inside* a link.
|
||||
*/
|
||||
|
||||
.nv-card__link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* --- Command blocks ----------------------------------------------------------- */
|
||||
/*
|
||||
A command is the one thing on these pages that must be copied exactly, so it
|
||||
gets a recess of its own rather than sitting in the card's own material.
|
||||
`white-space: pre` and not `pre-wrap`: a wrapped shell command is a command
|
||||
that can be mistyped.
|
||||
*/
|
||||
|
||||
.cmd {
|
||||
margin: 0;
|
||||
padding: var(--nv-space-3) var(--nv-space-4);
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
line-height: var(--nv-line-height-relaxed);
|
||||
background: var(--nv-color-surface-inset);
|
||||
border-radius: var(--nv-radius-nested-md);
|
||||
box-shadow: var(--nv-elevation-inset-sm);
|
||||
}
|
||||
|
||||
/* --- The documentation reader ------------------------------------------------ */
|
||||
/*
|
||||
Index, document, contents. Three columns that become one on a narrow screen,
|
||||
in that order of importance: the document is the page, the index is how you
|
||||
got there, and the contents panel is a convenience.
|
||||
*/
|
||||
|
||||
.docshell {
|
||||
max-inline-size: var(--nv-layout-max-width);
|
||||
margin-inline: auto;
|
||||
padding-inline: var(--nv-layout-gutter);
|
||||
padding-block: var(--nv-space-8) var(--nv-space-16);
|
||||
display: grid;
|
||||
gap: var(--nv-space-8);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
@media (min-width: 64rem) {
|
||||
.docshell {
|
||||
grid-template-columns: 15rem minmax(0, 1fr) 14rem;
|
||||
gap: var(--nv-space-10);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 64rem) {
|
||||
.docshell__rail { display: none; }
|
||||
}
|
||||
|
||||
/* Both panels follow the reader down the page, under the bar rather than
|
||||
behind it. */
|
||||
.docshell__aside,
|
||||
.docshell__rail {
|
||||
position: sticky;
|
||||
inset-block-start: var(--nv-space-16);
|
||||
max-block-size: calc(100vh - var(--nv-space-24));
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* A vertical nav that is a panel in a page, not the chrome of an app: no
|
||||
surface of its own, and no full-height rule down its edge. */
|
||||
.docnav {
|
||||
position: static; /* the column is what sticks, not the nav */
|
||||
background: none;
|
||||
border: 0;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
inline-size: 100%; /* the sidebar component is a fixed 15rem */
|
||||
block-size: auto;
|
||||
}
|
||||
|
||||
.docnav .nv-nav__list {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: var(--nv-space-px);
|
||||
margin-block-end: var(--nv-space-5);
|
||||
}
|
||||
|
||||
/* A nav item in a bar is one line by definition; a document title is not. The
|
||||
contents panel is 14rem wide and some headings are longer than that, so they
|
||||
wrap rather than being cut off mid-word -- a truncated entry is a
|
||||
destination you cannot recognise. */
|
||||
.docnav .nv-nav__item {
|
||||
justify-content: flex-start;
|
||||
inline-size: 100%;
|
||||
white-space: normal;
|
||||
block-size: auto;
|
||||
min-block-size: var(--nv-control-height-sm);
|
||||
padding-block: var(--nv-space-1-5);
|
||||
text-align: start;
|
||||
}
|
||||
|
||||
/* A contents panel is a shape: depth reads as indentation. */
|
||||
.toc-lvl3 .nv-nav__item { padding-inline-start: var(--nv-space-5); }
|
||||
.toc-lvl4 .nv-nav__item { padding-inline-start: var(--nv-space-8); }
|
||||
|
||||
/* --- The document itself ----------------------------------------------------- */
|
||||
|
||||
.doc {
|
||||
max-inline-size: none; /* the column already sets the measure */
|
||||
}
|
||||
|
||||
.doc > * + * {
|
||||
margin-block-start: var(--nv-space-4);
|
||||
}
|
||||
|
||||
.doc h1,
|
||||
.doc h2,
|
||||
.doc h3,
|
||||
.doc h4 {
|
||||
margin-block-start: var(--nv-space-8);
|
||||
scroll-margin-block-start: var(--nv-space-20);
|
||||
}
|
||||
|
||||
.doc h2 {
|
||||
padding-block-end: var(--nv-space-2);
|
||||
border-block-end: var(--nv-border-width) solid var(--nv-color-border-subtle);
|
||||
}
|
||||
|
||||
.doc__pre {
|
||||
margin: 0;
|
||||
padding: var(--nv-space-4);
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
line-height: var(--nv-line-height-relaxed);
|
||||
background: var(--nv-color-surface-inset);
|
||||
border-radius: var(--nv-radius-md);
|
||||
box-shadow: var(--nv-elevation-inset-sm);
|
||||
}
|
||||
|
||||
.doc blockquote {
|
||||
margin-inline: 0;
|
||||
padding: var(--nv-space-1) var(--nv-space-5);
|
||||
border-inline-start: var(--nv-border-width-strong) solid var(--nv-color-accent-border);
|
||||
color: var(--nv-color-text-secondary);
|
||||
}
|
||||
|
||||
.doc ul,
|
||||
.doc ol {
|
||||
padding-inline-start: var(--nv-space-6);
|
||||
}
|
||||
|
||||
.doc li + li {
|
||||
margin-block-start: var(--nv-space-2);
|
||||
}
|
||||
Loading…
Reference in a new issue