/* ---------------------------------------------------------------------------
   previous.li

   The page somebody lands on before they know anything about this project. It
   does not reproduce NeXTSTEP: the admin tool does that, and doing it twice in
   two registers would make neither convincing. What this page carries are
   allusions, and there are three of them.

     The colour        #555577, the workspace the original drew behind its
                       windows, read off a screenshot of the running system in
                       design/kit/base.css. Every screenshot on this page lies
                       on it, which is where a NeXTSTEP window lies.
     The title bar     Black, with its name centred in white and bold. The
                       install block wears one, because that block is the
                       thing you operate, and it is the one shape of the
                       original that everybody recognises.
     The face          Inter, a neo-grotesque in the line Helvetica belongs to,
                       which is what NeXTSTEP set its whole interface in.

   Everything else is ordinary modern layout with a great deal of air in it.
   --------------------------------------------------------------------------- */

/* --- the type ------------------------------------------------------------ */

/* Served from here rather than from a font host, because this page hands out
   an install script and has no business telling a third party who fetched it.
   The latin subset alone: the page is English, and the rest is 85kB nobody on
   it would use. */
@font-face {
  font-family: "Inter Variable";
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url("fonts/inter-variable-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191,
    U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* --- the tokens ---------------------------------------------------------- */

:root {
  /* The four values a mono NeXT could draw, and the one that is not a grey.
     They are the same values design/kit/base.css states, because they are the
     same system. */
  --next-white: #ffffff;
  --next-light: #aaaaaa;
  --next-dark: #555555;
  --next-black: #000000;
  --next-desktop: #555577;
  --next-live: #2f7d32;

  /* What the page is made of, stated once for both schemes. `light-dark()`
     reads whichever of its two the `color-scheme` in force names, so the
     switch in the bar sets that one property and every value below follows.
     The alternative is a second block of the same tokens under a media query,
     which is the same question answered twice.

     The neutrals carry a trace of the workspace colour rather than being pure
     grey, so a surface next to the accent reads as belonging to it. */
  --paper: light-dark(#ffffff, #121218);
  --paper-sunk: light-dark(#f4f4f8, #1b1b23);
  --ink: light-dark(#16161c, #f2f2f6);
  --ink-soft: light-dark(#5a5a68, #a6a6b4);
  --rule: light-dark(#e2e2ea, #2c2c38);
  /* After dark the workspace colour is lifted, because the original was chosen
     to sit behind black window frames rather than to carry text on a dark
     page. */
  --accent: light-dark(var(--next-desktop), #8f8fc4);
  --accent-ink: light-dark(var(--next-white), #12121a);
  /* What a main navigation entry lies on whilst the pointer is over it. The
     same value in both schemes: on the light page it is a dark shape, and on
     the dark one it is the surface a shade above the page, which is where
     every other raised surface here sits too. Its letters stay light in both,
     because the shape is dark in both. */
  --nav-hover: #2d2d3a;
  --nav-hover-ink: light-dark(#ffffff, var(--ink));
  /* Secondary text on the workspace colour, which is one value for all of it.
     It measures 6.09 against that background, and white itself measures
     7.11, so the two read as two weights of the same voice. */
  --on-workspace-soft: color-mix(in oklab, var(--next-white) 90%, var(--next-desktop));
  --accent-wash: color-mix(in oklab, var(--accent) 10%, var(--paper));
  --accent-hover: color-mix(in oklab, var(--accent) 82%, light-dark(black, white));

  /* One measure for the bar, the content and the foot, so the page reads as
     one column rather than three. 1120 is what the original screenshot this
     project measures everything against is wide. */
  --measure: 1120px;
  --measure-text: 68ch;

  /* One step, and every gap is a multiple of it. Nothing on this page states
     a spacing of its own. */
  --step: 8px;
  --space-1: var(--step);
  --space-2: calc(var(--step) * 2);
  --space-3: calc(var(--step) * 3);
  --space-4: calc(var(--step) * 4);
  --space-6: calc(var(--step) * 6);
  --space-8: calc(var(--step) * 8);
  --space-12: calc(var(--step) * 12);
  --space-16: calc(var(--step) * 16);

  /* A card states its outer radius and its padding, and the two values every
     nested surface and every introducing heading needs follow from those. The
     padding is larger than the radius here, so anything at the content edge
     sits past the curve entirely and is square, which is what the clamp
     says. */
  --card-radius: 24px;
  --card-padding: var(--space-4);
  --card-text-inset: calc(var(--card-radius) / 2);
  --card-inner-radius: max(calc(var(--card-radius) - var(--card-padding)), 0px);

  /* A control is not a card and takes none of a card's arithmetic. */
  --control-radius: 12px;

  --text-xs: 13px;
  --text-sm: 15px;
  --text-base: 17px;
  --text-lg: 21px;
  --text-xl: 27px;
  --text-2xl: 36px;
  --text-3xl: 56px;

  --line-tight: 1.15;
  --line-normal: 1.6;

  /* Code is read at the size it is written at, so it does not follow the
     reading scale. */
  --code-size: 15px;

  --font: "Inter Variable", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, "DejaVu Sans Mono", monospace;

  /* A mixed-case label inside a control sits a little above the cap midline,
     because most of its ink is lowercase and lowercase mass lies between the
     baseline and the x-height. A quarter of the distance between the two,
     measured on this face at 0.728em and 0.546em, in em so it scales. */
  --label-lift: 0.046em;

  /* How much larger a glyph is than the label it stands beside. One value,
     because the correction above has to divide by exactly this. */
  --icon-scale: 1.15;

  --duration: 180ms;
  --ease: cubic-bezier(0.2, 0, 0, 1);

  /* A shadow is not a colour, so `light-dark()` cannot sit in the shorthand.
     Its colours are tokens instead, and the shorthands read them. After dark a
     shadow has to be deeper to be seen at all. */
  --shadow-near: light-dark(rgb(22 22 28 / 6%), rgb(0 0 0 / 40%));
  --shadow-far: light-dark(rgb(22 22 28 / 6%), rgb(0 0 0 / 30%));
  --shadow-float-near: light-dark(rgb(22 22 28 / 10%), rgb(0 0 0 / 50%));
  --shadow-float-far: light-dark(rgb(22 22 28 / 18%), rgb(0 0 0 / 45%));
  --shadow-card: 0 1px 2px var(--shadow-near), 0 8px 24px var(--shadow-far);
  --shadow-float: 0 2px 4px var(--shadow-float-near), 0 18px 48px var(--shadow-float-far);

  /* Both schemes are built, so the page follows the system until somebody
     says otherwise. The two rules below are that somebody: the switch writes
     the attribute, and `only` takes the system out of the decision. */
  color-scheme: light dark;
}

:root[data-scheme="light"] {
  color-scheme: only light;
}

:root[data-scheme="dark"] {
  color-scheme: only dark;
}

/* --- the page ------------------------------------------------------------ */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  /* The bar is sticky, so an anchor would otherwise land underneath it. */
  scroll-padding-top: var(--space-12);
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: var(--text-base) / var(--line-normal) var(--font);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1,
h2,
h3 {
  margin: 0;
  line-height: var(--line-tight);
  letter-spacing: -0.02em;
  text-wrap: balance;
}

h1 {
  font-size: var(--text-3xl);
  font-weight: 700;
  letter-spacing: -0.03em;
}

h2 {
  font-size: var(--text-2xl);
  font-weight: 650;
}

h3 {
  font-size: var(--text-lg);
  font-weight: 600;
}

p {
  margin: 0;
  text-wrap: pretty;
}

a {
  color: inherit;
  text-decoration-color: color-mix(in oklab, currentcolor 35%, transparent);
  text-underline-offset: 3px;
  transition: text-decoration-color var(--duration) var(--ease);
}

a:hover {
  text-decoration-color: currentcolor;
}

code,
kbd {
  font-family: var(--mono);
  font-size: var(--code-size);
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 4px;
}

/* --- the measure --------------------------------------------------------- */

/* One column, stated once. A band that spans the window carries this inside
   itself rather than around itself, so a background reaches both edges whilst
   the text still begins where the text above it begins. */
.measure {
  width: 100%;
  max-width: var(--measure);
  margin-inline: auto;
  padding-inline: var(--space-3);
}

/* --- icons --------------------------------------------------------------- */

.icon {
  width: 1em;
  height: 1em;
  flex: none;
  fill: currentcolor;
}

/* --- the bar ------------------------------------------------------------- */

.bar {
  position: sticky;
  top: 0;
  z-index: 10;
  background: color-mix(in oklab, var(--paper) 80%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--rule);
}

.bar__row {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  height: calc(var(--step) * 8);
}

.wordmark {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--text-lg);
  font-weight: 650;
  letter-spacing: -0.02em;
  text-decoration: none;
}

/* No correction: on this face the midline between the baseline and the cap
   line and the middle of the line box are 0.14px apart at 21px, and the cube's
   own ink is centred in its viewBox. Centring the boxes lands on the
   typographic line the mark belongs on. */
.wordmark .icon {
  color: var(--accent);
  font-size: 1.15em;
}

/* Pushed to the far end, with the switch after it, so the wordmark keeps the
   left edge on its own. */
.bar__links {
  display: flex;
  align-items: center;
  gap: 2px;
  margin-inline-start: auto;
  font-size: var(--text-sm);
}

/* An entry is a shape rather than an underlined word: the line under it reads
   as prose, and these are places to go. The shape appears under the pointer
   and takes the letters with it. */
.bar__links a {
  display: flex;
  align-items: center;
  height: calc(var(--step) * 4.5);
  padding-inline: var(--space-2);
  padding-bottom: calc(var(--label-lift) * 2);
  border-radius: var(--control-radius);
  text-decoration: none;
  transition: background-color var(--duration) var(--ease),
    color var(--duration) var(--ease);
}

.bar__links a:hover {
  background: var(--nav-hover);
  color: var(--nav-hover-ink);
}

/* --- the scheme switch --------------------------------------------------- */

/* A glyph alone, so it carries no fill of its own until the pointer arrives,
   and then it takes the same shape the entries beside it take. */
.scheme {
  display: grid;
  place-items: center;
  width: calc(var(--step) * 4.5);
  height: calc(var(--step) * 4.5);
  margin-inline-start: 2px;
  padding: 0;
  border: 0;
  border-radius: var(--control-radius);
  background: transparent;
  color: inherit;
  font-size: var(--text-lg);
  cursor: pointer;
  transition: background-color var(--duration) var(--ease),
    color var(--duration) var(--ease);
}

.scheme:hover {
  background: var(--nav-hover);
  color: var(--nav-hover-ink);
}

/* The three glyphs share one cell and show which of the three states is in
   force: the screen whilst the page follows the system, and the sun or the
   moon once somebody has chosen. The one that is showing turns in, the others
   turn out, so the change reads as a movement rather than as a redraw.

   One rule names all three showing cases, because each of them is a different
   glyph under a different condition and none of the three can be true twice. */
.scheme .icon {
  grid-area: 1 / 1;
  opacity: 0;
  transform: rotate(-90deg) scale(0.5);
  transition: opacity var(--duration) var(--ease),
    transform var(--duration) var(--ease);
}

:root:not([data-scheme]) .scheme__system,
:root[data-scheme="light"] .scheme__sun,
:root[data-scheme="dark"] .scheme__moon {
  opacity: 1;
  transform: none;
}

/* Read by a screen reader and by nothing else. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Below the measure the entries crowd the wordmark, and the page's own
   sections are a scroll away in any case. The switch stays. */
@media (max-width: 640px) {
  .bar__links {
    display: none;
  }

  .scheme {
    margin-inline-start: auto;
  }
}

/* --- sections ------------------------------------------------------------ */

.section {
  padding-block: var(--space-16);
}

.section--sunk {
  background: var(--paper-sunk);
}

/* The workspace, which is where a NeXTSTEP window lies. Everything shown on it
   is a picture of the running system. */
.section--workspace {
  background: var(--next-desktop);
  color: var(--next-white);
}

.section__head {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-width: var(--measure-text);
  margin-bottom: var(--space-8);
}

/* A heading standing above a card begins where that card's curve gives way to
   its straight edge, rather than out at the page edge the corner has already
   pulled the card away from. Only above a card: the sections whose content is
   a picture or a list of steps have no curve to clear, and taking the inset
   there would turn a derived value back into an arbitrary one. */
.section:has(.cards) .section__head {
  padding-inline-start: var(--card-text-inset);
}

.section__kicker {
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}

.section--workspace .section__kicker {
  color: var(--on-workspace-soft);
}

.section__lead {
  font-size: var(--text-lg);
  color: var(--ink-soft);
}

.section--workspace .section__lead {
  color: var(--on-workspace-soft);
}

/* --- the hero ------------------------------------------------------------ */

/* The page opens on one thing, so the opening is centred on it: the sentence,
   the line to paste, and nothing to either side. Everything below this is
   left aligned, because that is where reading starts. */
.hero {
  padding-block: var(--space-16) var(--space-12);
  text-align: center;
}

.hero__grid {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-6);
}

.hero__copy {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
}

/* Both are held narrower than the page rather than the block around them, so
   each breaks where its own type wants to. A headline set across 1120 pixels
   is read in two movements of the head. */
.hero h1 {
  max-width: 18ch;
}

.hero__lead {
  font-size: var(--text-xl);
  line-height: 1.4;
  color: var(--ink-soft);
  max-width: 52ch;
}

.hero__facts {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-1);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--text-sm);
}

.hero__fact {
  padding-inline: var(--space-2);
  padding-top: calc(var(--label-lift) * -1);
  line-height: calc(var(--step) * 4);
  border: 1px solid var(--rule);
  border-radius: calc(var(--step) * 2);
  color: var(--ink-soft);
}

/* --- the install block --------------------------------------------------- */

/* A window, which is the one shape of NeXTSTEP everybody recognises: a black
   bar with its name centred in white and bold. The frame around it is this
   page's own, so the quotation stays a quotation. */
.window {
  width: 100%;
  max-width: 760px;
  border-radius: var(--control-radius);
  overflow: hidden;
  background: var(--paper);
  box-shadow: var(--shadow-float);
  /* Its own alignment, because the opening above it is centred and a line to
     paste is read from the left. */
  text-align: left;
}

.window__title {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  height: calc(var(--step) * 4);
  background: var(--next-black);
  color: var(--next-white);
  font-size: var(--text-sm);
  font-weight: 700;
  letter-spacing: -0.01em;
}

.window__title .icon {
  font-size: calc(1em * var(--icon-scale));
  opacity: 0.85;
}

.window__body {
  display: flex;
  align-items: stretch;
  gap: var(--space-2);
  padding: var(--space-2);
  background: var(--paper-sunk);
}

.command {
  flex: 1 1 auto;
  /* A shell line is read left to right and is not prose, so it neither wraps
     nor shortens. It scrolls inside itself, and reports no width upwards. */
  min-width: 0;
  overflow-x: auto;
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding-inline: var(--space-2);
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--control-radius);
  font-family: var(--mono);
  font-size: var(--code-size);
  line-height: calc(var(--step) * 5);
  white-space: pre;
}

.command__prompt {
  color: var(--accent);
  user-select: none;
}

.window__note {
  padding: 0 var(--space-2) var(--space-2);
  background: var(--paper-sunk);
  font-size: var(--text-sm);
  color: var(--ink-soft);
}

/* --- buttons ------------------------------------------------------------- */

/* Every action is a button with a visible shape and an icon that says what it
   does. The width comes from the label, and only a phone stretches it. */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 0 var(--space-2);
  height: calc(var(--step) * 5);
  border: 1px solid transparent;
  border-radius: var(--control-radius);
  background: var(--accent);
  color: var(--accent-ink);
  font: 600 var(--text-sm) / 1 var(--font);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--duration) var(--ease),
    border-color var(--duration) var(--ease), color var(--duration) var(--ease),
    transform var(--duration) var(--ease);
}

.button:hover {
  background: var(--accent-hover);
}

.button:active {
  transform: translateY(1px);
}

.button--quiet {
  background: transparent;
  border-color: var(--rule);
  color: var(--ink);
}

.button--quiet:hover {
  background: var(--accent-wash);
  border-color: var(--accent);
}

/* Confirmation arrives at once and drains away afterwards, so the transition
   sits on the resting state and not on the confirmed one. */
.button--copied,
.button--copied:hover {
  background: var(--next-live);
  color: var(--next-white);
  transition: none;
}

.button .icon {
  font-size: calc(1em * var(--icon-scale));
}

/* The lift a mixed-case label needs inside a control. A box of a stated height
   centres its contents in whatever is left after the padding, so the padding
   is twice the lift and the contents rise by half of it. The height itself
   does not move, because the box is sized border to border.

   The glyph beside the label is handed the lift back, since a mark belongs on
   the cap midline whatever the letters next to it are doing. Its own em is
   larger than the label's, so the correction is divided by exactly the factor
   that made it larger. */
.button,
.window__title,
.hero__fact {
  padding-bottom: calc(var(--label-lift) * 2);
}

.button .icon,
.window__title .icon {
  transform: translateY(calc(var(--label-lift) / var(--icon-scale)));
}

/* --- cards --------------------------------------------------------------- */

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-3);
}

.card {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--card-padding);
  border-radius: var(--card-radius);
  background: var(--paper);
  border: 1px solid var(--rule);
  box-shadow: var(--shadow-card);
}

.card__icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: calc(var(--step) * 6);
  height: calc(var(--step) * 6);
  margin-bottom: var(--space-1);
  /* An icon tile stands in a card rather than inside a padded surface of its
     own, so its radius is a control's and not a card's inner one. */
  border-radius: var(--control-radius);
  background: var(--accent-wash);
  color: var(--accent);
  font-size: 26px;
}

.card__text {
  color: var(--ink-soft);
}

/* --- the steps ----------------------------------------------------------- */

.steps {
  display: grid;
  gap: var(--space-3);
  counter-reset: step;
}

.step {
  display: grid;
  grid-template-columns: calc(var(--step) * 5) 1fr;
  gap: var(--space-2);
  align-items: start;
  padding-block: var(--space-2);
  border-top: 1px solid var(--rule);
}

.step:first-child {
  border-top: 0;
}

.step::before {
  counter-increment: step;
  content: counter(step);
  display: flex;
  align-items: center;
  justify-content: center;
  height: calc(var(--step) * 4);
  border-radius: 50%;
  background: var(--accent-wash);
  color: var(--accent);
  font-size: var(--text-sm);
  font-weight: 650;
}

.step__body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}

.step__text {
  color: var(--ink-soft);
  max-width: var(--measure-text);
}

/* A shell line inside a step, which scrolls rather than widening the page. */
.snippet {
  display: block;
  overflow-x: auto;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--control-radius);
  background: var(--paper-sunk);
  border: 1px solid var(--rule);
  font-family: var(--mono);
  font-size: var(--code-size);
  white-space: pre;
}

/* --- the screenshots ----------------------------------------------------- */

/* The picture carries the same workspace colour this section is painted in, so
   it needs no frame: the windows in it lie on the page the way they lie on the
   screen. That is also why it is not a card. */
.shot {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
}

/* Shown at its own size wherever the column can hold it, because this is a
   bitmap interface drawn one pixel per pixel and any scaling turns its
   single-pixel edges into grey. The picture is exactly as wide as the column
   inside the measure, so anything from a laptop upwards gets it untouched. */
.shot img {
  display: block;
  width: auto;
  max-width: 100%;
  height: auto;
}

.shot__caption {
  font-size: var(--text-sm);
  color: var(--on-workspace-soft);
}

/* --- the foot ------------------------------------------------------------ */

.foot {
  padding-block: var(--space-8);
  border-top: 1px solid var(--rule);
  font-size: var(--text-sm);
  color: var(--ink-soft);
}

.foot__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

.foot__links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

.foot__links a {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

/* --- narrow -------------------------------------------------------------- */

@media (max-width: 720px) {
  :root {
    --text-3xl: 38px;
    --text-2xl: 28px;
    --text-xl: 21px;
  }

  .section {
    padding-block: var(--space-8);
  }

  .hero {
    padding-block: var(--space-8) var(--space-6);
  }
}

/* A phone, where a full-width button is the platform convention and a thumb
   needs the target. This is the one place any button stretches. */
@media (max-width: 520px) {
  .window__body {
    flex-direction: column;
  }

  .window__body .button {
    width: 100%;
  }
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    /* Shortened rather than removed, because an instant change of state reads
       as a glitch rather than as a response. */
    transition-duration: 60ms !important;
    animation-duration: 60ms !important;
  }
}
