/* The front door.
 *
 * A port of the app's intro screen (lib/widgets/intro_screen.dart), and it is
 * written as a feed, because that is what this product is: every section
 * arrives the way a line arrives in the monitor: ticked on the severity rail,
 * stamped with a clock. The middle section is an incident timeline, so the
 * ascending clocks carry meaning rather than decoration.
 *
 * The eye in the header follows the story as you scroll: green while things
 * are healthy, red the moment the 03:12 record lands. It is the only thing on
 * the page allowed to be loud. */

/* --cy-col, defined in cyclopes.css, is the single source of truth for how
   wide this column is: the ambient gutters are measured against it, and they
   are siblings of .lp rather than children, so it cannot be scoped here. */
.lp {
  max-width: var(--cy-col);
  margin: 0 auto;
  padding: 0 var(--space-5) var(--space-7);
  position: relative;
  z-index: 1;
}

/* ---------- the readout in the appbar ----------
   The one page selling a product about watching things had an empty header.
   It now runs the product's own status line: a mood dot that agrees with the
   eye, a clock that actually ticks, and a line that retypes itself as sections
   arrive, reporting on the reader the way the terminal reports on an app.

   Decoration, not content: aria-hidden in the template, and gone below the
   width where the appbar's own buttons start to crowd. */
.lp-bar {
  display: none;
  flex: 1;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
  font-size: var(--fs-0);
  color: var(--ink-muted);
}
@media (min-width: 940px) { .lp-bar { display: flex; } }

.lp-bar-dot {
  flex: none;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--ok);
  transition: background 0.5s ease;
}
.lp-bar-dot[data-mood="warn"] { background: var(--level-warning); }
.lp-bar-dot[data-mood="error"] { background: var(--level-error); }

.lp-bar-clock {
  flex: none;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
/* The line can be long and the appbar cannot grow, so it truncates rather than
   pushing the sign-in buttons off the right edge. */
.lp-bar-line { min-width: 0; overflow: hidden; white-space: nowrap; }
.lp-bar-caret {
  flex: none;
  margin-left: -4px;
  color: var(--accent);
  animation: blink 1.1s steps(1) infinite;
}
@media (prefers-reduced-motion: reduce) {
  .lp-bar-caret { animation: none; }
}

/* ---------- the scene title ----------
   Each section announces itself once, in type too big to be anything but a
   joke and at an opacity too low to be anything but a watermark. It sits
   behind the reading column (.lp is z-index 1) and under the appbar (z-index
   50), and it never takes a click. */
.lp-scene {
  position: fixed;
  inset: 0;
  z-index: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
  user-select: none;
  opacity: 0;
  transition: opacity 0.5s ease;
}
.lp-scene.is-on { opacity: 1; }
/* Big enough to be a joke, faint enough that the paragraph sitting on top of
   it still reads; 0.07 was legible as text and started competing. */
.lp-scene span {
  max-width: min(90vw, 900px);
  padding: 0 var(--space-5);
  font-size: clamp(2rem, 6.5vw, 4.25rem);
  font-weight: 700;
  letter-spacing: -0.05em;
  line-height: 1.05;
  text-align: center;
  text-wrap: balance;
  color: var(--ink);
  opacity: 0.045;
  transform: translateY(14px);
  transition: transform 0.8s cubic-bezier(0.2, 0.9, 0.2, 1);
}
.lp-scene.is-on span { transform: none; }
/* A watermark needs a margin to be a watermark; on a phone it would be behind
   the words themselves. And reduced motion means nothing flashes at all. */
@media (max-width: 940px), (prefers-reduced-motion: reduce) {
  .lp-scene { display: none; }
}

/* ---------- the floating mark ----------
   Takes over from the hero once it scrolls away, on the left where there is
   nothing to collide with. A hairline circle rather than a filled button: it
   should read as the mark still being there, not as a control bolted on. */
.lp-float {
  position: fixed;
  left: clamp(var(--space-4), 4vw, 44px);
  bottom: clamp(var(--space-5), 8vh, 72px);
  z-index: 40;
  display: grid;
  place-items: center;
  width: 64px;
  height: 64px;
  padding: 0;
  cursor: pointer;
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: 50%;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.35s ease, transform 0.35s ease, border-color 0.15s;
}
.lp-float.is-in { opacity: 1; transform: none; }
.lp-float:hover { border-color: var(--accent); }
.lp-float[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
  .lp-float { transition: none; }
}
/* On a phone the column already fills the screen; a floating mark beside it is
   just something in the way. */
@media (max-width: 640px) {
  .lp-float { display: none; }
}

/* ---------- type ---------- */
.lp-display {
  margin: 0;
  font-size: clamp(1.75rem, 5.4vw, 2.75rem);
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1.2;
  color: var(--ink);
  text-wrap: balance;
}
.lp-lede {
  margin: 0;
  font-size: clamp(0.9rem, 1.6vw, 1.03rem);
  line-height: 1.7;
  color: var(--ink-muted);
}
.lp-heading {
  margin: 0;
  font-size: clamp(1.05rem, 2vw, 1.25rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.lp-body { margin: 5px 0 0; font-size: var(--fs-1); line-height: 1.7; color: var(--ink-muted); }
.lp-note { font-size: var(--fs-0); line-height: 1.6; color: var(--ink-faint); }

/* An eyebrow is a label and the rule it starts. */
.lp-eyebrow {
  display: flex; align-items: center; gap: var(--space-3);
  margin: 0 0 var(--space-4);
  font-size: var(--fs-0); font-weight: 700; letter-spacing: 0.19em;
  color: var(--ink-faint);
}
.lp-eyebrow::after { content: ""; flex: 1; height: 1px; background: var(--border); }

/* ---------- hero ---------- */
.lp-hero { padding: clamp(var(--space-6), 9vh, 92px) 0 var(--space-7); }
.lp-hero-mark {
  display: flex; align-items: center; gap: var(--space-4);
  margin-bottom: var(--space-6);
}
.lp-hero-mark .cy-eye { flex: none; }
.lp-hero-word {
  font-size: var(--fs-2); font-weight: 700; letter-spacing: 0.12em;
  color: var(--ink-muted);
}
.lp-hero .lp-lede { max-width: 34em; margin-top: var(--space-5); }

.lp-cta { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-6); }
.lp-facts {
  display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4);
  margin-top: var(--space-5);
  font-size: var(--fs-0); color: var(--ink-faint);
}
.lp-facts span { display: inline-flex; align-items: center; gap: 7px; }
.lp-facts span::before { content: "·"; color: var(--border-bright); }
.lp-facts span:first-child::before { content: none; }

/* ---------- highlights ----------
   Colour on this page still means what it means everywhere else in Cyclopes:
   severity, not emphasis. So a highlighted phrase has to *be* the severity it
   is painted with. Green is a thing working or a cost avoided, amber is a
   degradation, red is a failure, and anything merely important is ink at full
   weight rather than a fourth hue. Break that and the page teaches a colour
   language the product then contradicts. */
.hl-ok { color: var(--ok); }
.hl-warn { color: var(--level-warning); }
.hl-err { color: var(--level-error); }
.hl-ink { color: var(--ink); font-weight: 700; }

/* ---------- sections ---------- */
.lp-section { padding: var(--space-7) 0; border-top: 1px solid var(--border); }

/* ---------- the figures, at the head of their section ----------
   Each section opens with a small live diagram of the mechanism it is about,
   the full width of the reading column.

   They used to live outside the column, in the gutter, alternating sides as
   you went down. Three things were wrong with that. The gutter only exists
   above 1300px, so most readers never saw them at all. A figure beside a
   section is a caption to it, and these are not captions: they are the claim
   the section then explains in words. And 232px is not enough room for a
   diagram to be legible about anything, so all six were drawn small and terse
   and asked the reader to squint at a picture they had no reason to trust.

   At the head of the section, at the width of the words, they are the first
   thing read, which is the right order: every one of them shows the
   mechanism the paragraph below then names.

   HTML and CSS rather than SVG. An SVG figure is drawn once at one size and
   scaled, so its type is whatever the scale factor makes it: at gutter width
   the labels were 11px, and the same drawings stretched to a 760px column
   would set them at 26. Here the text is text: it sets at the size the rest
   of the page sets at, wraps when the window is narrow, and is picked up by
   the reader's own font settings. */
.lp-figure {
  margin: 0 0 var(--space-6);
  padding: var(--space-5);
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-m);
  /* Arrives with its section rather than sitting there from the start: the
     page should look like it is being written as you read it. */
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.7s ease, transform 0.7s ease;
}
.lp-figure.is-in { opacity: 1; transform: none; }
.lp-figure-cap {
  margin: var(--space-4) 0 0;
  font-size: var(--fs-0); color: var(--ink-faint);
  text-align: center;
}

/* On a phone the frame is competing with the thing inside it for the width of
   the screen, and the thing inside it should win. */
@media (max-width: 560px) {
  .lp-figure { padding: var(--space-4); }
}

/* Nothing animates until it is on screen, and nothing animates at all for a
   reader who asked for stillness: `is-running` is added by landing-figures.js
   and every keyframe below is gated on it. */
@media (prefers-reduced-motion: reduce) {
  .lp-figure { opacity: 1; transform: none; transition: none; }
  .lp-figure * { animation: none !important; }
}

/* A section headline is a headline, but the hero stays the loudest thing on
   the page. */
.lp-section .lp-display { font-size: clamp(1.35rem, 3.8vw, 1.95rem); }
.lp-section .lp-display + .lp-lede { margin-top: var(--space-4); }
/* The eyebrow's block introduces what follows; give it room to be a preamble
   rather than the first item in the list. */
.lp-section > .lp-block:first-child { margin-bottom: var(--space-6); }

/* ---------- the incident ----------
   The rail runs the whole height of a record, the way it runs down the feed's
   margin: a tick you can read like a seismograph. */
.lp-record {
  display: grid;
  grid-template-columns: 3px 8ch 1fr;
  grid-template-rows: auto auto;
  gap: 0 var(--space-4);
  margin-bottom: var(--space-5);
}
.lp-record::before {
  content: ""; grid-column: 1; grid-row: 1 / 3;
  background: var(--border); border-radius: 1px; margin: 3px 0;
}
.lp-clock { grid-column: 2; grid-row: 1; }
.lp-record-title { grid-column: 3; grid-row: 1; }
.lp-record .lp-body { grid-column: 3; grid-row: 2; }
.lp-record-warn::before { background: var(--level-warning); }
.lp-record-error::before { background: var(--level-error); }
.lp-clock { font-size: var(--fs-0); color: var(--ink-faint); line-height: 1.45; font-variant-numeric: tabular-nums; }
.lp-record-title {
  margin: 0; font-size: var(--fs-2); font-weight: 700; line-height: 1.45; color: var(--ink);
}
.lp-record-warn .lp-record-title { color: var(--level-warning); }
.lp-record-error .lp-record-title { color: var(--level-error); }
.lp-record .lp-body { grid-column: 3; }

/* ---------- what you get ----------
   Name, claim, detail. The claim is the line doing the selling, so it is set
   in ink between two quieter greys. Without it the section was six headings
   and six identical paragraphs, which scans as a spec sheet. */
.lp-surface {
  display: grid; grid-template-columns: 22px 1fr; gap: 0 var(--space-2);
  margin-bottom: var(--space-5);
}
.lp-surface-mark { color: var(--ok); font-size: var(--fs-3); line-height: 1.4; }
.lp-surface > div { grid-column: 2; }
.lp-claim {
  margin: 6px 0 0;
  font-size: var(--fs-2); font-weight: 700; line-height: 1.5;
  letter-spacing: -0.015em;
  color: var(--ink);
  text-wrap: balance;
}
.lp-claim + .lp-body { margin-top: 7px; }

/* A pitch is a claim the page is prepared to be held to, so it gets a frame
   and the accent rail: the same weight the app gives a card, not the weight
   it gives a banner. */
.lp-pitch {
  padding: var(--space-5);
  margin: var(--space-6) 0 var(--space-5);
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-left: 2px solid var(--accent);
  border-radius: var(--radius-m);
}
.lp-pitch .lp-eyebrow { margin-bottom: var(--space-3); }
.lp-pitch .lp-heading + .lp-body { margin-top: var(--space-3); }
.lp-pitch .lp-snippet { margin: var(--space-4) 0; border-radius: var(--radius-m); }
.lp-pitch .lp-note { margin: 0; }
.lp-pitch b { color: var(--ink); }

/* The cost ledger. Deliberately a definition list: each line is a rule with a
   name, and the names are the part someone repeats to their finance person. */
.lp-ledger {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4) var(--space-5);
  margin: var(--space-5) 0 0;
}
.lp-ledger dt {
  font-size: var(--fs-0); font-weight: 700; letter-spacing: -0.01em;
  color: var(--ink);
}
.lp-ledger dt::before { content: "· "; color: var(--accent); }
.lp-ledger dd {
  margin: 4px 0 0;
  font-size: var(--fs-0); line-height: 1.6; color: var(--ink-muted);
}
.lp-ledger em { color: var(--ink); font-style: normal; }
@media (max-width: 640px) {
  .lp-ledger { grid-template-columns: 1fr; }
  .lp-pitch { padding: var(--space-4); }
}

/* ---------- the phone's edit ----------
   A paragraph that reads as one confident line on a monitor reads as six on a
   phone, and this page has twenty-four of them. `lp-trim` marks the trailing
   elaboration of the longest few: the sentence you would drop first if
   somebody asked you to say it faster.

   Hidden rather than cut, because on a wide screen that colour is what makes
   the page worth reading, and still in the document, because it is the same
   page either way as far as anything crawling it is concerned. Nothing that
   carries an argument on its own is in here: every section, every claim and
   every price survives the trim. */
@media (max-width: 640px) {
  .lp-trim { display: none; }
}

/* The audience line sits under the buttons, so it reads as a qualifier on the
   offer rather than as a second headline: smaller than the lede above it, and
   held at the muted ink. */
.lp-audience {
  margin-top: var(--space-4);
  font-size: var(--fs-1); color: var(--ink-muted);
  max-width: 46ch;
}

/* ---------- the proof ----------
   The only measured numbers on the page, so they are set like readings rather
   than like a marketing stat row: mono, ink, no colour, no icons. Colour is
   reserved for severity everywhere else in this product and a green uptime
   figure would be the page congratulating itself. */
.lp-proof { border-left: 2px solid var(--accent); padding-left: var(--space-4); }
.lp-proof-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-4) var(--space-5);
  margin: var(--space-5) 0 var(--space-4);
}
.lp-proof-grid dt {
  font-family: var(--font-mono);
  font-size: var(--fs-4); line-height: 1.1; letter-spacing: -0.02em;
  color: var(--ink);
}
.lp-proof-grid dd {
  margin: 7px 0 0;
  font-size: var(--fs-0); line-height: 1.55; color: var(--ink-muted);
}

/* ---------- the ladder ----------
   Four tiers read side by side, because the question is comparative: nobody
   wants to know what Team costs, they want to know what Team has that Solo
   does not. Same card weight as .lp-pitch, one accent rail on the tier most
   people land on, and no colour anywhere else: price is not a severity. */
/* auto-fit rather than a fixed count: the ladder gained two rungs and will
   gain more, and a hard `repeat(4)` turns that into six columns of 90px on a
   laptop. The 168px floor is the width at which "10 journeys, 90 days of
   history" still sets on two lines. */
/* Six tiers do not fit a 760px reading column: auto-fit lands on four and
   leaves a ragged two on the next row, which reads as a broken grid rather than
   a price list. So the ladder breaks out of the column to the width it needs,
   centred, and only the ladder does. Prose stays where prose belongs. */
.lp-plans {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
  gap: var(--space-3);
  margin: var(--space-5) 0 var(--space-4);
  width: min(1180px, calc(100vw - var(--space-6) * 2));
  margin-left: 50%;
  transform: translateX(-50%);
}
/* Below the breakout width the transform is doing nothing useful and the
   column is the whole screen anyway. */
@media (max-width: 900px) {
  .lp-plans { width: auto; margin-left: 0; transform: none; }
}
.lp-plan {
  display: flex; flex-direction: column; gap: var(--space-2);
  padding: var(--space-4);
  background: var(--bg-1);
  border: 1px solid var(--border);
  border-radius: var(--radius-m);
}
.lp-plan[data-featured="true"] { border-left: 2px solid var(--accent); }
.lp-plan-name {
  margin: 0;
  font-size: var(--fs-0); font-weight: 700; letter-spacing: -0.01em;
  color: var(--ink);
}
.lp-plan-tag { font-size: var(--fs-0); font-weight: 400; color: var(--accent); }
.lp-plan-price { margin: 0; font-size: var(--fs-4); line-height: 1.2; color: var(--ink); }
.lp-plan-price small { font-size: var(--fs-0); color: var(--ink-muted); }
.lp-plan-list { margin: 0; padding: 0; list-style: none; flex: 1; }
.lp-plan-list li {
  margin-top: 7px;
  font-size: var(--fs-0); line-height: 1.55; color: var(--ink-muted);
}
.lp-plan-list li::before { content: "· "; color: var(--border-bright); }
.lp-plan .btn { align-self: flex-start; }
/* auto-fit already handles the middle widths; the phone case still needs
   saying, because two 168px columns technically fit and read as a spreadsheet. */
@media (max-width: 520px) { .lp-plans { grid-template-columns: 1fr; } }

/* ---------- the terminal, standing in for the argument ----------
   A pricing page makes claims about volume and cost that a reader has no way
   to check. These panes answer with the product's own log view instead: real
   `.term-line` markup, real level colours, and the price sitting on the rows
   that carry it. If the terminal ever changes, these change with it, because
   they are not a picture of it.

   Chrome only, deliberately: the rows are the product's, this file owns the
   window around them. No script: the lines arrive on a stagger the moment
   the block they sit in does, and then hold. A pricing page that loops an
   animation forever is a pricing page nobody finishes reading. */
.lp-term {
  margin: var(--space-5) 0 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-m);
  background: var(--t-bg);
  overflow: hidden;
}
.lp-term-bar {
  display: flex; align-items: center; gap: var(--space-3);
  padding: 7px var(--space-3);
  background: var(--bg-1);
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-0);
  color: var(--ink-faint);
}
.lp-term-dots { display: inline-flex; gap: 5px; flex: none; }
.lp-term-dots i {
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--border-bright);
}
.lp-term-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Pushed to the right edge and coloured like the thing it reports: green while
   the stream is merely running, red once it is paging somebody. */
.lp-term-live {
  margin-left: auto; flex: none;
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--ok);
}
.lp-term-live::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: currentColor;
}
.lp-term-live.is-err { color: var(--level-error); }

.lp-term-body {
  padding: var(--space-3);
  font-family: var(--font-mono, ui-monospace, monospace);
  font-size: 11.5px;
  line-height: 1.75;
  color: var(--t-fg);
  overflow-x: auto;
}
/* The rows are narrower here than in the real terminal, which has a whole
   window; the reading column has 760px and prose either side of it. */
.lp-term .term-line {
  grid-template-columns: 9ch 9ch 5ch 1fr;
  column-gap: var(--space-3);
}
@media (max-width: 720px) {
  .lp-term .term-line { grid-template-columns: 9ch 5ch 1fr; }
}
.lp-term .term-line:hover { background: none; }

.lp-term-foot {
  display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4);
  margin: var(--space-3) 0 0;
  padding-top: var(--space-2);
  border-top: 1px dashed var(--border);
  font-size: var(--fs-0);
  color: var(--ink-faint);
}
.lp-term-caret { margin-left: auto; color: var(--accent); }

/* Arrival. The rows are already in the document; this is only when they are
   allowed to be seen, and it is gated on the same `is-in` the surrounding
   block gets, so the pane fills as you reach it rather than before. Outside
   the reveal contract (no data-reveal, no script, reduced motion) every row
   simply sits there at full opacity, which is the readable state. */
@media (prefers-reduced-motion: no-preference) {
  html.js [data-reveal] .lp-term .term-line { opacity: 0; }
  html.js [data-reveal] .lp-block.is-in .lp-term .term-line {
    animation: lp-term-line 0.4s ease both;
    animation-delay: calc(var(--i, 0) * 0.11s + 0.15s);
  }
  html.js [data-reveal] .lp-term-foot { opacity: 0; }
  html.js [data-reveal] .lp-block.is-in .lp-term-foot {
    animation: lp-term-line 0.5s ease both;
    animation-delay: 1.05s;
  }
  .lp-term-caret { animation: lp-caret 1.1s steps(1) infinite; }
}
@keyframes lp-term-line {
  from { opacity: 0; transform: translateY(5px); }
  to { opacity: 1; transform: none; }
}
@keyframes lp-caret {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0; }
}

/* ---------- the wire ---------- */
.lp-tabs { display: flex; gap: var(--space-1); margin-bottom: -1px; }
.lp-tab {
  font: inherit; font-size: var(--fs-0); cursor: pointer;
  padding: 6px 12px; color: var(--ink-faint);
  background: none; border: 1px solid transparent; border-bottom: 0;
  border-radius: var(--radius-m) var(--radius-m) 0 0;
}
.lp-tab:hover { color: var(--ink); }
.lp-tab[aria-selected="true"] {
  color: var(--ink); background: var(--bg-1);
  border-color: var(--border); border-bottom: 0;
}
.lp-snippet {
  margin: 0; padding: var(--space-4);
  background: var(--bg-1);
  border: 1px solid var(--border); border-radius: 0 var(--radius-m) var(--radius-m);
  overflow-x: auto; font-size: var(--fs-0); line-height: 1.7; color: var(--ink);
}
.lp-snippet[hidden] { display: none; }

/* ---------- close ---------- */
.lp-close { padding: var(--space-7) 0 var(--space-6); border-top: 1px solid var(--border); }
.lp-close .lp-display { font-size: clamp(1.35rem, 3.6vw, 1.85rem); }
.lp-foot {
  display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-5);
  align-items: center;
  padding-top: var(--space-5); border-top: 1px solid var(--border);
  font-size: var(--fs-0); color: var(--ink-faint);
}
.lp-foot .cy-eye { opacity: 0.7; }

/* ---------- chips ----------
   A vocabulary, laid out as one. Used for the things that can page you and
   the channels they can page you on: both are closed sets, and printing the
   whole set is the claim. */
.lp-chips {
  display: flex; flex-wrap: wrap; gap: var(--space-2);
  margin: var(--space-4) 0 0; padding: 0; list-style: none;
}
.lp-chips li {
  padding: 3px 9px;
  font-size: var(--fs-0); color: var(--ink-muted);
  background: var(--bg-1);
  border: 1px solid var(--border); border-radius: var(--radius-m);
}
.lp-chips-legend { margin: var(--space-5) 0 0; font-size: var(--fs-0); color: var(--ink-faint); }


/* The page's own records at the same breakpoint. Kept apart from the demo's
   rules above so the demo can be lifted into its own file whole. */
@media (max-width: 620px) {
  .lp-record { grid-template-columns: 3px 1fr; grid-template-rows: auto auto auto; }
  .lp-record::before { grid-row: 1 / 4; }
  .lp-clock { grid-column: 2; grid-row: 1; }
  .lp-record-title { grid-column: 2; grid-row: 2; }
  .lp-record .lp-body { grid-column: 2; grid-row: 3; }
  .lp-surface { grid-template-columns: 1fr; }
  .lp-surface-mark { display: none; }
  .lp-surface > div { grid-column: 1; }
}

/* ---------- the figures ----------
   Six diagrams, one per section, each animating the mechanism its section
   then explains. HTML and CSS only: no canvas, no library, no SVG scaled to a
   size it was not drawn at, nothing that costs a frame budget on a phone.

   Every keyframe is gated on `.is-running`, added by landing-figures.js when a
   figure scrolls into view and removed when it leaves. Six loops all running
   behind the fold is how a landing page warms somebody's laptop while they
   read the footer.

   The shared vocabulary is small on purpose: a clock, a line of code, a note,
   and the three severities. It is the same vocabulary a record on this page
   uses, because a figure and the paragraph under it should not look like they
   came from different products. */
.fig { font-family: var(--font-mono); }
.fig .fig-code { color: var(--ink); font-size: var(--fs-1); }
.fig .fig-clock {
  color: var(--ink-faint); font-size: var(--fs-1);
  font-variant-numeric: tabular-nums;
}
.fig .fig-note { color: var(--ink-faint); font-size: var(--fs-0); }
.fig .fig-str { color: var(--accent-dim); }
.fig .fig-dim { color: var(--ink-faint); }
.fig .is-ok { color: var(--ok); }
.fig .is-warn { color: var(--level-warning); }
.fig .is-err { color: var(--level-error); }
.fig .fig-foot { margin: var(--space-4) 0 0; }

/* Two states in one cell, so the taller sets the height and nothing above the
   readout moves when the state changes. Used by the night and the ceiling,
   which both tell a two-beat story: it was fine, and then it was not. */
.fig-readout { display: grid; }
.fig-state {
  grid-area: 1 / 1;
  margin: 0;
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: var(--space-2) var(--space-4);
}

/* --- the night: ticks arrive, three of them spike ---
   The rail is a flex row of equal ticks rather than 64 placed rectangles, so
   it is exactly as wide as the column at every width and the spike lands in
   the same place on a phone as on a desk. */
.fig-night .fig-rail {
  display: flex; align-items: flex-end; gap: 2px;
  height: 54px;
  margin-bottom: var(--space-4);
  border-bottom: 1px solid var(--border);
  padding-bottom: var(--space-3);
}
.fig-night .fig-tick {
  flex: 1;
  min-width: 2px;
  height: 18px;
  border-radius: 1px;
  background: var(--border-bright);
  opacity: 0;
}
.fig-night.is-running .fig-tick {
  animation: figTick 9s linear infinite;
  animation-delay: calc(var(--i) * 0.048s);
}
.fig-night.is-running .fig-tick.is-spike { animation-name: figSpike; }
@keyframes figTick {
  0% { opacity: 0; }
  6%, 92% { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes figSpike {
  0% { opacity: 0; }
  6%, 54% { opacity: 1; height: 18px; background: var(--border-bright); }
  60%, 92% { opacity: 1; height: 46px; background: var(--level-error); }
  100% { opacity: 0; height: 18px; }
}
.fig-night-broken { opacity: 0; }
.fig-night.is-running .fig-night-calm { animation: figWas 9s linear infinite; }
.fig-night.is-running .fig-night-broken { animation: figBecomes 9s linear infinite; }
@keyframes figWas { 0%, 52% { opacity: 1; } 60%, 100% { opacity: 0; } }
@keyframes figBecomes { 0%, 52% { opacity: 0; } 60%, 92% { opacity: 1; } 100% { opacity: 0; } }

/* --- the surfaces: one lit at a time, in the order the nav has them --- */
.fig-surfaces .fig-surface-row {
  list-style: none; margin: 0; padding: 0;
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: var(--space-3);
}
.fig-surfaces .fig-surface {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-m);
  opacity: 0.4;
  text-align: center;
}
.fig-surfaces .fig-glyph { color: var(--ink-faint); font-size: var(--fs-4); line-height: 1; }
.fig-surfaces .fig-label { color: var(--ink-muted); font-size: var(--fs-0); }
.fig-surfaces.is-running .fig-surface {
  animation: figSurface 7.2s linear infinite;
  animation-delay: calc(var(--i) * 1.2s);
}
@keyframes figSurface {
  0%, 2% { opacity: 0.4; border-color: var(--border); }
  6%, 14% { opacity: 1; border-color: var(--border-bright); }
  20%, 100% { opacity: 0.4; border-color: var(--border); }
}
.fig-surfaces.is-running .fig-surface .fig-glyph {
  animation: figGlyph 7.2s linear infinite;
  animation-delay: calc(var(--i) * 1.2s);
}
@keyframes figGlyph {
  0%, 2% { color: var(--ink-faint); }
  6%, 14% { color: var(--accent); }
  20%, 100% { color: var(--ink-faint); }
}
/* Six across needs six across of room. Below that it folds to three and then
   to two, which is a nav bar wrapping, not a different figure. */
@media (max-width: 700px) {
  .fig-surfaces .fig-surface-row { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 380px) {
  .fig-surfaces .fig-surface-row { grid-template-columns: repeat(2, 1fr); }
}

/* --- the swap: one host replaces another, nothing else moves --- */
.fig-swap .fig-source {
  margin: 0;
  font-family: inherit;
  font-size: var(--fs-1);
  line-height: 1.7;
  color: var(--ink);
  overflow-x: auto;
}
/* The longest line is the dsn, and it is the one line a reader will check
   character by character, so on a narrow screen the type gives way rather
   than the string: a dsn clipped at `sentry.ic` is worse than a small dsn.
   Sized off the window below the point where thirty-four monospace columns
   stop fitting, with a floor that is still readable: one line of dsn, on
   every phone anybody still carries. */
@media (max-width: 620px) {
  .fig-swap .fig-source { font-size: clamp(10.5px, 2.9vw, var(--fs-1)); }
}
/* The two hosts share one cell, each carrying the tail of the line with it,
   so the swap replaces a whole string rather than leaving the width of the
   longer host sitting empty after the shorter one. Justified left and
   baseline-aligned, or the swapped-in host would sit a hair above the string
   it is inside. */
.fig-swap .fig-host {
  display: inline-grid;
  grid-template-columns: auto;
  justify-items: start;
  align-items: baseline;
  position: relative;
}
.fig-swap .fig-host-old,
.fig-swap .fig-host-new { grid-area: 1 / 1; }
.fig-swap .fig-host-new { opacity: 0; }
/* The new host is the one thing on the line that is a *result*, so it is the
   one thing painted with the colour a result gets. */
.fig-swap .fig-host-new .fig-str { color: var(--ok); }
.fig-swap.is-running .fig-host-old { animation: figWas 6s ease-in-out infinite; }
.fig-swap.is-running .fig-host-new { animation: figHostIn 6s ease-in-out infinite; }
@keyframes figHostIn {
  0%, 52% { opacity: 0; }
  62%, 100% { opacity: 1; }
}
.fig-swap .fig-caret {
  grid-area: 1 / 1;
  width: 7px; height: 1.1em;
  align-self: center;
  background: var(--accent);
  opacity: 0;
}
.fig-swap.is-running .fig-caret { animation: figCaret 6s steps(1) infinite; }
@keyframes figCaret {
  0%, 40% { opacity: 0; }
  44%, 46% { opacity: 1; }
  48%, 50% { opacity: 0; }
  52%, 54% { opacity: 1; }
  56%, 100% { opacity: 0; }
}

/* --- the ceiling: spend climbs, then degrades rather than breaks --- */
.fig-ceiling .fig-head { margin: 0 0 var(--space-2); }
.fig-ceiling .fig-meter {
  position: relative;
  height: 14px;
  border: 1px solid var(--border);
  border-radius: 2px;
  /* Room above for the cap's label, which hangs off the line rather than
     sitting in the row above it. */
  margin: 0 0 var(--space-4);
}
.fig-ceiling .fig-fill {
  height: 100%;
  width: 0;
  background: var(--ok);
  border-radius: 1px;
}
.fig-ceiling.is-running .fig-fill { animation: figFill 8s ease-in-out infinite; }
@keyframes figFill {
  0% { width: 0; background: var(--ok); }
  55% { width: 85%; background: var(--ok); }
  62%, 92% { width: 85%; background: var(--level-warning); }
  100% { width: 0; background: var(--ok); }
}
.fig-ceiling .fig-cap-line {
  position: absolute; top: -5px; bottom: -5px; left: 85%;
  border-left: 1px dashed var(--ink-faint);
}
.fig-ceiling .fig-cap-label {
  position: absolute; bottom: calc(100% + 2px); left: 5px;
  line-height: 1;
}
.fig-ceiling-degraded { opacity: 0; }
.fig-ceiling.is-running .fig-ceiling-sending { animation: figWas 8s linear infinite; }
.fig-ceiling.is-running .fig-ceiling-degraded { animation: figBecomes 8s linear infinite; }

/* --- the ladder: stops on a rail, lighting in order, then an ack --- */
.fig-ladder .fig-climb {
  list-style: none; margin: 0; padding: 0;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-4);
}
.fig-ladder .fig-rung {
  display: grid;
  grid-template-rows: auto auto auto;
  gap: var(--space-2);
  opacity: 0.35;
}
/* Each stop owns the length of rail that leaves it, so the rail runs the width
   of the figure and stops dead at the ack: nothing continues past it, because
   the climb does not either. */
.fig-ladder .fig-rail {
  position: relative;
  display: block;
  height: 9px;
}
.fig-ladder .fig-rail::before {
  content: "";
  position: absolute; top: 4px; left: 9px; right: calc(-1 * var(--space-4));
  border-top: 1px solid var(--border);
}
.fig-ladder .fig-rung:last-child .fig-rail::before { content: none; }
.fig-ladder .fig-dot {
  position: absolute; top: 0; left: 0;
  height: 9px; width: 9px;
  border-radius: 50%;
  background: var(--border-bright);
}
.fig-ladder .fig-code { line-height: 1.45; }
.fig-ladder.is-running .fig-rung {
  animation: figRung 10s ease-in-out infinite;
  animation-delay: calc(var(--i) * 1.6s);
}
@keyframes figRung {
  0%, 4% { opacity: 0.35; }
  10%, 52% { opacity: 1; }
  60%, 100% { opacity: 0.35; }
}
.fig-ladder.is-running .fig-rung .fig-dot {
  animation: figRungDot 10s ease-in-out infinite;
  animation-delay: calc(var(--i) * 1.6s);
}
@keyframes figRungDot {
  0%, 4% { background: var(--border-bright); }
  10%, 52% { background: var(--level-error); }
  60%, 100% { background: var(--border-bright); }
}
/* The ack is the one stop that is not a page, so it arrives late, in green,
   and it is what the previous three were climbing towards rather than a
   fourth rung. */
.fig-ladder.is-running .fig-rung-ack { animation-name: figAck; }
@keyframes figAck {
  0%, 56% { opacity: 0; }
  64%, 92% { opacity: 1; }
  100% { opacity: 0; }
}
.fig-ladder.is-running .fig-rung-ack .fig-dot { animation-name: figAckDot; }
@keyframes figAckDot {
  0%, 56% { background: var(--border-bright); }
  64%, 92% { background: var(--ok); }
  100% { background: var(--border-bright); }
}
/* Four stops across is a timeline; four stops in 90px each is a word list.
   Below the fold-point it goes back to being a ladder, which is the shape the
   same information has when it is stacked. */
@media (max-width: 660px) {
  .fig-ladder .fig-climb { grid-template-columns: 1fr; gap: var(--space-3); }
  .fig-ladder .fig-rung {
    grid-template-columns: 4.5ch 9px 1fr;
    align-content: start;
    grid-template-rows: auto;
    align-items: center;
    gap: var(--space-3);
  }
  .fig-ladder .fig-rail { height: 100%; min-height: 9px; }
  .fig-ladder .fig-rail::before {
    top: 9px; bottom: calc(-1 * var(--space-3)); left: 4px; right: auto;
    border-top: 0; border-left: 1px solid var(--border);
  }
}

/* --- the pocket: the same mark, on two screens --- */
.lp-figure-pocket .fig-pocket {
  display: flex; align-items: flex-end; justify-content: center;
  gap: var(--space-5);
}
.fig-pocket .fig-desk {
  flex: 1 1 auto;
  max-width: 420px;
  background: var(--bg-0);
  border: 1px solid var(--border-bright);
  border-radius: var(--radius-m);
  overflow: hidden;
}
.fig-pocket .fig-desk-bar {
  display: flex; align-items: center; gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border);
}
.fig-pocket .fig-desk-name { color: var(--ink-muted); font-size: var(--fs-0); }
.fig-pocket .fig-desk-live {
  margin-left: auto;
  color: var(--ok); font-size: var(--fs-0);
}
.fig-pocket .fig-desk-body {
  display: flex; flex-direction: column; gap: 9px;
  padding: var(--space-4);
}
.fig-pocket .fig-phone {
  flex: 0 0 auto;
  width: 112px;
  /* A phone is taller than it is wide, and four stacked children do not make
     it so on their own: without this the handset next to the desk panel reads
     as a small square, which is a tile, not a device. */
  min-height: 196px;
  padding: var(--space-3);
  display: flex; flex-direction: column; align-items: center; gap: 9px;
  background: var(--bg-0);
  border: 1px solid var(--border-bright);
  border-radius: 14px;
}
.fig-pocket .fig-phone-bar {
  width: 34px; height: 3px; border-radius: 2px;
  background: var(--border-bright);
  margin-bottom: var(--space-2);
}
.fig-pocket .fig-eye { display: block; }
.fig-pocket .fig-phone .fig-eye { margin-bottom: var(--space-2); }
.fig-pocket .fig-line {
  width: 100%; height: 5px; border-radius: 2px;
  background: var(--border);
}
.fig-pocket .fig-line.is-short { width: 42%; }
.fig-pocket .fig-line.is-mid { width: 68%; }
.fig-pocket .fig-phone .fig-line { width: 68px; }
.fig-pocket .fig-phone .fig-line.is-short { width: 42px; }
/* Side by side is the comparison. Stacked, it is two pictures, so below the
   fold-point the desk panel goes and the handset stands alone, which is what
   the reader is holding at that width anyway. */
@media (max-width: 560px) {
  .fig-pocket .fig-desk { display: none; }
}
