/* ════════════════════════════════════════════════════════════════════
   channelstricks.com — Editorial Ink components          v2 · 2026-07-28
   System of record: chanstri-sys/brand (components/editorial-ink-components.css)

   The SECOND file. Tokens live in tokens/editorial-ink.css and are pasted
   byte-identical so "diff" proves zero drift; this file is the component
   vocabulary built on top of them. Keeping them apart is what makes that
   diff possible at all — mixing them destroys the check.

   Distilled from the "Pastel Press" reference implementation, translated
   from its React components to hand-authored CSS. Zero build, no JS
   required, no external anything.

   THREE RULES FOR EDITING THIS FILE
   1. Values come from tokens via var(). There is not one raw hex here,
      and adding one is the drift this whole system exists to prevent.
   2. Classes are prefixed "ink-". Never ship an unprefixed ".card",
      ".tile" or ".badge" — home. and apex already define those, and a
      bare name would silently capture their markup.
   3. Per-instance values use "--blk-*" and ALWAYS carry a var() fallback,
      so a block with no hue set still renders. "--blk-*" is deliberately
      distinct from "--ei-*" (raw token values) and from the unprefixed
      semantic tokens: "--blk-face" is a block's face, not a variant of
      the "--ink" text color.
   ════════════════════════════════════════════════════════════════════ */


/* ── the block ────────────────────────────────────────────────────────
   The signature object. Default face is "lit": a dark pane carrying its
   hue in a glowing rim, with no extrusion. "solid" is the opt-in pastel
   face standing off the page on a same-hue cut edge. "flat" is a plain
   card for dense or long-form surfaces where a field of tinted blocks
   would be loud.

   Depth is one length, --depth. The shadow STACKS are composed here, on
   the component, and never at :root — a stack composed at :root would
   resolve --blk-edge / --blk-rim / --blk-glow in :root's scope, where
   they are unset, and bake in the fallback hue for every block. */

.ink-block {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-height: 150px;
  padding: var(--pad-block);
  border-radius: var(--radius-block);
  background: var(--blk-face, var(--card));
  color: var(--ink);
  border: 0;
  text-align: left;
  font: inherit;
  text-decoration: none;
  box-shadow:
    inset 0 1px 0 var(--block-sheen),
    0 var(--depth) 0 var(--blk-edge, var(--edge-default)),
    var(--cast-rest);
  /* a solid block stands ON something; reserve the room its edge needs */
  margin-bottom: calc(var(--depth-hover) + 12px);
}

.ink-block__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}

/* Instrument Sans 700, not the serif — the serif is the page title only
   (decision 2026-07-28). */
.ink-block__title {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--text-title);
  line-height: var(--leading-tight);
  letter-spacing: -0.01em;
}

.ink-block__body { margin: 0; color: var(--muted); }
.ink-block__body strong { color: var(--accent); font-weight: 700; }
.ink-block__foot { margin-top: auto; padding-top: var(--space-2); }

/* Only a block that actually does something gets the affordance. */
.ink-block--tap {
  cursor: pointer;
  transition:
    box-shadow var(--motion-hover) var(--ease-settle),
    transform  var(--motion-hover) var(--ease-settle);
}
.ink-block--tap:hover {
  --depth: var(--depth-hover);
  transform: translateY(-3px);
  box-shadow:
    inset 0 1px 0 var(--block-sheen),
    0 var(--depth) 0 var(--blk-edge, var(--edge-default)),
    var(--cast-lit),
    0 4px 38px 2px var(--blk-glow, var(--glow-default));
}
/* The press is mechanical, not animated — the one fast timing. */
.ink-block--tap:active {
  --depth: var(--depth-press);
  transform: translateY(9px);
  transition-duration: var(--motion-press);
  transition-timing-function: var(--ease-press);
}

/* ── face: lit (the default) ──
   No extrusion, so no reserved margin. The rim does the separating. */
.ink-block--lit {
  background: var(--lit-face);
  color: var(--lit-ink);
  margin-bottom: 0;
  box-shadow:
    inset 0 0 0 1px var(--blk-rim, var(--rim-default)),
    inset 0 0 34px -12px var(--blk-glow, var(--glow-default));
}
.ink-block--lit .ink-block__body { color: var(--muted); }
.ink-block--lit.ink-block--tap:hover {
  transform: translateY(-2px);
  box-shadow:
    inset 0 0 0 1.5px var(--blk-rim, var(--rim-default)),
    inset 0 0 40px -6px var(--blk-glow, var(--glow-default)),
    0 0 34px -2px var(--blk-glow, var(--glow-default));
}
.ink-block--lit.ink-block--tap:active {
  transform: translateY(0);
  box-shadow:
    inset 0 0 0 1px var(--blk-rim, var(--rim-default)),
    inset 0 0 34px -12px var(--blk-glow, var(--glow-default));
}

/* ── face: flat ──
   The reference expressed hover here as "border darkens to ink" with a
   2px lift. That is the v1 rule v2 explicitly retires, so flat now
   brightens its rim like every other face. */
.ink-block--flat {
  box-shadow: none;
  border: var(--hair) solid var(--line);
  background: var(--blk-face, var(--card));
  margin-bottom: 0;
}
.ink-block--flat.ink-block--tap:hover {
  transform: none;
  border-color: var(--blk-rim, var(--rim-default));
  box-shadow: 0 0 20px -8px var(--blk-glow, var(--glow-default));
}

/* Honest degradation: broken says so, and never glows. */
.ink-block--broken {
  border: var(--hair) dashed var(--muted);
  box-shadow: none;
  background: var(--wash);
  margin-bottom: 0;
}

@media (prefers-reduced-motion: reduce) {
  .ink-block--tap { transition: none; }
  .ink-block--tap:hover { transform: none; }
}


/* ── the tint family ──────────────────────────────────────────────────
   One modifier per hue, so a consumer never writes a raw value into
   markup and never needs JS to set a face. Tints are faces and fills
   ONLY — never text, never a second accent, never a surface identity.

   On a SOLID face the plain muted/accent tokens measure 3.9–5.0:1 and
   fail AA, so a solid face also swaps in the on-tint pair. A lit face
   keeps its own dark ground and does not. */
.ink-block--lagoon { --blk-face: var(--tint-lagoon); --blk-edge: var(--edge-lagoon); --blk-glow: var(--glow-lagoon); --blk-rim: var(--rim-lagoon); }
.ink-block--sky    { --blk-face: var(--tint-sky);    --blk-edge: var(--edge-sky);    --blk-glow: var(--glow-sky);    --blk-rim: var(--rim-sky);    }
.ink-block--lilac  { --blk-face: var(--tint-lilac);  --blk-edge: var(--edge-lilac);  --blk-glow: var(--glow-lilac);  --blk-rim: var(--rim-lilac);  }
.ink-block--rose   { --blk-face: var(--tint-rose);   --blk-edge: var(--edge-rose);   --blk-glow: var(--glow-rose);   --blk-rim: var(--rim-rose);   }

.ink-block:not(.ink-block--lit):not(.ink-block--broken).ink-block--lagoon,
.ink-block:not(.ink-block--lit):not(.ink-block--broken).ink-block--sky,
.ink-block:not(.ink-block--lit):not(.ink-block--broken).ink-block--lilac,
.ink-block:not(.ink-block--lit):not(.ink-block--broken).ink-block--rose {
  --muted: var(--muted-on-tint);
  --accent: var(--accent-on-tint);
}


/* ── grid ─────────────────────────────────────────────────────────────
   --blk-cols sets the desired column count; the breakpoints cap it.
   Big mode forces 2-up, and 1-up on a phone — a contract, not a guess.

   NOTE: this file never defines ".big" itself. The token block owns that
   selector and does the stepping; a third definition site is how a ".big"
   collision gets shipped. */
.ink-grid {
  display: grid;
  gap: var(--gap);
  grid-template-columns: repeat(var(--blk-cols, 3), 1fr);
  align-items: start;
}
@media (max-width: 1024px) {
  .ink-grid { grid-template-columns: repeat(min(var(--blk-cols, 3), 2), 1fr); }
}
@media (max-width: 620px) {
  .ink-grid { grid-template-columns: 1fr; }
}
.big .ink-grid, body.big .ink-grid { grid-template-columns: repeat(2, 1fr); }
@media (max-width: 620px) {
  .big .ink-grid, body.big .ink-grid { grid-template-columns: 1fr; }
}

/* One entrance per page load. Never replays. */
.ink-grid--rise > * {
  animation: ink-rise var(--motion-rise) var(--ease-settle) both;
  animation-delay: calc(var(--blk-i, 0) * var(--motion-rise-step));
}
@keyframes ink-rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .ink-grid--rise > * { animation: none; }
}


/* ── badges & stamps ──────────────────────────────────────────────────
   Plain words, never icons. Absence of a badge means "here" — a badge
   earns its place by being the exception.

   The dot is a non-text graphic held to 3:1, not 4.5:1. It does NOT
   clear that floor on a light SOLID tint face (2.85:1 worst, on lilac),
   so don't badge tinted solid blocks in the light twin. */
.ink-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: var(--text-stamp);
  letter-spacing: var(--tracking-label);
  text-transform: lowercase;
  color: var(--muted);
  white-space: nowrap;
}
.ink-badge__dot {
  flex: none;
  width: 0.55rem;
  height: 0.55rem;
  border-radius: 50%;
  background: var(--blk-dot, var(--mode-inline));
}
.ink-badge--here  { --blk-dot: var(--mode-inline); }
.ink-badge--opens { --blk-dot: var(--mode-page);   }
.ink-badge--app   { --blk-dot: var(--mode-link);   }
/* Big mode hides badges — it may add a recognition aid, never subtract
   structure, and the badge is chrome rather than content. */
.big .ink-badge, body.big .ink-badge { display: none; }

/* Freshness. Every data view carries one. */
.ink-stamp {
  font-family: var(--font-mono);
  font-size: var(--text-stamp);
  letter-spacing: var(--tracking-stamp);
  color: var(--muted);
}
.ink-stamp--stale { color: var(--accent); }


/* ── figure ───────────────────────────────────────────────────────────
   Big on-screen numerals. Sans 700 since the serif narrowed; tabular so
   a changing number doesn't jitter its own layout. */
.ink-figure {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: var(--text-figure);
  line-height: 1;
  letter-spacing: -0.03em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.ink-figure__unit {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: 0.34em;
  color: var(--muted);
  margin-left: 0.25em;
  letter-spacing: 0;
}


/* ── meter ────────────────────────────────────────────────────────────
   A graphical object, 3:1 floor. The zone is always named in words too —
   a meter is never the only signal. */
.ink-meter { display: flex; flex-direction: column; gap: 6px; }
.ink-meter__well {
  height: 10px;
  border-radius: var(--radius-pill);
  background: var(--wash);
  border: var(--hair) solid var(--line);
  overflow: hidden;
}
.ink-meter__fill {
  height: 100%;
  border-radius: var(--radius-pill);
  background: var(--blk-meter, var(--ink));
}
.ink-meter__legend {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--text-stamp);
  letter-spacing: var(--tracking-stamp);
  color: var(--muted);
}


/* ── broken / mock ────────────────────────────────────────────────────
   Broken ≠ blank. A failing component says so in plain words and offers
   the fallback that still works. */
.ink-broken { display: flex; flex-direction: column; gap: var(--space-2); }
.ink-broken__say { margin: 0; font-style: italic; color: var(--muted); }
.ink-broken__fallback { align-self: flex-start; font-family: var(--font-body); color: var(--second); }

.ink-mock {
  display: inline-block;
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: var(--text-stamp);
  letter-spacing: var(--tracking-label);
  text-transform: lowercase;
  color: var(--muted);
  border: var(--hair) dashed var(--muted);
  border-radius: var(--radius-sm);
  padding: 2px 8px;
}


/* ── buttons ──────────────────────────────────────────────────────────
   Min target --tap-min everywhere: the Grandma bar is not home.-only.
   The accent variant carries the one sanctioned glow outside a tile rim. */
.ink-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--tap-min);
  padding: 0 20px;
  border-radius: var(--radius-pill);
  font-family: var(--font-body);
  font-size: var(--text-body);
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  border: 1.5px solid var(--ink);
  background: var(--ink);
  color: var(--paper);
  box-shadow: 0 3px 0 var(--edge-default);
  transition:
    box-shadow var(--motion-hover) var(--ease-settle),
    transform  var(--motion-hover) var(--ease-settle),
    background var(--motion-hover) linear;
}
.ink-btn:hover  { transform: translateY(-1px); box-shadow: 0 4px 0 var(--edge-default); }
.ink-btn:active { transform: translateY(2px);  box-shadow: 0 1px 0 var(--edge-default); transition-duration: var(--motion-press); }

.ink-btn--accent {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
  box-shadow: 0 3px 0 var(--edge-default), 0 0 16px -7px var(--accent);
}
.ink-btn--accent:hover { box-shadow: 0 4px 0 var(--edge-default), 0 0 20px -6px var(--accent); }

.ink-btn--quiet { background: transparent; color: var(--ink); border-color: var(--ink); box-shadow: none; }
.ink-btn--quiet:hover  { background: var(--wash); box-shadow: none; transform: none; }
.ink-btn--quiet:active { transform: translateY(1px); }

.ink-btn[disabled],
.ink-btn[aria-disabled="true"] { opacity: .45; cursor: not-allowed; transform: none; box-shadow: none; }

@media (prefers-reduced-motion: reduce) {
  .ink-btn { transition: none; }
  .ink-btn:hover, .ink-btn:active { transform: none; }
}

/* Focus is never removed and never subtle. Both themes, always. */
.ink-btn:focus-visible,
.ink-block--tap:focus-visible,
.ink-subnav a:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}


/* ── dialog (page view) ───────────────────────────────────────────────
   The v1 rule was a 2px ink border. Under the pin that reads as a
   glowing white box, so the dialog takes the lit treatment instead.
   Never stack dialogs. */
.ink-dialog {
  position: relative;
  border: 0;
  border-radius: var(--radius);
  background: var(--lit-face);
  box-shadow:
    inset 0 0 0 1px var(--rim-default),
    inset 0 0 40px -18px var(--glow-default);
  padding: clamp(20px, 3vw, 32px);
  max-width: var(--frame-max);
  margin-inline: auto;
  animation: ink-settle var(--motion-dialog) var(--ease-settle) both;
}
.ink-dialog__kicker {
  margin: 0 0 6px;
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
}
.ink-dialog__h {
  margin: 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: clamp(1.6rem, 3.4vw, 2.2rem);
  line-height: 1.12;
  letter-spacing: -0.02em;
}
.ink-dialog__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-6);
  border-bottom: var(--hair) solid var(--line);
  padding-bottom: var(--space-5);
  margin-bottom: var(--space-6);
}
.ink-dialog__body > p { max-width: var(--measure); }

@keyframes ink-settle {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .ink-dialog { animation: none; }
}


/* ── chrome: masthead, subnav, colophon ───────────────────────────── */
.ink-topline {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-4);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
  padding-block: 10px 12px;
}
.ink-topline > span { white-space: nowrap; }

/* The one place DM Serif still speaks: the page title. */
.ink-masthead { border-block: var(--rule) solid var(--ink); padding: 18px 0 20px; }
.ink-masthead__h {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: var(--text-masthead);
  line-height: 1.05;
  letter-spacing: var(--tracking-masthead);
}
/* The stop. Always vermilion, spent once per composition. */
.ink-masthead__stop { color: var(--accent); }
.ink-masthead__greeting {
  margin: 10px 0 0;
  color: var(--muted);
  font-family: var(--font-body);
  font-size: clamp(1rem, 2.2vw, 1.15rem);
}

.ink-subnav {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
  margin-top: 14px;
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-stamp);
}
.ink-subnav a {
  color: var(--muted);
  text-decoration: none;
  padding-block: 4px;
  border-bottom: 2px solid transparent;
}
.ink-subnav a:hover { color: var(--ink); }
.ink-subnav a[aria-current] { color: var(--ink); border-bottom-color: var(--accent); }

.ink-colophon {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-3);
  margin-top: var(--space-10);
  padding-top: 14px;
  border-top: var(--rule) solid var(--ink);
  font-family: var(--font-mono);
  font-size: var(--text-stamp);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
}


/* ── wordmark ─────────────────────────────────────────────────────────
   ONE master, not a light/dark swap.

   The reference implementation selected its colorway with
   @media (prefers-color-scheme: dark). That is a bug under v2 and it has
   already been found in the wild: "prefers-color-scheme" follows the OS,
   while the page ground is pinned by "color-scheme: dark". A viewer whose
   OS is in light mode got the LIGHT wordmark on the near-black ground —
   light ink on dark paper, measuring 1.15:1 — and the wordmark is usually
   the page's h1 and its only accessible name.

   While the scheme is pinned, the mark is pinned with it. If the pin is
   ever lifted, these must move together. */
.ink-wm { display: inline-block; line-height: 0; }
.ink-wm img { display: block; height: var(--blk-wm-h, 32px); width: auto; }


/* ── inkstream ────────────────────────────────────────────────────────
   The motion engine is JS and lives per-surface; these are only the
   layers it draws into. One river per viewport — a texture, not a
   wallpaper. Always aria-hidden: it carries no information. */
.ink-stream {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  pointer-events: none;
  z-index: 0;
}
/* Still forms — same engine, same one-vermilion rule, no motion. */
.ink-band { display: block; width: 100%; height: var(--blk-band-h, 120px); }
.ink-rule { display: block; width: 100%; height: var(--blk-rule-h, 46px); opacity: .9; }
