SchOS/docs/Website/app.css
Nexoniarz c9790df04c site, branding: skeuomorphic marks, and the new Niveus components
The marks are now drawn the way Niveus draws its keys: one light from the
upper left, gloss on the faces that catch it, a specular line on the edges
that face it, and soft contact shadows.

- Shift: shaded slabs, each casting onto the one below. shift-mask.svg is
  untouched, so the kernel's logo.h coverage mask is unchanged.
- Uberblock: bevelled slots in a groove; the newest slot and the root glow.
- USB stick: moulded body, steel connector, a lit activity LED.
- Pride flag: cloth folds and a hem over the flag's own colours.

The pages use the components the new Niveus added: both terminals are
.nv-window frames, the origin and first-boot sequences are .nv-steps, the
numbers are a .nv-stat-group, and each feature card has an .nv-tile glyph.

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

498 lines
13 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 ------------------------------------------------------------------ */
.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 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 {
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);
}
/* --- 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;
}