/* Crashboard — crashboard.net
   Three static pages. No frameworks, no fonts fetched, no scripts, no trackers.

   The palette is not an approximation of the app's: the ramp stops below are
   computed from Barometer.hue(percentile:) and Barometer.maxChroma(_:_:) in
   CrashboardCore, at the same lightness the app paints (0.74 light, 0.44 dark).
   The paper is the same hue at the top of the lightness range and almost no
   chroma -- hue 73, the ochre the dashboard sits on for a middling reading --
   so the page is warm the way the app is warm without pretending to be a
   reading. It was #fbfbfa, one per cent off pure white, which read as nothing.
   See scripts/check-site-copy.py, which keeps this file honest about the
   disclaimer the way check-store-copy.sh does for the listing. */

:root {
  color-scheme: light dark;

  --ink:        #111213;
  --ink-soft:   #55585c;
  --ink-faint:  #85898e;
  --paper:      #faf4ed;
  --paper-sunk: #f2eae0;
  --rule:       #e0d8ce;

  --ramp: linear-gradient(90deg,
      #eb1d2e 0% 20%,
      #d97b31 20% 40%,
      #d1a84c 40% 60%,
      #c3d069 60% 80%,
      #acf3ae 80% 100%);

  --measure: 34rem;
  --sans: "Helvetica Neue", Helvetica, Arial, "Liberation Sans", system-ui, sans-serif;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
}

@supports (color: oklch(0.74 0.15 25)) {
  :root {
    --paper:      oklch(0.970 0.012 73);
    --paper-sunk: oklch(0.940 0.016 73);
    --rule:       oklch(0.885 0.016 73);
    --ramp: linear-gradient(90deg,
        oklch(0.60 0.2315 25) 0% 20%,
        oklch(0.68 0.1445 55) 20% 40%,
        oklch(0.75 0.1193 85) 40% 60%,
        oklch(0.82 0.1287 115) 60% 80%,
        oklch(0.90 0.1172 145) 80% 100%);
  }
}

@media (prefers-color-scheme: dark) {
  :root {
    --ink:        #ecedee;
    --ink-soft:   #a6a9ad;
    --ink-faint:  #74777b;
    --paper:      #120f0c;
    --paper-sunk: #1f1b17;
    --rule:       #33302b;

    --ramp: linear-gradient(90deg,
        #64050d 0% 20%,
        #653610 20% 40%,
        #685221 40% 60%,
        #666d34 60% 80%,
        #4d8b50 80% 100%);
  }
  @supports (color: oklch(0.44 0.15 25)) {
    :root {
      --ramp: linear-gradient(90deg,
          oklch(0.32 0.1248 25) 0% 20%,
          oklch(0.39 0.0833 55) 20% 40%,
          oklch(0.45 0.0720 85) 40% 60%,
          oklch(0.51 0.0806 115) 60% 80%,
          oklch(0.58 0.1098 145) 80% 100%);
    }
  }
}

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

.wrap { max-width: 46rem; margin: 0 auto; padding: 0 1.5rem; }

a { color: inherit; text-underline-offset: 0.18em; text-decoration-thickness: 1px; }
a:hover { text-decoration-thickness: 2px; }

/* ---- masthead ---------------------------------------------------------- */

.masthead {
  display: flex; align-items: baseline; gap: 1rem 1.25rem; flex-wrap: wrap;
  padding: 2.25rem 0 1.5rem;
}
.wordmark {
  font-size: 0.78rem; font-weight: 700; letter-spacing: 0.22em;
  text-decoration: none; white-space: nowrap;
}
.masthead .tag {
  font-size: 0.78rem; color: var(--ink-faint); margin-right: auto;
}
.masthead nav { display: flex; gap: 1.1rem; font-size: 0.85rem; }
.masthead nav a { color: var(--ink-soft); text-decoration: none; }
.masthead nav a:hover, .masthead nav a[aria-current="page"] {
  color: var(--ink); text-decoration: underline;
}

.band { height: 6px; background: var(--ramp); }

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

h1 {
  font-size: clamp(2.1rem, 6.2vw, 3.4rem);
  font-weight: 700; letter-spacing: -0.035em; line-height: 1.04;
  margin: 2.5rem 0 0; text-wrap: balance;
}
.lede {
  font-size: clamp(1.05rem, 2.4vw, 1.3rem);
  color: var(--ink-soft); max-width: var(--measure);
  margin: 1.1rem 0 0; text-wrap: pretty;
}
h2 {
  font-size: 1.32rem; font-weight: 700; letter-spacing: -0.018em;
  margin: 3.25rem 0 0.65rem;
}
h3 { font-size: 1rem; font-weight: 700; margin: 1.75rem 0 0.3rem; }
p, li { max-width: var(--measure); text-wrap: pretty; }
p { margin: 0.85rem 0; }
ul { padding-left: 1.1rem; margin: 0.85rem 0; }
li { margin: 0.4rem 0; }
strong { font-weight: 700; }

.eyebrow {
  font-size: 0.68rem; font-weight: 700; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--ink-faint);
  margin: 3.25rem 0 0.5rem;
}
.muted { color: var(--ink-soft); }
.small { font-size: 0.86rem; }

/* ---- figures ----------------------------------------------------------- */

.scale { margin: 2.5rem 0 0; }
.scale .bar {
  height: 2.5rem; border-radius: 5px; background: var(--ramp);
}
.scale .ends {
  display: flex; justify-content: space-between;
  font-size: 0.72rem; color: var(--ink-faint); margin-top: 0.5rem;
  font-family: var(--mono);
}

.facts {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
  gap: 1.25rem 1.5rem; margin: 2.25rem 0 0;
  border-top: 1px solid var(--rule); padding-top: 1.25rem;
}
.facts div { max-width: none; }
.facts dt, .facts .n {
  font-size: 1.9rem; font-weight: 700; letter-spacing: -0.03em; line-height: 1;
}
.facts dd, .facts .l {
  margin: 0.35rem 0 0; font-size: 0.79rem; color: var(--ink-soft);
  line-height: 1.35;
}

.readings {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: 1.75rem; margin: 1.5rem 0 0;
}
.readings > div { max-width: none; }

/* The largest push, under each reading. A row rather than a table: there is
   one of them now, and a one-row table reads as a table that failed. */
/* Each column is a column, so the push can be pushed to the foot of it. The
   two readings do not produce the same amount of text -- "of the record was
   more stressed" wraps where "of the record read lower" does not -- and
   without this the two rules under them sit at different heights, which reads
   as a mistake rather than as a difference in the labels. */
.readings > div { display: flex; flex-direction: column; }
.push {
  display: flex; align-items: baseline; gap: 0.6rem; flex-wrap: wrap;
  margin: 1.25rem 0 0; padding-top: 1rem; border-top: 1px solid var(--rule);
}
.readings .push { margin-top: auto; }
.push-lab {
  font-size: 0.72rem; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-soft);
}
.push-who { font-weight: 600; }
.push .num { margin-left: auto; font-variant-numeric: tabular-nums; }
.readings h3 { margin-top: 0; }
.readings p { max-width: none; }

.sources {
  list-style: none; padding: 0; margin: 1rem 0 0;
  font-size: 0.86rem; color: var(--ink-soft);
}
.sources li {
  max-width: none; padding: 0.6rem 0; border-top: 1px solid var(--rule);
  margin: 0;
}
.sources li b { color: var(--ink); font-weight: 700; }

.price {
  border: 1px solid var(--rule); border-radius: 8px;
  padding: 1.4rem 1.5rem; margin: 1.25rem 0 0; background: var(--paper-sunk);
}
.price p { max-width: none; }
.price .free { font-weight: 700; }

/* ---- the legal notice -------------------------------------------------- */

.important {
  border-top: 1px solid var(--rule);
  margin-top: 3.5rem; padding-top: 1.5rem;
}
/* Set at the size of the prose above it, not in the small type a disclaimer
   usually gets. This is the part with legal weight, and it used to sit under a
   paragraph that said the same thing in body size and larger words -- that
   paragraph is gone, so this one carries it, and carries it legibly. Still the
   softer ink, because the rule above and the colour are what mark it out as a
   notice rather than more of the argument. */
.important p {
  color: var(--ink-soft);
}

/* The publishing cadence, set apart from the attribution it follows: one is a
   legal requirement and the other is the practical fact a reader wants, and
   running them together as one sentence read as more licence text. */
.cadence {
  display: block; margin-top: 0.2rem;
  font-size: 0.82rem; color: var(--ink-faint);
}

footer { padding: 0 0 4rem; }
.colophon {
  display: flex; gap: 0.75rem 1.25rem; flex-wrap: wrap; align-items: baseline;
  margin-top: 1.75rem; font-size: 0.8rem; color: var(--ink-faint);
}
.colophon nav { display: flex; gap: 1.25rem; margin-left: auto; }
.colophon a { color: var(--ink-soft); }

/* ---- contact ----------------------------------------------------------- */

.contact {
  border: 1px solid var(--rule); border-radius: 8px;
  padding: 1.5rem; margin: 1.5rem 0 0; background: var(--paper-sunk);
}
.contact p { max-width: none; margin: 0.5rem 0 0; }
.contact .addr {
  font-size: clamp(1.1rem, 3.4vw, 1.5rem); font-weight: 700;
  letter-spacing: -0.02em; word-break: break-word;
}

dl.qa { margin: 1rem 0 0; }
dl.qa dt { font-weight: 700; margin: 1.5rem 0 0.25rem; }
dl.qa dd { margin: 0; max-width: var(--measure); color: var(--ink-soft); }
dl.qa dd p { color: var(--ink-soft); margin: 0.5rem 0; }

@media (max-width: 34rem) {
  body { font-size: 16px; }
  .masthead { padding-top: 1.5rem; }
  /* The hard break is a desktop nicety; at phone widths it fights the
     natural wrap and orphans a word. The markup keeps a space before the
     break for exactly this reason: hidden, the two halves have to still read
     as one sentence rather than run together. */
  h1 br { display: none; }
}

/* --- the guided screenshot ---------------------------------------------
   An image map without JavaScript. Six radio inputs hold the state, six labels
   sit over the image as markers, and each label carries its own description.
   Hover reveals it instantly; clicking pins it open, which is what a touch
   screen gets since it has no hover; keyboard focus reveals it too, and the
   radios are a group so arrow keys move between markers.

   The description sits over the picture rather than under it. Below the image
   it was frequently past the bottom of the window, so the text changed where
   nobody could see it and the markers looked inert. */
.tour {
  margin: 2.6rem 0 0;
  position: relative;
  left: 50%;
  transform: translateX(-50%);
  /* Narrower than the 960 it was, because the screenshot inside it turned
     from landscape to portrait: the iPad set is captured portrait now, where
     the whole dashboard fits including the history bands the tour points at.
     At 960 wide a 3:4 image is 1280 tall, which is a scroll and a half of one
     picture. */
  width: min(700px, calc(100vw - 3rem));
}
.tour > input {
  position: absolute; width: 1px; height: 1px;
  opacity: 0; margin: 0; pointer-events: none;
}
.tour > .eyebrow, .tour-hint, .tour-zoom {
  max-width: 46rem; margin-left: auto; margin-right: auto;
  padding-left: 1.5rem; padding-right: 1.5rem;
}
/* The frame does not clip. It used to carry the border, the rounding and
   `overflow: hidden`, which meant a description box was cut off wherever its
   marker sat near an edge -- marker four is at 13% across, and a 270px box
   centred there started 44px outside the picture and lost its first word.
   The rounding moves to the picture, which is the thing that needs rounding,
   and the boxes are free to hang over the edge the way a tooltip should. The
   narrow-screen rules below have always done it this way. */
.tour-frame { position: relative; line-height: 0; }
.tour-frame picture {
  display: block;
  border: 1px solid var(--rule); border-radius: 10px; overflow: hidden;
  background: var(--paper-sunk);
}
.tour-frame img { width: 100%; height: auto; display: block; }

.pin {
  position: absolute; left: var(--x); top: var(--y);
  transform: translate(-50%, -50%);
  width: 28px; height: 28px;
  cursor: pointer;
  z-index: 2;
}
.dot {
  display: grid; place-items: center;
  width: 100%; height: 100%; border-radius: 50%;
  font: 700 12px/1 var(--sans); color: #fff;
  background: rgba(17,18,19,0.78);
  border: 2px solid rgba(255,255,255,0.92);
  box-shadow: 0 1px 7px rgba(0,0,0,0.4);
  transition: transform 120ms ease, background 120ms ease;
}
.pin:hover .dot { background: rgba(17,18,19,0.96); transform: scale(1.14); }

/* The description, floating over the screenshot. */
.tip {
  position: absolute; z-index: 3;
  width: 270px; max-width: 40vw;
  padding: 0.62rem 0.75rem;
  border-radius: 9px;
  background: rgba(14,15,16,0.95);
  color: #f3f2f0;
  font: 400 12.6px/1.45 var(--sans);
  letter-spacing: 0.005em;
  text-align: left;
  box-shadow: 0 6px 22px rgba(0,0,0,0.35);
  opacity: 0; visibility: hidden;
  transition: opacity 110ms ease;
  pointer-events: none;
}
.tip b { color: #fff; }

/* Placed so the box stays on the picture: below by default, above for the
   marker near the bottom, and to the side for the ones near an edge. */
.pin--below .tip { left: 50%; top: calc(100% + 11px); transform: translateX(-50%); }
.pin--above .tip { left: 50%; bottom: calc(100% + 11px); transform: translateX(-50%); }
.pin--right .tip { left: calc(100% + 11px); top: 50%; transform: translateY(-50%); }
.pin--left  .tip { right: calc(100% + 11px); top: 50%; transform: translateY(-50%); }

/* Opening on hover is gone. A description that appears because the pointer
   crossed a marker also disappears because it crossed the next one, so reading
   one meant holding still, and half the visitors -- every touch screen -- never
   had the gesture at all. Clicking is the only way in now, and what a click
   opens stays open until another marker is clicked. The markers still light up
   under the pointer, which is the part of hover that was doing useful work. */

.tour-hint { margin: 0.8rem auto 0; color: var(--ink-faint); }
.tour-zoom { display: none; margin: 0.7rem auto 0; }

/* Screen-reader-only text. */
.vh {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* A phone has no hover, but it does have taps, and hiding the markers there
   turned an image map into a list of six paragraphs -- which read as broken to
   anyone who had seen it work on a desktop.

   So the markers stay. What moves is the description: instead of floating next
   to the marker, where at 327px it would cover most of the picture, it sits in a
   reserved slot under the image. The picture is only about 245px tall there, so
   the marker and its answer are on screen together.

   The trick is inverting what is positioned. On a wide screen the label is
   absolute and the description hangs off it. Here the label goes static and the
   dot takes over the coordinates -- custom properties inherit, so --x and --y
   reach it -- which leaves the description free to resolve against the frame
   instead of the marker. */
@media (max-width: 34rem) {
  .tour-frame {
    overflow: visible; border: 0; background: none;
    /* Margin, not padding. Padding would grow the frame, and the markers are
       positioned as a percentage of it -- marker six at 78% then resolved
       against picture-plus-slot and landed below the picture entirely. Margin
       reserves the same space without changing what the percentages mean. */
    margin-bottom: 8.2rem;
  }
  .tour-frame picture {
    display: block; border: 1px solid var(--rule);
    border-radius: 10px; overflow: hidden;
  }

  .pin { position: static; width: auto; height: auto; }
  .dot {
    position: absolute; left: var(--x); top: var(--y);
    transform: translate(-50%, -50%);
    width: 30px; height: 30px; z-index: 2;
  }
  #tour1:checked ~ .tour-frame label[for="tour1"] .dot,
  #tour2:checked ~ .tour-frame label[for="tour2"] .dot,
  #tour3:checked ~ .tour-frame label[for="tour3"] .dot,
  #tour4:checked ~ .tour-frame label[for="tour4"] .dot,
  #tour5:checked ~ .tour-frame label[for="tour5"] .dot,
  #tour6:checked ~ .tour-frame label[for="tour6"] .dot,
  #tour7:checked ~ .tour-frame label[for="tour7"] .dot,
  #tour8:checked ~ .tour-frame label[for="tour8"] .dot,
  #tour9:checked ~ .tour-frame label[for="tour9"] .dot {
    transform: translate(-50%, -50%) scale(1.18);
  }

  /* One slot at the bottom, whichever marker is chosen. */
  .pin--below .tip, .pin--above .tip, .pin--right .tip, .pin--left .tip {
    left: 0; right: 0; bottom: auto; top: calc(100% + 0.7rem);
    transform: none;
    width: auto; max-width: none;
    font-size: 0.9rem; line-height: 1.5;
    padding: 0.7rem 0.8rem;
  }

  .tour-hint { display: none; }
  .tour-zoom { display: block; }
}

@media (prefers-reduced-motion: reduce) {
  .dot, .tip { transition: none; }
}

/* Clicking opens a description; keyboard focus reveals it too.

   The :checked rules used to carry :not(:hover) on the frame, so a clicked
   description would step aside while the pointer was anywhere on the image and
   come back when it left. That existed only to keep a hover box and a clicked
   box from overlapping, and there are no hover boxes any more. Generated one
   rule per marker because CSS cannot match "the label whose input is checked"
   any other way. */
#tour1:checked ~ .tour-frame label[for="tour1"] .tip,
#tour1:focus-visible ~ .tour-frame label[for="tour1"] .tip,
#tour2:checked ~ .tour-frame label[for="tour2"] .tip,
#tour2:focus-visible ~ .tour-frame label[for="tour2"] .tip,
#tour3:checked ~ .tour-frame label[for="tour3"] .tip,
#tour3:focus-visible ~ .tour-frame label[for="tour3"] .tip,
#tour4:checked ~ .tour-frame label[for="tour4"] .tip,
#tour4:focus-visible ~ .tour-frame label[for="tour4"] .tip,
#tour5:checked ~ .tour-frame label[for="tour5"] .tip,
#tour5:focus-visible ~ .tour-frame label[for="tour5"] .tip,
#tour6:checked ~ .tour-frame label[for="tour6"] .tip,
#tour6:focus-visible ~ .tour-frame label[for="tour6"] .tip,
#tour7:checked ~ .tour-frame label[for="tour7"] .tip,
#tour7:focus-visible ~ .tour-frame label[for="tour7"] .tip,
#tour8:checked ~ .tour-frame label[for="tour8"] .tip,
#tour8:focus-visible ~ .tour-frame label[for="tour8"] .tip,
#tour9:checked ~ .tour-frame label[for="tour9"] .tip,
#tour9:focus-visible ~ .tour-frame label[for="tour9"] .tip { opacity: 1; visibility: visible; }

/* The open marker sits above the others. The markers are siblings, so without
   this the one later in the document paints over an open description whatever
   z-index the box itself carries -- marker four's dot was sitting on top of
   marker three's text. */
#tour1:checked ~ .tour-frame label[for="tour1"],
#tour2:checked ~ .tour-frame label[for="tour2"],
#tour3:checked ~ .tour-frame label[for="tour3"],
#tour4:checked ~ .tour-frame label[for="tour4"],
#tour5:checked ~ .tour-frame label[for="tour5"],
#tour6:checked ~ .tour-frame label[for="tour6"] { z-index: 6; }

#tour1:checked ~ .tour-frame label[for="tour1"] .dot,
#tour2:checked ~ .tour-frame label[for="tour2"] .dot,
#tour3:checked ~ .tour-frame label[for="tour3"] .dot,
#tour4:checked ~ .tour-frame label[for="tour4"] .dot,
#tour5:checked ~ .tour-frame label[for="tour5"] .dot,
#tour6:checked ~ .tour-frame label[for="tour6"] .dot { background: var(--ink); border-color: #fff; transform: scale(1.2); }
#tour1:focus-visible ~ .tour-frame label[for="tour1"] .dot,
#tour2:focus-visible ~ .tour-frame label[for="tour2"] .dot,
#tour3:focus-visible ~ .tour-frame label[for="tour3"] .dot,
#tour4:focus-visible ~ .tour-frame label[for="tour4"] .dot,
#tour5:focus-visible ~ .tour-frame label[for="tour5"] .dot,
#tour6:focus-visible ~ .tour-frame label[for="tour6"] .dot { outline: 3px solid #2f6fed; outline-offset: 3px; }

/* --- the app, and where to get it --------------------------------------- */
.getit {
  display: flex; align-items: center; gap: 1.15rem;
  margin: 2.4rem 0 0; padding: 1.25rem 1.35rem;
  border: 1px solid var(--rule); border-radius: 12px;
  background: var(--paper-sunk);
}
.getit-icon {
  width: 84px; height: 84px; flex: none;
  border-radius: 19px; display: block;
  box-shadow: 0 1px 5px rgba(0,0,0,0.16);
}
.getit-text { min-width: 0; }
.getit h2 { margin: 0 0 0.3rem; font-size: 1.12rem; letter-spacing: -0.01em; }
.getit p { margin: 0; }
.getit .muted { font-size: 0.95rem; }
.getit-status {
  margin-top: 0.5rem; font-size: 0.86rem; font-weight: 600;
  color: var(--ink-faint);
}
/* The badge, once there is somewhere for it to point. */
.getit-badge {
  display: inline-block; margin-top: 0.6rem;
  padding: 0.5rem 0.95rem; border-radius: 9px;
  background: var(--ink); color: var(--paper);
  font-size: 0.92rem; font-weight: 600; text-decoration: none;
}
.getit-badge:hover { text-decoration: none; opacity: 0.88; }

@media (max-width: 34rem) {
  .getit { flex-direction: column; align-items: flex-start; gap: 0.9rem; }
  .getit-icon { width: 68px; height: 68px; border-radius: 15px; }
}
