SchOS/docs/Website/app.css
Nexoniarz 7af579a20d site: a lit hero, new slogans, the wallpapers, and a better reader
Home:
- The hero stands on a light from the upper left and a pool of the
  accent, mixed from Niveus tokens so it follows the theme. The mark sits
  in an .nv-tile key under a slogan: "Every layer its own -- from the
  first instruction to the last pixel."
- New section headings: "A teaching kernel that outgrew its lessons",
  "Measured, not marketed", "One ISO. One stick. Your disks untouched."
- A Wallpapers section with the four new ones, each downloadable as a
  4K JPEG or as SVG. The four-card grids are two across, so none is left
  alone on its row.

Docs reader:
- An index filter, a breadcrumb with reading time and source path,
  code blocks framed with a language strip and a Copy key, previous and
  next keys at the foot, a reading-progress hairline, and the contents
  entry lit for the section on screen.
- The index lists the documents it was missing: scheduler, FAT, audio,
  LDLS, debugging and the hardware report.
- On a phone the page is no longer wider than the screen (a pre block
  was sizing the grid's auto column), and the index folds away.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-23 15:56:43 +02:00

690 lines
18 KiB
CSS

/* SPDX-License-Identifier: GPL-2.0-or-later */
/* ==========================================================================
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/gfx/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 ------------------------------------------------------------------ */
/* The band behind the hero, lit the way the Graphite wallpaper is: a light
from the upper left, and a pool of the accent on the surface the window
stands on. Mixed from the system's own tokens rather than from the picture,
so it follows the brand and the appearance instead of fighting the copy. */
.heroband {
background:
radial-gradient(ellipse 45% 38% at 68% 62%,
color-mix(in oklab, var(--nv-color-accent) 45%, transparent), transparent 72%),
radial-gradient(ellipse 60% 70% at 18% 0%,
color-mix(in oklab, var(--nv-color-text-primary) 7%, transparent), transparent 70%),
var(--nv-color-background);
padding-block-end: var(--nv-space-12);
}
.hero__tile {
flex: none;
}
.hero__slogan {
max-inline-size: 22ch;
}
.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;
}
/* --- Wallpapers ----------------------------------------------------------- */
/* Two across, not auto-fit: four of anything at three per row leaves one
alone on the last, which reads as an afterthought. */
.walls,
#docs .nv-card-grid {
display: grid;
gap: var(--nv-space-5);
grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (max-width: 48rem) {
.walls,
#docs .nv-card-grid { grid-template-columns: 1fr; }
}
.wall {
margin: 0;
overflow: hidden;
}
.wall__img {
inline-size: 100%;
block-size: auto;
aspect-ratio: 16 / 9;
object-fit: cover;
}
.wall__caption {
display: flex;
flex-direction: column;
gap: var(--nv-space-2);
padding: var(--nv-space-4) var(--nv-space-5) var(--nv-space-5);
}
/* --- The console ----------------------------------------------------------- */
/*
A terminal is a window into the machine, so it is drawn as one: a solid
.nv-window whose body is a recess the log is typed into. The traffic lights
are decoration here -- nothing on this page can be closed -- so they are
spans, hidden from assistive tech, rather than buttons that do nothing.
*/
.console {
min-inline-size: 0;
}
.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. */
#bootlog::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;
}
/* Only the boot log types; a finished transcript is as tall as it is. */
.console__log:not(#bootlog) { min-block-size: 0; }
@keyframes console-blink { 50% { opacity: 0; } }
@media (prefers-reduced-motion: reduce) {
#bootlog::after { animation: none; }
}
/* --- Origin, as steps ------------------------------------------------------- */
/*
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. .nv-steps cuts
the rail into the surface and lights what is done; all this adds is the
measure, because a step is a paragraph and not a label.
*/
.origin .nv-steps__body {
max-inline-size: var(--nv-measure);
padding-block-end: var(--nv-space-4);
display: flex;
flex-direction: column;
gap: var(--nv-space-1);
}
.origin .nv-steps__title {
font-size: var(--nv-font-size-lg);
}
.origin .nv-steps__text {
font-size: var(--nv-font-size-md);
color: var(--nv-color-text-secondary);
}
/* --- 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); }
}
/* --- 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 ------------------------------------------------------------------- */
/* A feature's tile belongs beside its title, not above it. A flex row rather than
`display: inline`, so the two stay aligned whatever the title does. */
.nv-card__title:has(.nv-tile) {
display: flex;
align-items: center;
gap: var(--nv-space-3);
}
.nv-card__title .nv-tile {
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 {
/* Wider than the rest of the site: the document column carries tables and
diagrams that the prose measure would squeeze, and both side panels have
to fit beside it. */
max-inline-size: calc(var(--nv-layout-max-width) + 16rem);
margin-inline: auto;
padding-inline: var(--nv-layout-gutter);
padding-block: var(--nv-space-8) var(--nv-space-16);
display: grid;
/* minmax(0, ...) even with one column: a code block is white-space: pre,
and an auto column grows to its longest line -- which on a phone made
the whole page wider than the screen. */
grid-template-columns: minmax(0, 1fr);
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; }
/* One column: the index stops following the reader and folds away, so
the document is the first thing on the screen rather than the fortieth. */
.docshell__aside {
position: static;
max-block-size: 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);
}
/* A code block is a small window: a raised strip naming the language, with
the copy key on it, over the recess the code sits in. */
.codeframe {
margin: 0;
border-radius: var(--nv-radius-md);
overflow: hidden;
background: var(--nv-color-surface-elevated);
box-shadow: var(--nv-elevation-1);
}
.codeframe__bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--nv-space-1) var(--nv-space-1) var(--nv-space-1) var(--nv-space-4);
border-block-end: var(--nv-border-width) solid var(--nv-color-border-subtle);
}
.codeframe__lang {
font-family: var(--nv-font-family-mono);
font-size: var(--nv-font-size-xs);
letter-spacing: var(--nv-letter-spacing-wide);
text-transform: uppercase;
color: var(--nv-color-text-tertiary);
}
.codeframe .doc__pre {
border-radius: 0;
}
.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);
}
/* --- The feature rail --------------------------------------------------------- */
/*
One declaration, because the component already does the rest: the equal
heights are the flex default, the snapping, the hidden scrollbar and the
fade that marks which end has more behind it are all .nv-scroller's. The
only thing it cannot know is how wide a card of *this* content wants to be,
and 18rem is a line where these carry a paragraph.
*/
.features {
--nv-scroller-item: 21rem;
}
/* --- The reader's furniture ----------------------------------------------- */
/* Where this document sits, and how long it is. */
.dochead {
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: var(--nv-space-2) var(--nv-space-4);
padding-block-end: var(--nv-space-4);
border-block-end: var(--nv-border-width) solid var(--nv-color-border-subtle);
}
.doc > .dochead + * {
margin-block-start: var(--nv-space-6);
}
/* Previous and next, as two keys. */
.docpager {
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--nv-space-4);
margin-block-start: var(--nv-space-12);
}
.docpager__key {
display: flex;
flex-direction: column;
gap: var(--nv-space-1);
padding: var(--nv-space-4) var(--nv-space-5);
text-decoration: none;
color: inherit;
}
.docpager__key--next {
text-align: end;
align-items: flex-end;
grid-column: 2;
}
@media (max-width: 40rem) {
.docpager { grid-template-columns: 1fr; }
.docpager__key--next { grid-column: auto; }
}
/* The filter well above the index. */
.docfind {
margin-block-end: var(--nv-space-5);
}
.docfind .nv-input {
inline-size: 100%;
}
/* Reading progress: a hairline of accent under the bar, growing from the
left. Transform rather than width, so it costs a composite and no layout. */
.readbar {
position: sticky;
inset-block-start: 0;
z-index: 10;
block-size: var(--nv-border-width-strong);
margin-block-end: calc(var(--nv-border-width-strong) * -1);
}
.readbar span {
display: block;
block-size: 100%;
background: var(--nv-color-accent);
box-shadow: 0 0 var(--nv-space-2) var(--nv-color-accent);
transform: scaleX(0);
transform-origin: left;
}
/* The contents entry for the section being read. */
#toc .nv-nav__item[aria-current="location"] {
color: var(--nv-color-accent-text);
}
/* The index's fold. Open on a wide screen, where it is a column; closed on a
narrow one (docs.js), where it would push the document out of view. */
.docindex > summary {
display: none;
}
@media (max-width: 64rem) {
.docindex > summary {
display: flex;
margin-block-end: var(--nv-space-3);
}
}