/*
 * Miosga Software - own website.
 *
 * Black and white only. Black text on white paper, nothing else: no accent,
 * no colour anywhere. Secondary text and boundaries are black at reduced
 * opacity, never a hue.
 *
 * The home page is a one-pager: one full screen per step of the process,
 * each with a line drawing on the left and the step on the right. The
 * logo sits in the top left corner of every page, the scroll cue at the
 * bottom of every screen.
 *
 * The earlier marketing sections live in git history (commit ff8b32a).
 *
 * One face for everything: San Francisco Mono. It ships with macOS and iOS
 * and is reached through ui-monospace - Apple's licence does not allow
 * serving it as a web font, so it cannot be self-hosted. Everywhere else the
 * stack falls through to that system's own monospace, which is why the
 * layout is measured in ch and never in fixed pixels.
 */

:root {
  --paper: #ffffff;
  --ink: #000000;
  /* Secondary text and punctuation: black, just quieter. The only greys in
     the system - an editor dims a comment, it does not colour it. */
  --ink-soft: rgba(0, 0, 0, 0.55);
  --ink-faint: rgba(0, 0, 0, 0.32);
  --rule: rgba(0, 0, 0, 0.12);
  /* Weight of every line in the diagram: frames, edges and the corner. */
  --line: 2px;
  /* The corner that carries on to the next screen. */
  --next-size: 2.4rem;
  /* How long the page takes to travel aside for a document, and how long it
     spends going out of focus before it sets off. */
  --slide: .7s;
  --haze: .28s;

  /* ui-monospace resolves to San Francisco Mono on Apple systems; the names
     after it are the explicit fallbacks for everything else. */
  --font: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;

  --column: 44rem;
  /* Wide enough for drawing and text side by side. */
  --page: 66rem;
  /* The measure of the step text - and with it the width of its column. */
  --measure: 44ch;
  /* Between the three package nodes, and the figure the split bar is
     inset by, so that it ends over the centre of the outer two. */
  --fan-gap: clamp(1rem, 2.5vw, 2rem);
  /* ">" plus its half-character margin, "miosga.software" and the caret -
     the width the boot block reserves so that nothing shifts while it is
     typed. */
  --boot-columns: 18;
  --rhythm: clamp(3rem, 7vw, 6rem);
}

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

/*
 * One step per screen, so scrolling moves from one to the next rather than
 * stopping in between. scroll-snap-stop keeps a hard flick from skipping a
 * step altogether.
 *
 * Guarded by a minimum height: on a short screen a step can be taller than
 * the viewport, and mandatory snapping would then make the overflow
 * unreachable. There, scrolling stays ordinary.
 */
html { scroll-behavior: smooth; }

@media (min-height: 34rem) {
  html { scroll-snap-type: y mandatory; }
  .step { scroll-snap-align: start; scroll-snap-stop: always; }
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font);
  font-size: clamp(1rem, 0.95rem + 0.3vw, 1.125rem);
  line-height: 1.65;
  text-align: center;
  -webkit-font-smoothing: antialiased;
}

h1, h2 {
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
  margin: 0;
  text-wrap: balance;
}
h1 { font-size: clamp(1.9rem, 1.4rem + 2.4vw, 3rem); }
h2 { font-size: 1.2rem; margin-top: 2.75rem; }

p { margin-inline: auto; max-width: var(--column); }
h1 + p { margin-top: 2rem; }
a { color: inherit; }

:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; }

.wrap { width: min(100% - 2.5rem, var(--column)); margin-inline: auto; }
.section { padding-block: var(--rhythm); }

/* Half a character of air after the prompt. A real space would be a full
   character wide in a monospace face, which reads as a gap rather than as a
   prompt; the margin is set in ch so it scales with the type. */
.prompt { margin-right: 0.5ch; }

/* The caret: an underscore, the way a terminal draws it when it is idle.
   Shared by the boot sequence, where it travels along with the typed text,
   and by the logo, where it simply sits parked behind the name. */
.caret {
  display: inline-block;
  width: 0.6em;
  height: 1em;
  vertical-align: baseline;
  /* Bold, like the name it follows. */
  border-bottom: 0.16em solid var(--ink);
}

/* ---------- Header ---------- */
/* Fixed in the top left corner, so the name and what it is stay on screen
   through every step. No rule underneath: a line would be one thing too many. */
.site-header {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 10;
  padding: clamp(1.5rem, 4vh, 2.5rem) clamp(1.25rem, 4vw, 2.5rem);
  text-align: left;
}

/* Documents (imprint, privacy, 404) scroll normally, so a fixed header would
   sit on top of their first lines. */
.doc .site-header { position: static; }

/* The one line that says what this is, before any step explains how. */
.slogan {
  margin: 0.35rem 0 0;
  max-width: 44ch;
  font-size: 0.8rem;
  color: var(--ink-soft);
  line-height: 1.45;
}
/* The logo is set as code, not as a wordmark: lower case, monospace, and a
   caret parked behind it as though the line were still live. The
   second half carries the weight - that is the name, the first half is the
   domain it sits on. */
.logo {
  display: inline-block;
  font-size: clamp(0.85rem, 0.8rem + 0.25vw, 1rem);
  font-weight: 500;
  letter-spacing: -0.01em;
  text-decoration: none;
}
.logo-name { font-weight: 700; }
/* Parked, not blinking: a cursor that never stops is a page that never
   settles. Add .boot-caret here if it should blink after all. */

/* ---------- Footer ---------- */
.site-footer { padding-block: var(--rhythm) 3rem; font-size: 0.9rem; }
.site-footer-nav { display: flex; justify-content: center; gap: 1.5rem; }
.site-footer a { text-decoration: none; border-bottom: 1px solid var(--rule); }
.site-footer a:hover { border-bottom-color: var(--ink); }

/* Braces, semicolons, comment markers: present, but never the loudest thing
   on the line. */
.punctuation { color: var(--ink-faint); }

/* ---------- The greeting ---------- */
/*
 * Where the line begins. Deliberately not a node: nothing happens here yet,
 * so there is nothing to draw a box around - the process starts below it.
 */
/* Flush left inside a block that is centred on the screen: typed text that
   is centred moves sideways with every character. */
.greeting {
  flex: 0 0 auto;
  width: min(100%, var(--measure));
  margin-inline: auto;
  text-align: left;
}

.greeting-line {
  margin: 0;
  max-width: none;
  font-size: clamp(2.2rem, 1.6rem + 3.2vw, 4rem);
  font-weight: 700;
  letter-spacing: -0.04em;
  /* A space in a monospace face is as wide as a letter - about 0.6em - which
     at this size tears the two words apart. Pulled back to roughly the 0.25em
     a proportional face would set. */
  word-spacing: -0.34em;
  line-height: 1.05;
  /* Holds its height while it is still empty. */
  min-height: 1.05em;
}

.greeting-note {
  margin: 1.5rem 0 0;
  max-width: none;
  font-size: clamp(0.9rem, 0.86rem + 0.2vw, 1rem);
  line-height: 1.75;
  color: var(--ink-soft);
}

/*
 * No line on this screen: the greeting sits in the middle of it, and in the
 * middle of the screen itself - not of whatever is left over once the header
 * and the cue have taken their share. That is why the cue is lifted out of
 * the flow and the top padding dropped: both would otherwise push the
 * greeting down by half their height, and it would sit visibly low.
 */
.step-hello {
  position: relative;
  justify-content: center;
}

.step-hello .scroll-cue {
  position: absolute;
  left: 0;
  right: 0;
  bottom: clamp(2rem, 7vh, 4.5rem);
  margin: 0;
  justify-content: center;
}

/* A hint that also works: same arrow as the corners carry, same destination,
   only quieter - it stays grey until it is pointed at. */
.scroll-cue {
  text-decoration: none;
  flex: 0 0 auto;
  margin: 0 0 clamp(2rem, 7vh, 4.5rem);
  max-width: none;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.75rem;
  color: var(--ink-soft);
  white-space: nowrap;
}
.scroll-cue:hover { color: var(--ink); }
.scroll-label { font-weight: 500; }
/* Sized so the stroke weighs the same as it does in a corner. */
.scroll-arrow { width: 0.95rem; height: auto; }

/* Typed one character at a time, each easing in - the same hand as the
   sequence on the curtain. */
.greeting-character { animation: character-in .16s ease-out both; }

/* The mark floats, barely. Both animations are named together because a
   second rule would replace the first outright rather than add to it. */
.greeting-mark {
  display: inline-block;
  animation: character-in .16s ease-out both,
             float 1.8s ease-in-out .2s infinite;
}

@keyframes float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-0.07em); }
}

/* ---------- Steps ---------- */
/*
 * The page reads as a diagram: every step is a node, a box with an edge
 * running out of its foot to the label that carries on to the next one.
 *
 * One node per screen, and one unbroken line down the whole page: it leaves
 * the foot of a node, runs to the bottom of the screen and picks up again at
 * the top of the next one.
 *
 * The line can only reach the bottom if nothing is centred: the flexible
 * lengths do the balancing instead. The first node has an empty spacer above
 * it that grows at the same rate as the line below, every node after it has
 * the incoming line in that place, and the last gets a spacer underneath.
 * That keeps every node at the same height on screen whatever comes before
 * or after it.
 */
.step {
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding-inline: clamp(1.25rem, 4vw, 2.5rem);
}
/* Clear of the fixed header on the first screen - except on the greeting,
   which is centred in the screen itself and lets the logo sit over it. */
.step:first-of-type:not(.step-hello) { padding-top: clamp(4rem, 11vh, 6.5rem); }
/* The last step manages its own foot, so it gets no spacer - written as an
   exception here rather than as an override further down, where it would
   lose on specificity and silently do nothing. */
.step:first-of-type:not(.step-hello)::before,
.step:last-of-type:not(.step-end)::after { content: ''; flex: 1 1 0; }

/* The node. Square corners and a thin, even line: a box in a diagram, not a
   card in a layout. The line itself is the SVG frame below, so that it can
   be drawn rather than simply be there. */
.node {
  position: relative;
  width: min(100%, var(--page));
  background: var(--paper);
  /* The box is deliberately wider than it needs to be: the contents should
     sit in it, not fill it. */
  padding: clamp(2.5rem, 6vw, 4.5rem);
}

/*
 * The frame, as six separate lines rather than a border, so that it can be
 * drawn: the two halves of the top grow outwards from the centre, the sides
 * run down from the corners, the two halves of the foot come back together
 * in the middle - where the edge picks the movement up again.
 *
 * The half lines overlap the centre by half a line width, otherwise an odd
 * number of pixels leaves a hairline gap where they meet.
 */
.node-frame {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.frame-line {
  position: absolute;
  background: var(--ink);
}

.frame-top, .frame-bottom {
  height: var(--line);
  width: calc(50% + var(--line) / 2);
}
.frame-top { top: 0; }
.frame-bottom { bottom: 0; }

.frame-side {
  top: 0;
  width: var(--line);
  height: 100%;
  transform-origin: top;
}

.frame-left { left: 0; }
.frame-right { right: 0; }

/* Each half grows away from the point the drawing passes through: the top
   from the centre outwards, the foot from the corners inwards. */
.frame-top.frame-left { transform-origin: right; }
.frame-top.frame-right { transform-origin: left; }
.frame-bottom.frame-left { transform-origin: left; }
.frame-bottom.frame-right { transform-origin: right; }

/* Step 2 and onwards: the box is drawn, the contents are still to come. */
.node-empty { min-height: clamp(12rem, 30vh, 18rem); }

/*
 * The line, in two roles: leading into a node from the top of the screen and
 * trailing out of its foot to the bottom. Both take up whatever room is left
 * over, which is what makes the line meet the screen edges exactly.
 */
.edge {
  flex: 1 1 0;
  width: var(--line);
  min-height: 2rem;
  background: var(--ink);
  transform-origin: top;
}

/*
 * Drawing and text sit as one pair in the middle of the box. The columns are
 * sized to what they hold rather than to the box: stretched columns would
 * push the pair against the left edge, because the paragraph stops at its
 * measure and leaves the rest of its column empty.
 */
.step-inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(2.5rem, 6vw, 5rem);
  align-items: center;
  justify-content: center;
  width: 100%;
}

.step-figure { justify-self: center; }

.figure {
  display: block;
  width: min(100%, 15rem);
  height: auto;
  margin-inline: auto;
  color: var(--ink);
}

.step-text { text-align: left; }

.step-title {
  margin: 0;
  font-size: clamp(1.6rem, 1.1rem + 2.2vw, 2.9rem);
  font-weight: 400;
  letter-spacing: -0.03em;
  line-height: 1.1;
  text-wrap: balance;
}
/* The label is the only bold thing in the block - everything after it reads
   as the value it names. */
.step-key { font-weight: 700; }

/* Set as a block comment, which is how an editor shows prose in code. */
.step-body {
  margin: 1.5rem 0 0;
  max-width: var(--measure);
  font-size: clamp(0.9rem, 0.86rem + 0.2vw, 1rem);
  line-height: 1.75;
  color: var(--ink-soft);
}

@media (min-width: 56rem) {
  .step-inner {
    grid-template-columns: clamp(9rem, 16vw, 13rem) minmax(0, var(--measure));
  }
}

/*
 * Every step draws itself the first time it is scrolled to, and then stays
 * drawn. Scrolling back up shows the diagram as it was built, not an empty
 * screen waiting to be filled in again.
 *
 * Transitions rather than animations, because the delay on each rule is then
 * all that orders the sequence, and because a step that is drawn once needs
 * no keyframes standing by for a second run.
 *
 * Which end it starts from follows the direction the reader arrived from, so
 * the drawing always begins where the line they were following comes in.
 * Coming down, the frame grows out of the top centre, down the sides and
 * back together at the foot. Coming up, .from-below mirrors it: out of the
 * foot, up the sides, closing at the top. Only delays and origins change,
 * which is why they are variables rather than a second set of rules.
 *
 * The contents take no part in any of it: they are there from the first
 * moment and the frame is drawn around them.
 *
 * The resting state only applies while a script is around to lift it again,
 * so without JavaScript every step is simply drawn.
 */
.step {
  --origin: top;
  --lead-in: 0s;
  --top-in: .25s;
  --side-in: .5s;
  --bottom-in: .9s;
  --next-in: 1.2s;
  --tail-in: 1.2s;
  --tail-time: .3s;
  --end-in: 1.8s;
  --top-left-origin: right;
  --top-right-origin: left;
  --bottom-left-origin: left;
  --bottom-right-origin: right;
}

.step.from-below {
  --origin: bottom;
  --lead-in: 1.2s;
  --top-in: .9s;
  --bottom-in: .25s;
  --tail-in: 0s;
  --top-left-origin: left;
  --top-right-origin: right;
  --bottom-left-origin: right;
  --bottom-right-origin: left;
}

/*
 * The gateway has two more phases than a plain step - the bar and the drops
 * at each end - so the frame phases move back to make room for them. The
 * three nodes themselves are drawn at the same moment as each other: they
 * share these variables, and nothing distinguishes them.
 */
.step-split {
  --lead-in: 0s;
  --split-bar-in: .25s;
  --split-drop-in: .45s;
  --top-in: .65s;
  --side-in: .9s;
  --bottom-in: 1.3s;
  --merge-drop-in: 1.5s;
  --merge-bar-in: 1.7s;
  --next-in: 1.6s;
  --tail-in: 1.9s;
}

.step-split.from-below {
  --tail-in: 0s;
  --merge-bar-in: .25s;
  --merge-drop-in: .45s;
  --bottom-in: .65s;
  --side-in: .9s;
  --top-in: 1.3s;
  --split-drop-in: 1.5s;
  --split-bar-in: 1.7s;
  --lead-in: 1.9s;
}

/* Scoped to a step on purpose: the same node is used in the panels, where
   nothing ever comes along to draw it - there it is simply there. */
html.js .step .frame-line { transform: scale(0); }
html.js .step .step-inner,
html.js .step .greeting,
html.js .step .scroll-cue { opacity: 0; }
html.js .edge { transform: scaleY(0); }
html.js .fan-bar { transform: scaleX(0); }
html.js .fan-drop { transform: scaleY(0); }

html.js .edge,
html.js .fan-drop,
html.js .frame-side { transform-origin: var(--origin); }
html.js .frame-top.frame-left { transform-origin: var(--top-left-origin); }
html.js .frame-top.frame-right { transform-origin: var(--top-right-origin); }
html.js .frame-bottom.frame-left { transform-origin: var(--bottom-left-origin); }
html.js .frame-bottom.frame-right { transform-origin: var(--bottom-right-origin); }
/* The split bar behaves exactly like the top of a frame and the merge bar
   like its foot, so they share those origins rather than keeping their own
   copy that could drift out of step. */
html.js .fan-split .fan-bar-left  { transform-origin: var(--top-left-origin); }
html.js .fan-split .fan-bar-right { transform-origin: var(--top-right-origin); }
html.js .fan-merge .fan-bar-left  { transform-origin: var(--bottom-left-origin); }
html.js .fan-merge .fan-bar-right { transform-origin: var(--bottom-right-origin); }

html.js .edge-lead    { transition: transform .3s ease; }
html.js .frame-top    { transition: transform .3s ease; }
html.js .frame-side   { transition: transform .4s ease; }
html.js .frame-bottom { transition: transform .3s ease; }
html.js .step-inner,
html.js .greeting,
html.js .scroll-cue   { transition: opacity .25s ease; }
html.js .edge-tail    { transition: transform var(--tail-time) ease; }
/* The closing line is the long one, so it takes longer to draw - without a
   transition of its own it would simply appear, which is what it did. */
html.js .edge-end     { transition: transform .55s ease; }
html.js .fan-bar      { transition: transform .3s ease; }
html.js .fan-drop     { transition: transform .25s ease; }

html.js .step.is-visible .edge-lead    { transform: scaleY(1); transition-delay: var(--lead-in); }
html.js .step.is-visible .frame-top    { transform: scaleX(1); transition-delay: var(--top-in); }
html.js .step.is-visible .frame-side   { transform: scaleY(1); transition-delay: var(--side-in); }
html.js .step.is-visible .frame-bottom { transform: scaleX(1); transition-delay: var(--bottom-in); }
html.js .step.is-visible .step-inner,
html.js .step.is-visible .greeting,
html.js .step.is-visible .scroll-cue   { opacity: 1; }
html.js .step.is-visible .edge-tail    { transform: scaleY(1); transition-delay: var(--tail-in); }
html.js .step.is-visible .edge-end     { transform: scaleY(1); transition-delay: var(--tail-in); }
html.js .step.is-visible .fan-split .fan-bar  { transform: scaleX(1); transition-delay: var(--split-bar-in); }
html.js .step.is-visible .fan-split .fan-drop { transform: scaleY(1); transition-delay: var(--split-drop-in); }
html.js .step.is-visible .fan-merge .fan-drop { transform: scaleY(1); transition-delay: var(--merge-drop-in); }
html.js .step.is-visible .fan-merge .fan-bar  { transform: scaleX(1); transition-delay: var(--merge-bar-in); }

/*
 * The end of the process: a ring at the foot of the last line, the way a
 * diagram closes. The legal pages hang off it - that is the one place on a
 * one-pager where a reader looks for them, and it needs no footer of its own
 * to break the drawing.
 *
 * The links keep their German names. They are the titles of German legal
 * documents, and the label has to be recognised without thinking - which is
 * exactly what a cleverer word would cost.
 */
/* The last line is the long one: it carries the eye from the closing node
   all the way down to the foot of the page, where the legal pages sit. */
.edge-end {
  flex: 2 1 0;
  min-height: 2.5rem;
}

/* The foot of the diagram sits at the foot of the screen. Snapped by its end
   rather than its start, so the links are what the screen comes to rest on
   even when the section runs taller than the viewport. */
.step-end {
  padding-bottom: clamp(0.9rem, 2vh, 1.4rem);
  scroll-snap-align: end;
}

.terminator {
  flex: 0 0 auto;
  width: 2.3rem;
  height: 2.3rem;
  border: calc(var(--line) * 1.5) solid var(--ink);
  border-radius: 50%;
}

.legal {
  margin: 1.4rem 0 0;
  max-width: none;
  display: flex;
  align-items: center;
  gap: 0.7rem;
  font-size: 0.78rem;
  color: var(--ink-soft);
}
.legal a {
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
}
.legal a:hover { border-bottom-color: var(--ink); }

/* It lands after the line that leads to it, and the links follow. */
html.js .terminator { transform: scale(0); transition: transform .3s ease; }
html.js .legal { opacity: 0; transition: opacity .4s ease; }
html.js .step.is-visible .terminator { transform: scale(1); transition-delay: var(--end-in); }
html.js .step.is-visible .legal { opacity: 1; transition-delay: calc(var(--end-in) + .2s); }

/*
 * The way on, in the bottom right corner of every node. Only two of its
 * sides are its own: the node's foot and right-hand side draw the other two,
 * which is why it has no background of its own - a filled corner would paint
 * over the very lines it is borrowing.
 */
.next {
  position: absolute;
  right: 0;
  bottom: 0;
  display: grid;
  place-items: center;
  width: var(--next-size);
  height: var(--next-size);
  border-top: var(--line) solid var(--ink);
  border-left: var(--line) solid var(--ink);
  color: var(--ink);
}
.next svg { width: 45%; height: auto; }
/* Inverted on hover: the one interactive thing on the page says so by
   turning into its own opposite. */
.next:hover { background: var(--ink); color: var(--paper); }

/* It arrives once the frame around it is closed. */
html.js .next { opacity: 0; transition: opacity .3s ease; }
html.js .step.is-visible .next { opacity: 1; transition-delay: var(--next-in); }

/* The one thing to actually do on this page, so it is drawn like a control
   and not like a sentence. */
.cta {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: 1.25rem;
  padding: 0.7rem 1.05rem;
  border: var(--line) solid var(--ink);
  color: var(--ink);
  font-size: 0.85rem;
  font-weight: 700;
  text-decoration: none;
}
.cta svg { width: 0.85rem; height: auto; }
.cta:hover { background: var(--ink); color: var(--paper); }

/* ---------- The gateway: three packages side by side ---------- */
/*
 * The line splits into three, runs through three nodes that together are as
 * wide as a single one, and closes again - the diagram's way of saying that
 * exactly one of these is chosen.
 *
 * The bars are taken out of the flow rather than placed in the grid: as a
 * grid item spanning all three columns a bar fills the only row, and the
 * three drops are then pushed into columns of their own outside the track.
 * Positioned absolutely it leaves the grid to the drops and is inset on both
 * sides by half a column instead, which lands its ends over the centre of
 * the outer two nodes. A calculation rather than a fixed percentage, so it
 * stays right whatever the gap is.
 *
 * flex: 0 0 auto on the rows, because they are flex items in the section:
 * with the default shrink factor a section that runs taller than the screen
 * is squared up by squashing these first, and the drops end as stubs.
 */
.packages {
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--fan-gap);
  width: min(100%, var(--page));
}

.fan {
  flex: 0 0 auto;
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  column-gap: var(--fan-gap);
  width: min(100%, var(--page));
  height: clamp(2.5rem, 6vh, 4rem);
}

/*
 * Two halves rather than one bar, for the same reason the frame of a node
 * has them: where the line arrives the halves spread outwards, where it
 * leaves they run back together. They overlap the middle by half a line
 * width so no hairline gap is left where they meet.
 */
.fan-bar {
  position: absolute;
  height: var(--line);
  background: var(--ink);
}
.fan-bar-left {
  left: calc((100% - 2 * var(--fan-gap)) / 6);
  right: calc(50% - var(--line) / 2);
}
.fan-bar-right {
  right: calc((100% - 2 * var(--fan-gap)) / 6);
  left: calc(50% - var(--line) / 2);
}

.fan-split .fan-bar { top: 0; }
.fan-merge .fan-bar { bottom: 0; }

.fan-drop {
  justify-self: center;
  width: var(--line);
  height: 100%;
  background: var(--ink);
}

/* The package node carries less, so it is held together more tightly - with
   room at the foot for the corner that sits in it. */
.package {
  --pad: clamp(1.4rem, 2.6vw, 2.1rem);
  width: auto;
  padding: var(--pad);
  padding-bottom: calc(var(--pad) + var(--next-size));
  text-align: left;
}

.package-name {
  margin: 0;
  font-size: clamp(1.05rem, 0.9rem + 0.6vw, 1.35rem);
  font-weight: 400;
  letter-spacing: -0.02em;
}

.package-price {
  margin: 1.25rem 0 0;
  max-width: none;
  font-size: clamp(1.3rem, 1.1rem + 1vw, 1.9rem);
  font-weight: 700;
  letter-spacing: -0.03em;
}

.package-setup {
  margin: 0.35rem 0 0;
  max-width: none;
  font-size: 0.78rem;
  color: var(--ink-soft);
}

.package-note {
  margin: 1.25rem 0 0;
  max-width: none;
  font-size: 0.8rem;
  line-height: 1.65;
  color: var(--ink-soft);
}

/* Below the breakpoint the three stand under one another and the bars would
   have nothing to span, so only the line through the middle remains.
   Named rather than counted: the bars are spans too, so :first-of-type
   would point at one of them and not at the first drop. */
@media (max-width: 55.999rem) {
  .packages, .fan { grid-template-columns: minmax(0, 1fr); }
  .fan-bar, .fan-drop-left, .fan-drop-right { display: none; }
}

/* ---------- The sliding panels ---------- */
/*
 * Imprint and privacy notice are not pages of their own: they lie beside the
 * one-pager and slide in over it, driven by :target - so the address still
 * carries them, they stay directly linkable, and none of it needs a script.
 *
 * The header stays outside .page on purpose. A transform on an ancestor makes
 * position: fixed resolve against that ancestor instead of the viewport, so a
 * header inside the sliding block would come loose the moment it moves. It
 * slides on its own instead, with the same timing - which looks identical.
 *
 * visibility keeps the panel out of the reading order while it is away; the
 * delay on it holds it visible until the slide out has finished.
 */
.doc-panel {
  position: fixed;
  inset: 0;
  z-index: 20;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--paper);
  transform: translateX(100%);
  visibility: hidden;
  transition: transform var(--slide) cubic-bezier(.4, 0, .2, 1) 0s,
              visibility 0s var(--slide);
}
.doc-panel:target {
  transform: none;
  visibility: visible;
  /* Waits out the blur, so page and document travel as one. */
  transition: transform var(--slide) cubic-bezier(.4, 0, .2, 1) var(--haze);
}

/*
 * The page does not just leave, it goes out of focus first and only then
 * sets off - otherwise the blur happens under the movement and nobody sees
 * it. On the way back the order reverses: it slides in and comes into focus
 * once it has arrived. The delays on the resting rule are the way back, the
 * ones on the target rule the way out.
 */
.page {
  transition: transform var(--slide) cubic-bezier(.4, 0, .2, 1) 0s,
              filter var(--haze) ease var(--slide),
              opacity var(--haze) ease var(--slide);
}
.doc-panel:target ~ .page {
  transform: translateX(-100%);
  filter: blur(7px);
  opacity: .35;
  transition: transform var(--slide) cubic-bezier(.4, 0, .2, 1) var(--haze),
              filter var(--haze) ease 0s,
              opacity var(--haze) ease 0s;
}

/*
 * The logo does not travel with the page. It is the one fixed point of the
 * site, and having it leave only to arrive again a moment later is motion
 * that says nothing. No ground behind it either: a white patch would punch a
 * hole in whatever passes underneath, and text running past it is the lesser
 * of the two.
 */
.site-header { z-index: 30; }

/* The way back, drawn like the corner that leads on - except that this one
   owns all four of its sides. */
.back {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: var(--next-size);
  height: var(--next-size);
  border: var(--line) solid var(--ink);
  color: var(--ink);
}
.back svg { width: 45%; height: auto; }
.back:hover { background: var(--ink); color: var(--paper); }

/* Clear of the fixed logo, which stays put while the panel slides under it. */
.panel-body {
  padding: clamp(7rem, 18vh, 10rem) clamp(1.25rem, 4vw, 2.5rem) clamp(4rem, 10vh, 7rem);
}

/* The way back sits next to the title, not above it. */
.doc-head {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: 2.5rem;
  text-align: left;
}

/* ---------- Document pages ---------- */
/*
 * Imprint and privacy notice: the same node as the diagram uses, holding a
 * list of label and value. Set flush left inside the box - an address read
 * down the middle of a page is an address nobody can read.
 */
.doc-title {
  margin: 0;
  font-size: clamp(1.5rem, 1.1rem + 1.6vw, 2.2rem);
  font-weight: 400;
  letter-spacing: -0.03em;
}

.doc-node {
  width: 100%;
  padding: clamp(1.75rem, 4vw, 2.75rem);
  text-align: left;
}

/* The privacy notice runs as prose, so its headings take the weight the
   labels carry in the imprint - same size, same rhythm. */
.doc-node h2 {
  margin: 2.25rem 0 0;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: -0.01em;
}
.doc-node h2:first-of-type { margin-top: 0; }
.doc-node h2 + p { margin-top: 0.4rem; }
.doc-node p {
  margin: 0.9rem 0 0;
  max-width: none;
  font-size: 0.88rem;
  line-height: 1.7;
  color: var(--ink-soft);
}
.doc-node a { text-decoration: none; border-bottom: 1px solid var(--rule); }
.doc-node a:hover { border-bottom-color: var(--ink); }

.facts { margin: 0; }

.facts dt {
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: -0.01em;
}
.facts dt::after { content: ':'; color: var(--ink-faint); }

.facts dd {
  margin: 0.4rem 0 0;
  font-size: 0.88rem;
  line-height: 1.7;
  color: var(--ink-soft);
}
.facts dd + dt { margin-top: 1.6rem; }

.facts a { text-decoration: none; border-bottom: 1px solid var(--rule); }
.facts a:hover { border-bottom-color: var(--ink); }

/* ---------- Boot sequence ---------- */
/*
 * The terminal curtain in front of the home page. Same paper, same ink as
 * the page behind it, so the handover is a fade and not a jump.
 *
 * html.is-booting - curtain closed, page hidden. Set inline in <head> so
 *                   nothing flashes before the first paint.
 * html.is-booted  - the curtain is gone and the logo fades in.
 */
.boot {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-content: center;
  padding-inline: 1.5rem;
  background: var(--paper);
  color: var(--ink);
  font-size: clamp(1.1rem, 0.9rem + 1vw, 1.6rem);
  font-weight: 500;
  line-height: 1.8;
  /* Goes out of focus as it goes, the way the page does when a document
     slides over it. Keep in step with TIMING.fadeOut in boot.js. */
  transition: opacity .9s ease, filter .9s ease;
}

/*
 * The block is centred on the page but set flush left inside itself, and it
 * is as wide as the finished line from the first frame on. That fixed width
 * is what makes the typing look calm: a block that only wraps its content
 * re-centres on every keystroke, which drags the whole line sideways.
 */
.boot-block {
  width: calc(var(--boot-columns) * 1ch);
  text-align: left;
}

.boot.is-done {
  opacity: 0;
  filter: blur(10px);
  pointer-events: none;
}

/* min-height keeps the second line reserved from the start, otherwise the
   first line jumps up the moment the caret drops down to it. */
.boot-line { margin: 0; max-width: none; white-space: pre; min-height: 1.8em; }

/* Every character is its own span so it can ease in instead of snapping on.
   Short enough to stay a keystroke, long enough to take the hard edge off. */
.boot-character { animation: character-in .16s ease-out both; }
/* Everything after the dot is the name - same weight as in the logo. */
.boot-name { font-weight: 700; }
/* The second line's prompt and its text both fade in on Enter. */
.boot-status,
.boot-line-second .prompt { animation: fade-in .18s ease both; }

@keyframes character-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* In the boot sequence it blinks; on Enter, boot.js moves it down a line. */
.boot-caret { animation: blink 1.1s steps(1, end) infinite; }
/* While keys are being pressed the caret holds still, like a real terminal. */
.boot-caret.is-typing { animation: none; }

@keyframes blink {
  0%, 50% { opacity: 1; }
  50.01%, 100% { opacity: 0; }
}

html.is-booting, html.is-booting body { overflow: hidden; }
html.is-booting .site-header,
html.is-booting main { opacity: 0; }

/* The page arrives in the order it is read: the name, then the diagram. */
html.is-booted .site-header { animation: fade-in .9s ease .25s both; }
html.is-booted main { animation: fade-in .9s ease .5s both; }

@keyframes fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; transition-duration: .01ms !important;
  }
  /* Without motion nothing fades in - it is simply there. */
  html.is-booting .site-header,
  html.is-booting main { opacity: 1; }
  html.is-booted .site-header,
  html.is-booted main { animation: none; }
  /* Nothing is drawn - everything is simply there. */
  html.js .frame-line { transform: none; }
  html.js .step-inner,
  html.js .greeting { opacity: 1; }
  html.js .step .scroll-cue { opacity: 1; }
  .greeting-mark { animation: none; }
  html.js .edge,
  html.js .fan-bar,
  html.js .fan-drop { transform: none; }
  html.js .next { opacity: 1; }
  html.js .terminator { transform: none; }
  html.js .legal { opacity: 1; }
}
