/*
 * HouseChalk — the state and failure vocabulary.
 *
 * The eleven components of
 * docs/superpowers/specs/2026-08-03-state-and-failure-vocabulary-design.md §8,
 * plus the ladder they hang off. Load AFTER hc.css and role-colors.css.
 *
 * WHY THIS IS A SEPARATE SHEET. hc.css is the component vocabulary — a card, a
 * chip, a row. This is the vocabulary for what happens when a component has
 * nothing to show, cannot be reached, or is telling you something that was true
 * an hour ago. Those are different jobs, and the first sheet is 1242 lines
 * without them. Every value here still comes from hc.css's tokens; nothing
 * below declares a color.
 *
 * THE ORGANIZING AXIS IS VOLUME, NOT CAUSE. This is the whole design and it is
 * why there is no .error class in this file. States are graded by how loudly
 * the product should say them:
 *
 *   rung 0  SILENT    the data renders its own state; nothing is added
 *   rung 1  QUIET     flat, no lift, in the user's own words
 *   rung 2  SPOKEN    interrupts the content, because you cannot proceed
 *                     without knowing
 *   rung 3  BLOCKING  the only thing on the screen
 *
 * Rung 0 HAS NO CLASS AND MUST NOT GET ONE. The undecided kitchen renders gray
 * because --r-wall is gray; an absent section is absent. If you find yourself
 * reaching for a .rung0 you have found a rung-1 state and mis-rated it.
 *
 * TWO RULES OVER THE WHOLE FILE.
 *
 *   1. EVERYTHING ABOVE RUNG 0 SAYS WHAT TO DO NOW. Every rung-2 and rung-3
 *      block below has a slot for an action, and filling it is one of three
 *      ways a block keeps this rule: its own action, the controls directly
 *      beneath it, or a sentence that says what happens next when there is
 *      nothing to press. What the rule forbids is the fourth thing -- a state
 *      that only describes itself, which is unfinished. Q208.
 *   2. CAUSE NEVER SURFACES. No status codes, no "sync failed", no exception
 *      text, no system voice. There is deliberately no way to spell any of
 *      those with the classes in this file.
 *
 * AND ONE ABOUT COLOR, because it is the rule this file is most likely to
 * break. Volume is carried by LAYOUT — flat versus interrupting versus alone on
 * the screen — and NOT by heat. There is no red rung. --terracotta means a
 * genuine clash (two things claimed on one wall) and it is not spent on "no
 * signal", which is not a fault: being offline in a half-built house is the
 * building's doing, and coloring it as an error blames the reader for it.
 * A tint here says what KIND of thing this is, exactly as in hc.css:
 *
 *   --surface-container   neutral: not yet, no signal, nobody has this
 *   amber / clay          a clock is running — and only Dave gets those
 *   indigo / lavender     somebody else acted
 *   emerald               settled, done, nothing outstanding
 */

/* ==========================================================================
   RUNG 1 — QUIET
   Flat, no lift, no hairline, in the reader's own words. This is
   .card-absent's rule from hc.css promoted to the whole vocabulary, which is
   why .card-absent is not redeclared here: it is already the reference
   implementation and this block is its siblings.
   ========================================================================== */

/* ---- 1. The as-of line ----------------------------------------------------
   ONE GRAMMAR, THREE SOURCES. A frozen link, an offline read, and a lapsed
   account are mechanically different and identical to a reader: this is true,
   as of a moment.

   The grammar is a human moment and what it means for you — "As of Tuesday
   evening" — never "Last updated 14:32". The second is the system talking
   about itself, and it is the single most likely regression in this file.

   STATED ONCE, AT THE TOP OF THE THING THAT IS STALE. Never per card. A stale
   screen that says so on every card is a screen of warnings, which is the
   anxiety failure this whole document exists to avoid. It is a block element
   with a bottom margin for exactly that reason: it is a header, not a badge,
   and it cannot be tucked into a row.

   It carries a leading dot rather than an icon. An icon at this size is either
   a clock — which starts a countdown nobody asked for — or a generic info
   glyph, which is the system's voice. A dot is punctuation. */
.asof {
  display: flex; align-items: baseline; gap: 8px;
  font-size: var(--text-xs); line-height: var(--leading-body);
  color: var(--on-surface-dense);
  margin-bottom: 16px;
}
.asof::before {
  content: ''; flex: none;
  width: 4px; height: 4px; border-radius: var(--r-full);
  background: var(--rail-ink);
  transform: translateY(-2px);
}
/* RUNG 2, and the escalation is the whole point. Staleness on a read-only page
   costs nothing; staleness where something can be ACTED ON is the only way
   staleness ever hurts anybody. Looking at an old allowance is free, deciding
   against one is not. So the same sentence gets a ground and a little air the
   moment the surface under it has a button.

   It is --surface-container and not a warm tint, because the reader has done
   nothing wrong and nothing is running out. */
.asof.act {
  background: var(--surface-container); color: var(--on-surface);
  border-radius: var(--r-sm); padding: 12px 12px;
  font-size: var(--text-sm);
}
.asof.act::before { background: var(--on-surface-variant); transform: translateY(-1px); }

/* ---- 3. The inert capability ----------------------------------------------
   A CAPABILITY YOU LACK IS VISIBLE AND INERT, NEVER ABSENT.

   Absence teaches nothing and reads as broken — an admin who cannot find the
   role control concludes the app has lost it. Inert, with the reason available
   when reached for, teaches the permission model once and never needs a
   settings page to explain it.

   RUNG 1 SITTING THERE, RUNG 2 ONLY IF THEY TRY IT. That is the two rules
   below: at rest it is simply quiet, and .why is closed. Reaching for it opens
   the one sentence. In a mockup both states are drawn, so .inert.reached is the
   frame that shows the second.

   NOT opacity on the control. An alpha over a whole control dims its label
   below AA and reads as a rendering fault rather than a decision; the ink moves
   to the muted tone instead and the control keeps its shape. This is hc.css's
   "never dim an on-container tone with opacity" applied to a whole element. */
.inert { color: var(--on-surface-variant); }
.inert .why {
  display: none;
  margin-top: var(--sp-snug); font-size: var(--text-sm);
  line-height: var(--leading-body); color: var(--on-surface-dense);
}
.inert.reached .why { display: block; }

/* THE CONTROL, AND IT IS AN OUTLINE PILL RATHER THAN A FILLED ONE.

   It was `display: block; text-align: center; background: --surface-container`
   — a full-width gray fill with a centered muted label, which is the drawing of
   a DISABLED PRIMARY BUTTON. That is precisely the pattern this component
   exists to replace, so drawn that way it reproduced the thing it was built to
   fix and stopped reading as tappable at all. `Inert.jsx` is unambiguous:
   "AND NOT A FILLED PILL."

   So it takes the outline: a hairline pill, the label on the LEFT where a
   row's label sits, and the ask on the RIGHT in blue — because asking is the
   one thing here you CAN do, and the blue word is what says this is a button
   and names what pressing it will do.

   ASKING IS THE INTERACTION, SO THIS IS A REAL <button>. The affordance sits
   on the unavailable control itself — you ask by activating the thing that is
   unavailable — and it carries aria-expanded plus aria-controls naming the
   .why it reveals. As a <span> only a mouse could ask, which left a keyboard
   or screen-reader user with the inert control and no way to learn who can act
   instead: the "disabled control with no explanation" this component replaces,
   rebuilt inside it. */
.inert .ctl {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  width: 100%; margin-top: 20px; text-align: left;
  border: 0; background: transparent; cursor: pointer;
  box-shadow: inset 0 0 0 1px var(--outline-strong);
  color: var(--on-surface-variant);
  font-family: var(--font-body); font-weight: 600; font-size: var(--text-base);
  padding: 15px 20px; border-radius: var(--r-full);
}
/* The ask. Blue, and one step down: it is a control inside a control, and it
   must not outweigh the capability it is asking about. */
.inert .ctl .ask {
  flex: none; font-size: var(--text-sm); font-weight: 600; color: var(--primary);
}

/* ---- 4. The skeleton ------------------------------------------------------
   THE SHAPE OF WHAT IS COMING, IN ITS QUIET STATE. Matches the real layout —
   if the card that arrives is not the shape of the block that held its place,
   the skeleton was decoration.

   NO SHIMMER, and this is a rule rather than a taste. A shimmer is an animation
   applied to a delay to make it feel shorter; it makes a slow screen look busy
   and a fast one flicker. Under about a second nothing is drawn at all — the
   screen simply arrives — so anything wearing this class is already known to be
   slow, and a slow screen does not need to sparkle.

   NEVER A FULL-PAGE SPINNER ONCE THE SHELL EXISTS. There is no spinner class in
   this file, at any rung. A homeowner who taps into their own house and gets a
   blank screen with a spinner has been told, wordlessly, that their house is
   loading. */
/* A VEIL OF THE PAGE'S OWN INK, not --surface-container, and this is the one
   value on the page that had to be measured rather than picked. A skeleton
   line filled with --surface-container reads on paper (#F2F1EE on white) and
   VANISHES on the shell, where the container is #232E29 and the card it sits
   on is #26302B — three points apart, so the whole loading state renders as an
   empty card. Same failure --rule-ink was invented to fix, same fix: derive
   from --on-surface, which inverts with the theme, so one declaration is a
   gray on paper and a lightening veil on the shell.

   8% and not 9%: --rule-ink is 9% and is a line, this is a block, and a block
   at a line's weight reads as content rather than as a placeholder. */
.skel {
  background: color-mix(in srgb, var(--on-surface) 8%, transparent);
  border-radius: var(--r-sm);
}
.skel.line { height: 12px; }
.skel.line + .skel.line { margin-top: 8px; }
.skel.line.short { width: 46%; }
.skel.line.mid   { width: 72%; }
.skel.block { height: 88px; border-radius: var(--r-base); }
/* A skeleton card is the .card box with nothing in it — same padding, same
   radius, same ring, so what lands does not move. */
.skel-card {
  background: var(--surface-card); border-radius: var(--r-card);
  box-shadow: inset 0 0 0 1px var(--outline);
  padding: 24px 24px;
}

/* ---- 5. The in-flight mark ------------------------------------------------
   OPTIMISTIC WRITES, QUIET. An observation — a photo, a checklist tick, a site
   note — has no counterparty and cannot conflict, so it is shown as done the
   moment it is made and this mark says the copy on the server has not caught
   up. SHOW THE RESULT, NOT THE PROCESS.

   IT IS NOT CALLED PENDING. hc.css already spends `.pending` on the diagonal
   hatch meaning "nobody has chosen this color" — which is absence, not
   waiting. Two states one word apart on the same screen is how a reader learns
   the wrong one.

   A hollow ring, at the rail's weight rather than the rule's, because like an
   empty spine node it is the only mark saying the thing is there at all. It
   does not spin. */
.inflight {
  display: inline-block; vertical-align: baseline;
  width: 8px; height: 8px; border-radius: var(--r-full);
  box-shadow: inset 0 0 0 1.5px var(--rail-ink);
  margin-left: 8px;
}

/* ---- 8. Year-two empty ----------------------------------------------------
   AN EMPTY PUNCH LIST SIX MONTHS AFTER MOVE-IN IS THE PRODUCT WORKING. An
   empty punch list on day one is "we have not started". Same zero, opposite
   meanings, and THEY MUST NOT SHARE A COMPONENT — if they do, the good news
   reads as an unfinished screen for the rest of the product's life, and on a
   surface built for anxious people an unfinished screen is read as something
   wrong with the house.

   Day-one emptiness is a SHAPE: here is what will be here. It is drawn by
   09-first-run's rules and by .skel above, and it is deliberately not in this
   block.

   Year-two emptiness is a STATEMENT: nothing is outstanding. So it takes the
   settled tint, it is a sentence rather than a frame, and it has no call to
   action — there is nothing for the reader to do, which is the news. */
/* IT SITS ON THE OUTLINE RUNG. The settled tint went with the other panel
   fills: --success-surface is a MARK's disc only, a tick and never a panel.
   What it says is said by the tick and the sentence, on a container-shaped
   absence standing in for the list it replaces. */
/* A STATE BLOCK IS PADDED LIKE A CARD, NOT LIKE A CONTROL. --state-padding was
   declared in hc.css and read by NOTHING while all four blocks sat at a
   control's 16px and --r-base. The design system says why in as many words:
   "At a control's 16px and 16px radius a state reads as a notice bar and the
   eye skips it — and a state is the only thing in its slot, so it has to hold
   that slot." The radius is --r-card, whatever --r-card is: the README calls
   it the "20px signature" and stage 2 moved the signature to 24, so the token
   is the claim and the number in that sentence is its old value. */
.empty-y2 {
  border-radius: var(--r-card);
  padding: var(--state-padding); box-shadow: inset 0 0 0 1px var(--outline-strong);
}
/* THE GREEN PANEL IS GONE AND A GREEN MARK REPLACED IT. It was a settled wash
   with the sentence written in the same green, which stated "settled" twice
   and left the words reading as decoration. The claim is carried by one dot —
   small, non-prose notation, which is the right place to spend a color — and
   the sentence takes the page's own ink.

   THE DOT IS --success-deep, NOT --success. The design system draws it in
   --success; that measures 2.6:1 on the page and the non-text bar is 3:1, so
   the deeper tone takes it at 6.3:1. Recorded in DESIGN_SYSTEM_REALIGNMENT.md
   as the one open question this stage had to answer rather than defer.

   AND IT IS NOT A TICK. A tick says somebody completed something; this state
   often means nothing needed doing in the first place. */
.empty-y2 .s {
  display: flex; align-items: center; gap: 10px;
  /* 600 -- see .say .t. Q253. */
  font-size: var(--text-md); font-weight: 600;
  line-height: var(--leading-snug); color: var(--on-surface);
}
.empty-y2 .s::before {
  content: ""; flex: none; width: 8px; height: 8px;
  border-radius: var(--r-full); background: var(--success-deep);
}
/* --sp-snug, NOT the 8px literal that was here. THE GAP IS SET BY WHETHER THE
   THING ABOVE IT IS A SENTENCE OR A LABEL: a state title is a sentence the
   reader can leave with and the line below is a second sentence explaining it,
   which is --sp-snug 12px. 8px is the label gap, drawn under a sentence. The
   package draws --sp-snug at all four states and the app drew 4, 8, 8 and 6 --
   one gap, three values, because nothing named the relationship. Q253. */
.empty-y2 .c {
  display: block; margin-top: var(--sp-snug);
  font-size: var(--text-sm); line-height: var(--leading-body);
  color: var(--on-surface-variant);
}

/* ---- 9. The dated container -----------------------------------------------
   AN EMPTY CONTAINER IS REMOVED. AN EMPTY CONTAINER THAT WILL FILL IS KEPT AND
   DATED. It says WHEN, not WHAT.

   This is the tab that cannot be opened yet — the punch list before handover.
   It is not day one (nothing here has started) and not year two (nothing is
   outstanding): it is a container with a date on it, and the date is the entire
   content. Writing "nothing here yet" in it collapses it into day one and
   throws away the one useful fact.

   THE DATE LINE IS THE ONLY AMBER THING HERE, and it is an eyebrow: a mark
   naming the kind. The amber panel is gone — nothing here is an object, so
   nothing here is a card, and a hairline says "this slot exists and will fill"
   without claiming there is already something in it, which is exactly what a
   not-yet state means. The .d line takes the same metrics as a spine node's
   date for the same reason: a date is a date wherever it is printed. */
.dated {
  border-radius: var(--r-card);
  padding: var(--state-padding); box-shadow: inset 0 0 0 1px var(--outline-strong);
}
.dated .d {
  display: block; font-size: 11px; font-weight: 700; letter-spacing: 1.4px;
  text-transform: uppercase; color: var(--amber-deep); line-height: 1.3;
}
/* THE DATE LINE IS THE ONLY AMBER THING HERE. The sentence takes the page's
   own ink: a color is a mark, and a sentence never takes one. It was
   --amber-deep while this block was an amber panel, where the tint had already
   named the kind and the prose was saying it a second time. */
/* --sp-snug, NOT the 8px literal that was here. THE GAP IS SET BY WHETHER THE
   THING ABOVE IT IS A SENTENCE OR A LABEL: a state title is a sentence the
   reader can leave with and the line below is a second sentence explaining it,
   which is --sp-snug 12px. 8px is the label gap, drawn under a sentence. The
   package draws --sp-snug at all four states and the app drew 4, 8, 8 and 6 --
   one gap, three values, because nothing named the relationship. Q253. */
.dated .s {
  display: block; margin-top: var(--sp-snug);
  font-size: var(--text-base); line-height: var(--leading-body);
  color: var(--on-surface);
}

/* ---- 10. Cold start -------------------------------------------------------
   THE LAUNCH SURFACE, HELD. Rung 0 — /splash is a real route and the shell does
   not exist yet, which is why "the shell is always there" is false of the first
   screen of every session and why this is the one place the no-spinner rule
   cannot simply be inherited.

   NOT A SPINNER ON IT, NOT A PROGRESS BAR UNDER IT, AND NO MESSAGE. The product
   holds its own identity until there is something to replace it with. The
   abnormal-wait case escalates to rung 2 and says what is happening in the
   reader's terms — .cold .late below — and it is the only text this surface
   ever carries.

   It takes the hero gradient rather than the page ground because it is the one
   moment the product introduces itself, and --on-hero rather than --on-primary
   for the reason hc.css gives at its declaration: this surface does not flip
   with the theme, so its ink does not either. */
/* z-index 45 — ABOVE the nav dock (40) and the scrim (30), BELOW the notch
   (50). A blocking surface covers the app's own chrome, which is the point of
   it; it does not cover the device. The first draft used 60 and painted over
   the bezel, which reads as a rendering fault rather than a screen. */
.cold {
  position: absolute; inset: 0; z-index: 45;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 16px;
  /* A launch surface. The gradient is Pigment to pigment-deep in both themes —
     it does not flip, which is why --on-hero is its own token. The 32px grid
     that used to sit over it at white 10% is gone with the rest of the
     pattern; the gradient alone is what makes this a brand surface. */
  background-color: var(--hero-start);
  background-image: linear-gradient(160deg, var(--hero-start), var(--hero-end));
  color: var(--on-hero);
}
.cold .mk {
  width: 64px; height: 64px; border-radius: 18px;
  display: flex; align-items: center; justify-content: center;
  background: rgba(255,255,255,0.14); color: var(--on-hero);
  font-weight: 700; font-size: 22px;
}
.cold .nm { font-family: var(--font-display); font-size: var(--text-lg); }
/* The abnormal wait. Rung 2, and the only sentence this screen ever gets. In
   the reader's terms: what is happening to THEM, never what the system is
   retrying. */
/* NO OPACITY ON THIS, and the first draft had one. hc.css forbids dimming an
   on-container tone with an alpha, and --on-hero is exactly that: it is already
   the light end of a pair chosen against a dark gradient, so an alpha on top is
   a second quiet step nobody chose. Measured, .82 lands this line near 4.2:1
   against the gradient's light end — under the floor, on the one screen a
   worried person reads while nothing else is happening.

   The hierarchy is carried by size and measure instead: 15px against the 22px
   Fraunces above it, capped at 240px so it reads as a note rather than a
   headline. */
.cold .late {
  max-width: 240px; text-align: center;
  font-size: var(--text-sm); line-height: var(--leading-body);
  color: var(--on-hero);
}

/* ==========================================================================
   RUNG 2 — SPOKEN
   It interrupts the content, because the reader cannot decide what to do next
   without it. One block, several kinds.

   IT SITS ABOVE THE CONTENT AND NEVER OVER IT. There is no scrim and no modal
   in this file. A dialog stops the reader to say something they could have read
   while still looking at their house; the .scrim in hc.css belongs to an open
   nav panel, which is a thing the reader opened themselves.

   RULE 1 IS NOT OPTIONAL; THE ACTION SLOT IS ONE WAY TO KEEP IT. This read
   "a .say with no .act is a bug in the page", and 24 of the deck's 56 says
   have none -- each still says what to do now: it heads the controls directly
   beneath it, or its sentence says what happens next when there is nothing to
   press (the offline bar below forbids a retry), or it is news after the fact.
   A .say that only describes itself is still unfinished. .say.gone keeps its
   .act: the way back, and there is always one. Q208.
   ========================================================================== */
.say {
  border-radius: var(--r-card); padding: var(--state-padding);
  background: var(--surface-container); color: var(--on-surface);
  margin-bottom: 16px;
}
/* THE TONE IS A WORD, NOT A COLOR. It was a tinted ground until 2026-09-05,
   which made the loudest thing on the screen also the most decorative and left
   the sentence recolouring itself to match. Every .say sits on the zone ground
   now and the tone SAYS what it is, in an eyebrow, in its own mark ink — "A
   clock is running" is more use to a reader than an amber panel they have to
   decode. */
.say .e {
  display: block; margin-bottom: 12px;
  font-size: var(--text-2xs); font-weight: 600;
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--on-surface-dense);
}
/* --text-md/600, and BOTH halves of that moved. A state title is the lead
   sentence of its block -- which is what --text-md is for -- and this drew
   --text-base, one step below its two siblings and one pixel above the body
   ramp, so the block lost its first line. The weight does not follow the step:
   600 holds from --text-base through --text-md, and regular weight only begins
   at --text-lg where size carries the hierarchy alone. At 18 over a 15px body
   it cannot, and 500 against the body's 400 reads as unemphasised body. Q253. */
.say .t {
  display: block; font-size: var(--text-md); font-weight: 600;
  line-height: var(--leading-snug);
}
/* --sp-snug, NOT the 8px literal that was here. THE GAP IS SET BY WHETHER THE
   THING ABOVE IT IS A SENTENCE OR A LABEL: a state title is a sentence the
   reader can leave with and the line below is a second sentence explaining it,
   which is --sp-snug 12px. 8px is the label gap, drawn under a sentence. The
   package draws --sp-snug at all four states and the app drew 4, 8, 8 and 6 --
   one gap, three values, because nothing named the relationship. Q253. */
.say .p {
  display: block; margin-top: var(--sp-snug);
  font-size: var(--text-sm); line-height: var(--leading-body);
  color: var(--on-surface-dense);
}
/* What to do now. A link's weight, not a button's — the reader is being told
   something, not asked to fill in a form, and a filled button here competes
   with whatever the screen was actually for. */
.say .act {
  margin-top: 12px;
  font-size: var(--text-sm); font-weight: 600; color: var(--accent-ink);
}

/* THE ACTION SLOT'S ROLE, ITS RESET AND ITS 44px BOX ALL LIVE IN hc.css, and
   they have to: `46-questions-to-ask.html` carries a `.resolve .act` and does
   NOT load this sheet. Putting the reset here shipped that one button wearing
   the UA's ButtonFace — #EFEFEF under a dark-theme label, 1.67:1, which is what
   `deck contrast` caught. `.resolve` is an hc.css component; the shared control
   rules follow it. What stays here is what is specific to the state blocks. */
.say .act, .lnk .act, .block .act { margin-top: 0; }

/* ---- 2. The offline bar ---------------------------------------------------
   IT SAYS WHAT STILL WORKS, not what broke.

   "You can look at everything you have already opened; you cannot decide until
   you are back." That sentence is the component. The boundary it draws is spec
   A's assertion boundary: you can write what you assert about your own work,
   you must be online to touch anyone else's record — so photos, ticks and notes
   go in and deciding, signing and approving wait.

   NEVER AN ERROR TONE. Neutral ground, no warm tint, no icon that could be read
   as a warning triangle. Being offline in a half-built house is not a fault and
   treating it as one blames the reader for the building.

   AND NO RETRY BUTTON. An error that can retry itself already has — but not
   while offline, where a retry button is a button that does nothing, and
   offering it is worse than the wait. .say .act on this variant points at what
   is still writable, never at "try again". */
/* .off, .gone: the neutral eyebrow ink of the base rule says these two. */

/* Time is Dave's information and Sam's anxiety, and this is the only
   structural difference between the two registers. "Not sent since 9:14" is
   what a builder coordinating five people in the physical world needs; the same
   line on a homeowner's screen is anxiety with a timestamp on it.

   So the clock prints "A clock is running" and a page that puts .say.clock on
   a homeowner surface has made a register error, not a color error. */
.say.clock .e { color: var(--amber-deep); }

/* Somebody else got there first. Indigo ink, because that is the mark for
   people and position throughout the set — this is not a failure, it is news
   about another person, and the eyebrow now says so rather than asking the
   reader to decode a tint. */
.say.who .e { color: var(--indigo-deep); }

/* ---- 11. Not found --------------------------------------------------------
   A bookmark to a deleted project, a mistyped URL, an object that is not there.
   Rung 2, and it is NOT any of its neighbors: not absence (that is about empty
   containers), not link death (that is about links), and not "something broke",
   because nothing broke.

   TWO SENTENCES THAT MUST NEVER BE CONFUSED, and this class exists mainly to
   keep them apart:

     not found     "That is not here." — it does not exist
     cold cache    "We do not have this yet." — it exists and you are offline

   Same blank screen, opposite facts, and the second one resolves itself the
   moment there is signal. Telling a homeowner with no signal that their house
   does not exist is the worst sentence in this document. */
.say.gone .act { /* the way back, and there is always one */ }

/* ==========================================================================
   RUNG 3 — BLOCKING
   The only thing on the screen. Almost nothing qualifies: proceeding without
   knowing has to be actively harmful, not merely uninformed.

   The whole list, and it is three long: an INVALID TOKEN (the only 401, and the
   only link state that blocks), a CLIENT TOO OLD to read the data, and the
   FIRST OPEN of a lapsed account. Everything else that feels urgent is rung 2
   with an action on it.
   ========================================================================== */
/* Same stacking rule as .cold above: over the app, under the device. */
.block {
  position: absolute; inset: 0; z-index: 45;
  display: flex; flex-direction: column; justify-content: center;
  gap: 16px; padding: 40px 32px;
  background: var(--surface);
}
.block .t {
  font-family: var(--font-display); font-size: var(--text-xl);
  /* The token, not the -0.4px literal it replaced — see .screen-title, Q247. */
  letter-spacing: var(--tracking-title); line-height: var(--leading-tight);
}
.block .p {
  font-size: var(--text-base); line-height: var(--leading-relaxed);
  color: var(--on-surface-variant);
}
/* The lead-in is the 44px box now; see the target rule above. */
.block .act { margin-top: 0; }

/* ==========================================================================
   6. THE DEAD-LINK TEMPLATE — ONE TEMPLATE, SEVEN COPIES
   Spec A's eight link states. Seven of them are not `live`, and they differ
   ONLY in their sentence and their forward pointer. Building seven pages
   guarantees six of them drift, so there is one block and seven modifiers.

     live                  rung 0   it is just the page — NO CLASS
     answered by you       rung 1   .lnk.mine
     answered by someone   rung 2   .lnk.theirs
     superseded            rung 2   .lnk.superseded   do not act on this
     replaced              rung 2   .lnk.replaced     act on the newer one
     withdrawn             rung 2   .lnk.withdrawn
     expired               rung 2   .lnk.expired
     invalid token         rung 3   .lnk.invalid      + .block

   A DEAD LINK IS NEVER RUNG 3 EXCEPT FOR AN INVALID TOKEN. Every other death
   has a what-to-do-now, and a state carrying an action is not blocking.

   SUPERSEDED AND REPLACED ARE OPPOSITE AND MUST NOT BE MERGED. Superseded says
   this page is WRONG — do not act on it, and there may be nothing to go to.
   Replaced says this page is OUT OF DATE and here is the newer one; it ALWAYS
   points forward. A reader who acts on a superseded page has done damage; a
   reader who acts on a replaced page has merely wasted a trip.

   EVERY ONE OF THEM PAINTS THE SENDER FIRST. The recipient is a stranger to us
   and knows the sender, so the attribution is above this block in the markup
   and is never inside it — see .lnk-from. A link that opens to our logo alone
   has failed at the only job its first second has. */
/* IT SITS ON THE OUTLINE RUNG. Six tinted grounds used to carry the state,
   which asked the reader to learn a color code to find out what happened.
   The state prints itself as an eyebrow in its own mark ink; the block itself
   is a hairline, because nothing here is an object to raise. */
.lnk {
  border-radius: var(--r-card); padding: var(--state-padding);
  box-shadow: inset 0 0 0 1px var(--outline-strong); color: var(--on-surface);
}
.lnk .e {
  display: block; margin-bottom: 12px;
  font-size: var(--text-2xs); font-weight: 600;
  letter-spacing: var(--tracking-eyebrow); text-transform: uppercase;
  color: var(--on-surface-dense);
}
/* 600, not 500 -- see .say .t: regular weight begins at --text-lg, and this is
   a step below it. Q253. */
.lnk .t {
  display: block; font-size: var(--text-md); font-weight: 600;
  line-height: var(--leading-snug);
}
/* --on-surface-variant, not --on-surface-dense, and the design system is what
   settles it: components/states/DeadLink.jsx draws its body at
   var(--on-surface-variant). It was the dense tone while this block sat on the
   zone; on the outline rung it is on the page, where variant is the muted tone
   at this step and measures 5.65:1. */
/* --sp-snug, NOT the 8px literal that was here. THE GAP IS SET BY WHETHER THE
   THING ABOVE IT IS A SENTENCE OR A LABEL: a state title is a sentence the
   reader can leave with and the line below is a second sentence explaining it,
   which is --sp-snug 12px. 8px is the label gap, drawn under a sentence. The
   package draws --sp-snug at all four states and the app drew 4, 8, 8 and 6 --
   one gap, three values, because nothing named the relationship. Q253. */
.lnk .p {
  display: block; margin-top: var(--sp-snug);
  font-size: var(--text-sm); line-height: var(--leading-body);
  color: var(--on-surface-variant);
}
/* No `display` and no `margin-top`: both come from the target rule above, and
   declaring them here would beat it -- this sits 136 lines further down. */
.lnk .act {
  font-size: var(--text-sm); font-weight: 600; color: var(--accent-ink);
}
/* Answered by you: the one quiet death. It is a confirmation, so its eyebrow
   takes the settled ink, and its action is what happens NEXT rather than a way
   back. */
.lnk.mine .e { color: var(--success-deep); }
/* Answered by someone else: news about a person, so the indigo mark, and the
   sentence names who. */
.lnk.theirs .e { color: var(--indigo-deep); }
/* Superseded: the only link state that is genuinely a clash — the page in front
   of the reader contradicts the record. This is what --terracotta is for and it
   is the only place in this file it is spent. */
.lnk.superseded .e { color: var(--terracotta-deep); }
/* Replaced: points forward, always, and the forward pointer is the action. Not
   terracotta — nothing is wrong, the reader is simply in the wrong place. */
/* .replaced, .withdrawn: the neutral eyebrow ink of the base rule says both. */
/* Withdrawn and expired share the neutral ground: nobody did anything wrong and
   there is nothing to fix. They differ only in their sentence, which is the
   whole argument for one template. */
.lnk.expired .e { color: var(--amber-deep); }
/* Expired carries a date, so it may carry the amber date line — and only it. */
.lnk.expired .d {
  display: block; margin-top: 12px;
  font-size: 11px; font-weight: 700; letter-spacing: 1.4px; text-transform: uppercase;
  color: var(--amber-deep);
}

/* The sender's attribution, above everything on any account-less page. Builder
   first, product mark second and smaller — they know Dave, they do not know us,
   and a page leading with a brand a stranger has never heard of reads as
   smishing. It is not part of .lnk because it is on the LIVE page too. */
.lnk-from { display: flex; align-items: center; gap: 12px; padding-bottom: 16px; }
.lnk-from .mk {
  width: 32px; height: 32px; border-radius: 10px; flex: none;
  background: var(--primary-surface); color: var(--primary-deep);
  display: flex; align-items: center; justify-content: center;
  font-weight: 700; font-size: var(--text-xs);
}
.lnk-from .nm { font-size: var(--text-sm); font-weight: 600; line-height: 1.3; }
.lnk-from .by {
  display: block; margin-top: 2px;
  font-size: var(--text-3xs); letter-spacing: .1em; text-transform: uppercase;
  color: var(--on-surface-dense); font-weight: 700;
}

/* ==========================================================================
   7. THE LAPSED BANNER
   Rung 3 once, rung 1 after — and that escalation-then-silence is how "no nag"
   survives a state that persists indefinitely. A polite rung-2 banner on every
   screen for the rest of the account's life is a nag by duration.

   .lapsed.first is the first open after lapse and composes with .block above:
   they need to know once, properly. Every open after that is .lapsed alone —
   one line, where it is true, never again.

   READ-ONLY IS PERMANENT AND EVERYTHING ALREADY IN IT STAYS READABLE. The
   sentence is about what still works, exactly as the offline bar's is. It is
   also a source of the as-of line, because nothing new is going in.
   ========================================================================== */
.lapsed {
  display: flex; align-items: baseline; gap: 8px;
  font-size: var(--text-xs); line-height: var(--leading-body);
  color: var(--on-surface-dense);
  margin-bottom: 16px;
}
.lapsed::before {
  content: ''; flex: none; width: 4px; height: 4px; border-radius: var(--r-full);
  background: var(--rail-ink); transform: translateY(-2px);
}

/* ==========================================================================
   THE RUNG BADGE — a drawing aid, and it is NOT product chrome.
   Used only on the pattern sheet to label which rung a frame is showing, in
   the same register as .annot: outside the phone, never inside it. If this
   class appears within a .phone in any page, that page has a bug.
   ========================================================================== */
.rung {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 10px; font-weight: 700; letter-spacing: 1.6px; text-transform: uppercase;
  color: var(--on-surface-variant);
}
.rung::before {
  content: ''; width: 8px; height: 8px; border-radius: var(--r-full);
  background: var(--rail-ink);
}
/* THE INKS, NOT THE BASE ACCENTS. --amber and --terracotta were the deck's own
   mid-steps and the design system declares neither; a dot is one of the five
   places a status color may appear, so what changes here is the token and not
   the treatment. Both go DARKER, which a legend dot on the light desk wants
   anyway: the base accents measured 3.32:1 and 3.83:1. */
.rung.r2::before { background: var(--amber-deep); }
.rung.r3::before { background: var(--terracotta-deep); }
