/* PropFlow Docs — tokens mirrored from propflowai/src/app/globals.css.
   Light default → html.dark override, same as the app. */
:root {
  --color-bg: #FFFFFF;
  --color-bg-subtle: #F4F4F6;
  --color-bg-muted: #EBEBEF;
  --color-bg-card: #FFFFFF;
  --color-bg-hover: #EBEBEF;
  --color-bg-inset: #FAFAFC;
  --color-bg-app: #fafafa;

  --color-text: #0F2060;
  --color-text-body: #1A2D6E;
  --color-text-mid: #6680B0;
  --color-text-muted: #4F6B9E;
  /* ⚠️ Defined because the COPIED app CSS in app-verbatim.css references them
     (ADR-0131). An undefined custom property makes the whole declaration
     invalid and the value falls back to `inherit` — which is how the paginator's
     current page ended up with dark text on its indigo fill, and the ellipsis
     rendered at full text colour instead of faint. Values mirror the app's
     globals.css exactly; anything synced in future that reaches for a
     --color-* token needs the same treatment here. */
  --color-text-faint: #4F6B9E;
  --color-on-primary: #FFFFFF;
  --color-hover-overlay: rgba(0, 0, 0, 0.04);

  --color-border: #E8E8EB;
  --color-border-faint: rgba(0, 0, 0, 0.06);

  --color-primary: #4F46E5;
  --color-accent: #45C1FF;
  --color-link: var(--color-text-body);
  --color-link-hover: var(--color-text);

  --color-success: #059669;
  --color-warning: #F59E0B;
  --color-danger: #DC2626;

  --color-badge-green-bg: #F0FDF4;  --color-badge-green-text: #166534;
  --color-badge-yellow-bg: #FEF3C7; --color-badge-yellow-text: #92400E;
  --color-badge-blue-bg: #DBEAFE;   --color-badge-blue-text: #1E40AF;
  --color-badge-red-bg: #FEF2F2;    --color-badge-red-text: #991B1B;
  --color-badge-purple-bg: #FAF5FF; --color-badge-purple-text: #6B21A8;

  --shadow-card-soft: 0 1px 2px rgba(15, 32, 96, 0.04), 0 4px 12px rgba(15, 32, 96, 0.05);
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius-pill: 999px;
  --motion-ease-glide: cubic-bezier(0.32, 0.72, 0, 1);
}

html.dark {
  --color-bg: #0A0A0F;
  --color-bg-subtle: #0F0F17;
  --color-bg-muted: #1C1C28;
  --color-bg-card: #14141D;
  --color-bg-hover: #1C1C28;
  --color-bg-inset: #0C0C13;
  --color-bg-app: #121212;

  --color-text: #F4F4F6;
  --color-text-body: #C9C9D4;
  --color-text-mid: #888897;
  --color-text-muted: #888897;
  --color-text-faint: #888897;
  --color-on-primary: #FFFFFF;
  --color-hover-overlay: rgba(255, 255, 255, 0.06);

  --color-border: #232331;
  --color-border-faint: rgba(255, 255, 255, 0.06);

  --color-success: #34D399;
  --color-warning: #FBBF24;
  --color-danger: #F87171;

  --color-badge-green-bg: #052e16;  --color-badge-green-text: #86efac;
  --color-badge-yellow-bg: #422006; --color-badge-yellow-text: #fde68a;
  --color-badge-blue-bg: #172554;   --color-badge-blue-text: #93c5fd;
  --color-badge-red-bg: #450a0a;    --color-badge-red-text: #fca5a5;
  --color-badge-purple-bg: #3B0764; --color-badge-purple-text: #D8B4FE;

  --shadow-card-soft: 0 1px 2px rgba(0, 0, 0, 0.4), 0 4px 12px rgba(0, 0, 0, 0.3);
}

* { box-sizing: border-box; }
html { color-scheme: light; }
html.dark { color-scheme: dark; }
body {
  margin: 0;
  background: var(--color-bg-app);
  color: var(--color-text);
  /* ⚠️ THE FONT WAS DOWNLOADED AND NEVER USED. build.mjs copies
     SuisseIntl-Regular/Medium.woff2 out of the app and its comment says the
     docs site "needs its own copy or every page silently renders in a fallback
     and looking like PropFlow is impossible" — and then nothing declared an
     @font-face or named the family, so that is exactly what happened. Every
     character on every page was the system stack while the app was Suisse.
     That, not the table, is why the whole site "felt different".

     Family + tracking copied from the app's own body rule (globals.css:1163). */
  font-family: 'Suisse Intl', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  font-size: 16px;
  line-height: 1.65;
  letter-spacing: -0.02em;
  -webkit-font-smoothing: antialiased;
}

/* ⚠️ TWO WEIGHTS ONLY, 400 and 500 — build.mjs explains why: globals.css maps
   semibold/bold/extrabold/black ALL to 500, so PropFlow's type never goes
   heavier than medium. Declaring a 600+ face we do not have would get a
   synthesised fake-bold that reads instantly wrong beside the real app.
   `font-display: swap` so a slow font never blanks the page. */
@font-face {
  font-family: 'Suisse Intl';
  src: url('/assets/fonts/SuisseIntl-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Suisse Intl';
  src: url('/assets/fonts/SuisseIntl-Medium.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}

/* ── header ─────────────────────────────────────────────────────── */
.site-header {
  position: sticky; top: 0; z-index: 100;
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  height: 56px;
  padding: 0 1.25rem;
  background: color-mix(in srgb, var(--color-bg-app) 85%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--color-border);
}
.brand { display: inline-flex; align-items: center; gap: 9px; text-decoration: none; min-width: 0; }
/* Specificity matters: `.brand img` would otherwise beat a bare `.logo-dark`
   and paint both wordmarks on top of each other. */
.brand img { height: 22px; width: auto; flex-shrink: 0; display: block; }
.brand img.logo-dark { display: none; }
html.dark .brand img.logo-light { display: none; }
html.dark .brand img.logo-dark { display: block; }
.brand-sep { width: 1px; height: 18px; background: var(--color-border); flex-shrink: 0; }
.brand-docs {
  font-size: 15px; font-weight: 500; letter-spacing: -0.01em;
  color: var(--color-text-mid);
}
.header-right { display: flex; align-items: center; gap: 0.6rem; min-width: 0; }
.src-link {
  font-size: 12.5px; color: var(--color-text-mid); text-decoration: none; white-space: nowrap;
}
.src-link:hover { color: var(--color-text); text-decoration: underline; }

/* theme cycle button — mirrors ThemeCycleButton default variant */
.theme-btn {
  width: 36px; height: 36px; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  border: none; background: transparent; border-radius: var(--radius-pill);
  color: var(--color-text-mid); cursor: pointer; font: inherit;
  transition: background 120ms ease, color 120ms ease;
}
.theme-btn:hover { background: var(--color-bg-muted); color: var(--color-text); }
.theme-btn svg { display: block; }

/* ── layout ─────────────────────────────────────────────────────── */
.layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 232px;
  gap: 2.5rem;
  max-width: 1080px; margin: 0 auto;
  padding: 2rem 1.25rem 5rem;
}
.content { min-width: 0; max-width: 76ch; }
@media (max-width: 900px) {
  .layout { grid-template-columns: minmax(0, 1fr); }
  .toc { display: none; }
}

.toc {
  position: sticky; top: calc(56px + 1.5rem); align-self: start;
  max-height: calc(100vh - 8rem); overflow-y: auto;
  font-size: 12.5px;
  border-left: 1px solid var(--color-border); padding-left: 1rem;
}
.toc-title {
  font-weight: 600; text-transform: uppercase; font-size: 10.5px;
  letter-spacing: 0.06em; color: var(--color-text-mid); margin-bottom: 0.5rem;
}
.toc a {
  display: block; color: var(--color-text-mid); text-decoration: none;
  padding: 0.18rem 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.toc a:hover { color: var(--color-text); }
.toc-3 { padding-left: 0.9rem; }

/* ── segmented control ────────────────────────────────────────────
   Lives in src/components/segmented.css — ONE definition, appended to this file at
   build time and ALSO shipped standalone for artifacts, which do not go
   through page() and so cannot reach this stylesheet. Do not re-add it
   here; that fork is what made every podcast page hand-roll its own. */

/* ── typography ─────────────────────────────────────────────────── */
h1 { font-size: 28px; line-height: 1.25; letter-spacing: -0.02em; margin: 0.25rem 0 0.6rem; }
h2 { font-size: 20px; letter-spacing: -0.01em; margin: 2.2rem 0 0.7rem; }
h3 { font-size: 16.5px; margin: 1.6rem 0 0.5rem; }
h2, h3 { scroll-margin-top: 5rem; }
p, li { color: var(--color-text-body); }
a { color: var(--color-link); text-decoration: underline; text-decoration-color: var(--color-border); text-underline-offset: 3px; }
a:hover { color: var(--color-link-hover); text-decoration-color: currentColor; }
hr { border: 0; border-top: 1px solid var(--color-border); margin: 2rem 0; }
blockquote {
  margin: 1rem 0; padding: 0.1rem 1rem;
  border-left: 3px solid var(--color-border); color: var(--color-text-muted);
}

code {
  font: 0.85em/1.5 ui-monospace, 'SF Mono', Menlo, monospace;
  background: var(--color-bg-muted); padding: 0.12em 0.35em; border-radius: 4px;
}
pre {
  background: var(--color-bg-subtle); border: 1px solid var(--color-border);
  border-radius: var(--radius-md); padding: 0.9rem 1.1rem; overflow-x: auto;
}
pre code { background: none; padding: 0; }
pre.mermaid { background: var(--color-bg-subtle); text-align: center; }

table {
  border-collapse: collapse; font-size: 13.5px;
  display: block; overflow-x: auto; max-width: 100%;
}
th, td { border: 1px solid var(--color-border); padding: 0.4rem 0.7rem; text-align: left; vertical-align: top; }
th { background: var(--color-bg-subtle); font-weight: 600; color: var(--color-text); }

.badge {
  display: inline-block; font-size: 11.5px; font-weight: 500;
  padding: 0.14rem 0.55rem; border-radius: var(--radius-pill);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 16rem;
}
.badge.accepted   { background: var(--color-badge-green-bg);  color: var(--color-badge-green-text); }
.badge.proposed   { background: var(--color-badge-yellow-bg); color: var(--color-badge-yellow-text); }
.badge.superseded { background: var(--color-bg-muted);        color: var(--color-text-mid); }
.badge.rejected   { background: var(--color-badge-red-bg);    color: var(--color-badge-red-text); }
.badge.other      { background: var(--color-bg-muted);        color: var(--color-text-mid); }
.badge.artifact   { background: var(--color-badge-purple-bg); color: var(--color-badge-purple-text); }

/* ── index ──────────────────────────────────────────────────────── */
.subtitle { color: var(--color-text-mid); margin: 0 0 1.25rem; font-size: 14.5px; }
/* The Apps section states its own admission test (ADR-0124), so the rule for
   what belongs here is on the page rather than only in the ADR nobody opens
   before publishing. All colors via the site's existing vars, so it follows
   the theme toggle like everything else. */
.section-note {
  color: var(--color-text-mid); font-size: 13.5px; line-height: 1.55;
  margin: 0 0 1.1rem; padding: 0.7rem 0.9rem;
  border-left: 3px solid var(--color-border);
  background: var(--color-bg-subtle, transparent);
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
}
.section-note[hidden] { display: none; }
.section-note b { color: var(--color-text); font-weight: 600; }
.index-controls { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; margin-bottom: 1.1rem; }

/* ── tag chips ──────────────────────────────────────────────────────
   Categories you can click, because search needs the exact wording and
   several artifacts are named after a moment rather than a subject.
   Every colour is an existing --color-* token, so dark mode is already
   handled by the html.dark block above — no second palette to keep in sync.
   The closed vocabulary lives in TAGS.md; build.mjs fails on drift. */
.tag-chips { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0 0 1.1rem; }
/* `display:flex` beats the UA stylesheet's [hidden]{display:none}, so the
   script's `chips.hidden = view!=='artifact'` was setting an attribute that did
   nothing and the tag chips showed on every tab — including ADRs, which the
   chips cannot filter. Surfaced by adding the Apps tab; the bug predates it. */
.tag-chips[hidden] { display: none; }
.chip {
  display: inline-flex; align-items: baseline; gap: 0.35rem;
  font: inherit; font-size: 12.5px; font-weight: 500;
  padding: 0.22rem 0.7rem;
  border: 1px solid var(--color-border); border-radius: var(--radius-pill);
  /* --color-text-body, not --color-text-mid: mid lands at 3.98:1 on the light
     card, and a chip is the control you read and click, not secondary text.
     The count beside it carries the muted weight instead. */
  background: var(--color-bg-card); color: var(--color-text-body);
  cursor: pointer; white-space: nowrap;
  transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
}
.chip:hover { background: var(--color-bg-hover); color: var(--color-text); }
.chip[aria-pressed="true"] {
  background: var(--color-text); color: var(--color-bg-card);
  border-color: var(--color-text);
}
.chip:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 1px; }
.chip-n { font-variant-numeric: tabular-nums; opacity: 0.62; }

/* Per-row tags. Hidden below 640px alongside .row-date — the title is what
   matters on a phone, and the chips above are still how you filter. */
.row-tags { display: flex; gap: 0.3rem; flex-shrink: 0; }
.tag {
  font-size: 11px; font-weight: 500; letter-spacing: 0.01em;
  padding: 0.1rem 0.45rem; border-radius: var(--radius-pill);
  background: var(--color-bg-muted); color: var(--color-text-mid);
  white-space: nowrap;
}
@media (max-width: 760px) { .row-tags { display: none; } }

/* The index list became a table (assets/data-table.css) — one table whose
   tbody the tabs swap, mirroring the renewals board. The flex .doc-list
   row and its .row-title / .row-num / .row-by / .row-date cells are gone
   with it; .row-tags and .tag survive because the tag chips still render
   inside the table cell. */

.empty { color: var(--color-text-mid); font-size: 14px; padding: 1.5rem 0.35rem; }
@media (max-width: 640px) { .row-date { display: none; } }

/* Artifact chrome (back-link + theme button) is injected inline by
   build.mjs — artifacts no longer load this stylesheet, so its body/h1/table
   rules can't clobber theirs. */

/* /systems — the catalog page. Facts tables are narrow key/value grids. */
.sys { margin: 2.5rem 0; padding-top: 1rem; border-top: 1px solid var(--color-border); }
.sys h2 a { color: inherit; text-decoration: none; }
.sys h2 a:hover { text-decoration: underline; }
.sys-facts { border-collapse: collapse; margin: 0.75rem 0 1rem; font-size: 14px; }
.sys-facts th { text-align: left; color: var(--color-text-mid); font-weight: 500; padding: 0.35rem 1rem 0.35rem 0; white-space: nowrap; vertical-align: top; }
.sys-facts td { padding: 0.35rem 0; vertical-align: top; }
.sys h4 { margin: 1.25rem 0 0.35rem; font-size: 13px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--color-text-mid); }

/* ── index: the line under the tabs, and the count under the table ────────
   The note names what the current view IS; the count says how much of it you
   are looking at. Both are set by the index script from ONE table of views,
   so a new tab cannot arrive without its sentence. */
.view-note {
  margin: 0.9rem 0 0.2rem; font-size: 13.5px; line-height: 1.6;
  color: var(--color-text-mid); max-width: 68ch;
}
.view-note code {
  font-size: 12.5px; padding: 0.05rem 0.3rem;
  background: var(--color-bg-muted); border-radius: 4px;
}
.table-count {
  margin: 0.85rem 0 0; font-size: 12.5px; color: var(--color-text-mid);
  font-variant-numeric: tabular-nums;
}

/* ── index: the Details disclosure ────────────────────────────────────────
   Collapsed by default. A reader who knows the place should not read the
   explanation on every visit; a reader who does not should be able to find it
   without asking. The template link lives in here rather than as a row in the
   table — a template is not a document you browse, it is the thing you start
   from, and mixing the two puts a fake page in every list of real work. */
.view-details { margin: 0.75rem 0 0; }
.view-details > summary {
  list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: 0.35rem;
  font-size: 12px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--color-text-mid); padding: 0.25rem 0; user-select: none;
}
.view-details > summary::-webkit-details-marker { display: none; }
.view-details > summary::after { content: '▸'; font-size: 10px; opacity: 0.7; }
.view-details[open] > summary::after { content: '▾'; }
.view-details > summary:hover { color: var(--color-text); }
.view-details-body {
  margin: 0.4rem 0 0; padding: 0.85rem 1rem;
  border: 1px solid var(--color-border); border-radius: var(--radius-md, 8px);
  background: var(--color-bg-card); max-width: 72ch;
}
.view-details-body p { margin: 0; font-size: 13.5px; line-height: 1.65; color: var(--color-text-mid); }
.view-details-body code {
  font-size: 12.5px; padding: 0.05rem 0.3rem;
  background: var(--color-bg-muted); border-radius: 4px;
}
.view-template { margin-top: 0.7rem !important; padding-top: 0.7rem; border-top: 1px solid var(--color-border); }

/* ── index: "start a new one" ─────────────────────────────────────────────
   The template link, in the open rather than folded into Details. Templates
   are deliberately NOT rows in the table — a list of real work should not
   carry a mock page — but a template nobody can find is a template nobody
   uses, which is exactly what happened when this lived behind a disclosure. */
.view-start { margin: 0.55rem 0 0; display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; }
.start-link {
  display: inline-flex; align-items: center; gap: 0.35rem;
  font-size: 13px; font-weight: 550; text-decoration: none;
  color: var(--color-text); padding: 0.28rem 0.6rem;
  border: 1px solid var(--color-border); border-radius: var(--radius-pill, 999px);
  background: var(--color-bg-card);
}
.start-link:hover { border-color: var(--color-primary); color: var(--color-primary); }
.start-plus { font-weight: 700; opacity: 0.75; }
.start-hint { font-size: 12.5px; color: var(--color-text-mid); }
.start-hint code { font-size: 12px; }

/* ── wide layout — pages with no table of contents ────────────────────────
   .content is capped at 76ch because that is a readable measure for ADR
   prose. A TABLE is not prose: at 76ch the index had five columns fighting
   over about half the window while a 232px sidebar column sat empty beside
   it, because the reserved TOC track is emitted whether or not there is a
   TOC to put in it.

   So a page with no TOC drops the sidebar track and the prose measure, and
   uses the width it actually has. Prose pages are untouched — the cap is the
   right call there, and this class never reaches them. */
.layout--wide {
  grid-template-columns: minmax(0, 1fr);
  /* No ceiling. 1600px was picked to stop rows stretching on a big monitor,
     but on a 2560 display it left a third of the window empty beside a table
     that is meant to be scanned — and the table is a table, not prose: a wide
     row is a row you can read across, not a line you lose your place in.
     The horizontal padding below is what keeps it off the glass. */
  max-width: none;
  padding-left: max(1.25rem, calc((100vw - 2200px) / 2));
  padding-right: max(1.25rem, calc((100vw - 2200px) / 2));
}
.layout--wide .content { max-width: none; }


/* ── tag colours — a fixed dictionary, not a hash ─────────────────────────
   Every tag in the closed vocabulary (TAGS.md) gets ONE colour, everywhere it
   appears: the chip row, the table cell, any future surface. `money` is the
   same green on the Artifacts tab as in a row as in a filter chip.

   Why a dictionary and not a hash of the tag name: a hash gives a stable
   colour too, but nobody can predict or adjust it, two tags collide on a hue
   with no recourse, and adding an 18th tag silently reshuffles nothing —
   which sounds fine until you want `collections` and `money` to read as
   related, or `incidents` to read as alarming. Those are judgements, and a
   hash cannot hold one.

   Grouped by what the tag is ABOUT, so related work reads as related:
     leasing lifecycle  indigo    renewals leasing turnovers collections
     the work itself    teal      maintenance conversations voice
     money             green      money gtm
     the system        slate/blue infra architecture design
     how we work       amber      agent-ops evals decisions
     alarm             red        incidents
     personal          violet     personal

   Tuned for BOTH modes: each pair is a soft tint + a readable ink, and the
   dark block swaps to a translucent tint over the dark ground rather than a
   second set of hexes, so a tag keeps its hue identity in either theme. */
:root {
  --tag-indigo-bg: #EEF0FE;  --tag-indigo-fg: #4338CA;
  --tag-teal-bg:   #E6F5F3;  --tag-teal-fg:   #0F766E;
  --tag-green-bg:  #E7F6EE;  --tag-green-fg:  #15803D;
  --tag-blue-bg:   #E8F1FC;  --tag-blue-fg:   #1D4ED8;
  --tag-amber-bg:  #FDF3E3;  --tag-amber-fg:  #B45309;
  --tag-red-bg:    #FDECEC;  --tag-red-fg:    #B91C1C;
  --tag-violet-bg: #F3EDFE;  --tag-violet-fg: #6D28D9;
}
html.dark, :root[data-theme="dark"] {
  --tag-indigo-bg: rgba(99,102,241,.16);  --tag-indigo-fg: #A5B4FC;
  --tag-teal-bg:   rgba(20,184,166,.16);  --tag-teal-fg:   #5EEAD4;
  --tag-green-bg:  rgba(34,197,94,.16);   --tag-green-fg:  #86EFAC;
  --tag-blue-bg:   rgba(59,130,246,.16);  --tag-blue-fg:   #93C5FD;
  --tag-amber-bg:  rgba(245,158,11,.16);  --tag-amber-fg:  #FCD34D;
  --tag-red-bg:    rgba(239,68,68,.16);   --tag-red-fg:    #FCA5A5;
  --tag-violet-bg: rgba(139,92,246,.16);  --tag-violet-fg: #C4B5FD;
}

/* Applied to BOTH the table cell (.tag) and the filter chip (.chip), so one
   tag cannot read as two different things on one page. */
.tag[data-tag="renewals"],    .chip[data-tag="renewals"],
.tag[data-tag="leasing"],     .chip[data-tag="leasing"],
.tag[data-tag="turnovers"],   .chip[data-tag="turnovers"],
.tag[data-tag="collections"], .chip[data-tag="collections"]
  { background: var(--tag-indigo-bg); color: var(--tag-indigo-fg); }

.tag[data-tag="maintenance"],   .chip[data-tag="maintenance"],
.tag[data-tag="conversations"], .chip[data-tag="conversations"],
.tag[data-tag="voice"],         .chip[data-tag="voice"]
  { background: var(--tag-teal-bg); color: var(--tag-teal-fg); }

.tag[data-tag="money"], .chip[data-tag="money"],
.tag[data-tag="gtm"],   .chip[data-tag="gtm"]
  { background: var(--tag-green-bg); color: var(--tag-green-fg); }

.tag[data-tag="infra"],        .chip[data-tag="infra"],
.tag[data-tag="architecture"], .chip[data-tag="architecture"],
.tag[data-tag="design"],       .chip[data-tag="design"]
  { background: var(--tag-blue-bg); color: var(--tag-blue-fg); }

.tag[data-tag="agent-ops"], .chip[data-tag="agent-ops"],
.tag[data-tag="evals"],     .chip[data-tag="evals"],
.tag[data-tag="decisions"], .chip[data-tag="decisions"]
  { background: var(--tag-amber-bg); color: var(--tag-amber-fg); }

.tag[data-tag="incidents"], .chip[data-tag="incidents"]
  { background: var(--tag-red-bg); color: var(--tag-red-fg); }

.tag[data-tag="personal"], .chip[data-tag="personal"]
  { background: var(--tag-violet-bg); color: var(--tag-violet-fg); }

/* A chip is a control, so its border follows its ink rather than staying the
   neutral hairline — otherwise a coloured chip reads as a coloured label with
   a grey box around it. */
.chip[data-tag]:not([data-tag=""]) { border-color: currentColor; }
.chip[data-tag]:not([data-tag=""])[aria-pressed="true"] {
  outline: 2px solid currentColor; outline-offset: -1px;
}

/* ── Index view ─────────────────────────────────────────────────────────────
   A tree, not a table. It uses the same tokens as everything else so it reads
   as part of the site rather than a bolted-on sitemap: section heads in the
   page's own heading weight, groups as disclosures, leaves as plain links.

   Groups are <details> so a section with 200 artifacts under 17 tags opens as
   17 lines rather than a wall — the whole point of the view is that you can see
   the SHAPE before you commit to reading anything. */
.idx { margin-top: 8px; }
.idx-section { margin: 0 0 28px; }
.idx-section[hidden] { display: none; }
.idx-h {
  font-size: 15px; font-weight: 600; letter-spacing: -0.01em;
  color: var(--color-text); margin: 0 0 10px;
  padding-bottom: 8px; border-bottom: 1px solid var(--color-border);
  display: flex; align-items: baseline; gap: 8px;
}
.idx-count {
  font-size: 12px; font-weight: 500; font-variant-numeric: tabular-nums;
  color: var(--color-text-mid);
}
.idx-count--big { color: var(--color-text-muted); }
.idx-list { list-style: none; margin: 0; padding: 0; }
.idx-list--top { display: grid; gap: 2px; }
.idx-group[hidden] { display: none; }
.idx-group > details > summary {
  cursor: pointer; padding: 5px 8px; border-radius: 6px;
  font-size: 13px; color: var(--color-text-body);
  display: flex; align-items: baseline; gap: 8px;
  list-style: none;
}
.idx-group > details > summary::-webkit-details-marker { display: none; }
/* The caret is drawn rather than inherited so it rotates with the disclosure
   and matches the table's sort caret in weight. */
.idx-group > details > summary::before {
  content: ''; width: 0; height: 0; flex: none;
  border-left: 4px solid currentColor;
  border-top: 3.5px solid transparent; border-bottom: 3.5px solid transparent;
  opacity: 0.45; transition: transform 0.12s ease;
  transform: translateY(-1px);
}
.idx-group > details[open] > summary::before { transform: translateY(-1px) rotate(90deg); }
.idx-group > details > summary:hover { background: var(--color-bg-hover); }
.idx-group > details > ul { padding: 2px 0 8px 20px; display: grid; gap: 1px; }
.idx-leaf { display: flex; align-items: baseline; gap: 10px; padding: 3px 8px; border-radius: 6px; }
.idx-leaf:hover { background: var(--color-bg-hover); }
.idx-leaf > a {
  font-size: 13px; color: var(--color-link); text-decoration: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.idx-leaf > a:hover { color: var(--color-link-hover); text-decoration: underline; }
.idx-meta {
  margin-left: auto; flex: none;
  font-size: 11px; font-variant-numeric: tabular-nums; color: var(--color-text-mid);
}
@media (max-width: 620px) {
  .idx-leaf { flex-wrap: wrap; gap: 2px 10px; }
  .idx-leaf > a { white-space: normal; }
  .idx-meta { margin-left: 0; }
}

/* ── Segmented control — ONE definition, three consumers ─────────────────────
   ⚠️ NOT THE SOURCE OF TRUTH. See the header of src/components/segmented.js.
   Source: propflowai/src/components/ui/TabSwitcher.tsx

   Consumers: the docs index (via style.css), ADR/slide pages (same), and
   artifacts (which do NOT go through page(), so they get this file directly).
   That third consumer is why the block moved out of style.css: it used to live
   there alone, artifacts could not reach it, and every artifact that wanted
   tabs hand-rolled its own .pod-tabs — which is the duplication this removes.

   TWO TOKEN VOCABULARIES. The site chrome speaks --color-*; artifacts speak
   the short palette (--panel --text --muted --border --accent). Every colour
   below is a fallback chain so the same file is correct in both, and neither
   consumer has to know the other exists.

   The shadow recipe and the cubic-bezier(.32,.72,0,1) glide are copied
   verbatim from the app — they are what make the thumb read as a card lifting
   off the track rather than a colour change. */

.segmented {
  position: relative; display: inline-flex; align-items: center; width: fit-content;
  gap: 3px; padding: 3px; max-width: 100%;
  background: var(--color-bg-muted, rgba(127, 133, 153, .14));
  border-radius: var(--radius-pill, 999px);
  overflow-x: auto; scrollbar-width: none;
}
.segmented::-webkit-scrollbar { display: none; }

.segmented__thumb {
  position: absolute; border-radius: var(--radius-pill, 999px);
  background: var(--color-bg-card, var(--panel));
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--color-border, var(--border)) 70%, transparent),
    0 1px 3px rgba(0, 0, 0, 0.08),
    0 1px 2px rgba(0, 0, 0, 0.05);
  transition: left 300ms var(--motion-ease-glide, cubic-bezier(.32, .72, 0, 1)),
              top 300ms var(--motion-ease-glide, cubic-bezier(.32, .72, 0, 1)),
              width 300ms var(--motion-ease-glide, cubic-bezier(.32, .72, 0, 1)),
              height 300ms var(--motion-ease-glide, cubic-bezier(.32, .72, 0, 1));
  pointer-events: none;
}

.segmented button {
  position: relative; z-index: 1; border: none; background: transparent; cursor: pointer;
  border-radius: var(--radius-pill, 999px);
  padding: 6px 14px;
  /* Constant weight on purpose: an active-state weight flip changes the label
     width and shifts every sibling while the thumb is mid-slide. */
  font: inherit; font-size: 14px; font-weight: 500; letter-spacing: -0.005em;
  color: var(--color-text-mid, var(--muted)); white-space: nowrap;
  transition: color 150ms ease, background 150ms ease;
}
.segmented button[aria-selected="true"] { color: var(--color-text, var(--text)); }
.segmented button:not([aria-selected="true"]):hover {
  background: color-mix(in srgb, var(--color-text, var(--text)) 6%, transparent);
  color: var(--color-text, var(--text));
}
.segmented button:focus { outline: none; }
.segmented button:focus-visible {
  outline: 2px solid var(--color-primary, var(--accent)); outline-offset: 1px;
}

/* The stand-in surface the active button wears until the thumb is measured.
   Identical pixels to .segmented__thumb, so dropping it is invisible. In the
   app this covers the gap before hydration; here it covers the gap before the
   script runs, which is the same bug wearing a different hat. */
.segmented button.segmented--preseat {
  background: var(--color-bg-card, var(--panel));
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--color-border, var(--border)) 70%, transparent),
    0 1px 3px rgba(0, 0, 0, 0.08),
    0 1px 2px rgba(0, 0, 0, 0.05);
}

.seg-count { color: var(--color-text-mid, var(--muted)); font-variant-numeric: tabular-nums; }

/* ── underline variant — mirrors NoiTabsUnderline.tsx ─────────────────────── */
.segmented[data-underline] {
  gap: 0; padding: 0; border-radius: 0; background: transparent;
  border-bottom: 1px solid var(--color-border, var(--border));
}
.segmented[data-underline] button {
  border-radius: 0; padding: 10px 15px;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.segmented[data-underline] button:not([aria-selected="true"]):hover { background: transparent; }
.segmented[data-underline] button[aria-selected="true"] {
  border-bottom-color: var(--color-text, var(--text));
}

@media (prefers-reduced-motion: reduce) { .segmented__thumb { transition: none; } }
@media (max-width: 560px) { .segmented button { font-size: 13px; padding: 6px 11px; } }

/* ── GENERATED — DO NOT EDIT ────────────────────────────────────────────────
   Copied verbatim from the app by `bin/sync-mirror`. Every rule below is the
   app's own CSS, byte for byte, so syncing is a re-run rather than a re-read.

   Anything that needed TRANSLATING (Tailwind strings, React <style> blocks)
   is NOT here — it is in data-table.css, guarded by `bin/check-mirror`.

   Class names are the APP'S (.dt-row, .dt-link-cell, …) on purpose: keeping
   the app's names is what lets these bytes be copied instead of rewritten.
*/

/* ── cell styling (.dt-th / .dt-td) — VERBATIM from propflowai/src/app/globals.css ── */
/* ── Standard table (DataTableCore) cell styling ─────────────────────────────
   These declarations used to live in inline `style={{…}}` objects on every
   <th> and <td> the component renders. They are here instead so that a
   stylesheet is the source of truth for how a PropFlow table LOOKS — which is
   what lets other surfaces (docs.propflowai.co mirrors this table) consume the
   real bytes rather than re-typing values read out of the .tsx by hand.

   What stayed inline is what CANNOT be a static rule: per-column `textAlign`
   and `width`, the dragged-column `overflow`, the pinned-header box-shadow,
   the stuck-corner radius, and the sortable/not cursor. Those are props, not
   styling, and a class cannot express them.

   ⚠️ Values here must stay byte-identical to what the inline styles set. An
   inline style OUTRANKS any selector, so while both exist the class is inert —
   which is exactly why the migration could be split into "add the class" and
   "remove the inline style" as two independently safe steps. */
.dt-th {
  padding: 7px 12px;
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--color-text-muted, #4d4d4d);
  text-transform: uppercase;
  white-space: nowrap;
  border-bottom: 1px solid var(--color-border, #ebebeb);
  user-select: none;
}
.dt-td {
  padding: 7px 12px;
  white-space: nowrap;
  border-bottom: 1px solid var(--color-border, #ebebeb);
  color: var(--color-text, #171717);
}

/* ── TableSearch (.dt-search family) — from the package, whole file — VERBATIM from propflowai/packages/ui/styles.css ── */
/**
 * @propflow/ui — the stylesheet the components need.
 *
 * These rules were lifted out of the app's globals.css, where the block's own
 * comment already named this move: "unlike a state variable — a stylesheet can
 * be shipped to another surface." This is that surface. The app now imports
 * this file rather than carrying its own copy, so there is ONE definition.
 *
 * THEMING. Every custom property carries a fallback matching PropFlow's light
 * theme. Inside propflowai the tokens are defined and the fallback never fires,
 * so this is visually identical to what it replaces; outside it, a consumer that
 * defines nothing still gets a correctly-styled component instead of an
 * unstyled one, and a consumer that defines the tokens is themed for free.
 * An undefined custom property with no fallback is an INVALID declaration —
 * it resolves to `inherit`, not to something sensible — which is why every one
 * of these has a second argument.
 *
 * NOT here: geometry. `rounded-full pl-10 pr-8 py-2 text-sm w-full` are still
 * Tailwind utilities in the JSX, so a consumer needs Tailwind (and to scan this
 * package) for the component to be shaped correctly. Converting those to plain
 * CSS is what would make the package framework-independent; it is a separate
 * step and deliberately not bundled in here.
 */

.dt-search {
  background-color: var(--color-bg, #FFFFFF);
  color: var(--color-text, #0F2060);
  border: 1px solid var(--color-border, #E8E8EB);
}
.dt-search:focus {
  border-color: var(--color-primary, #4F46E5);
  /* Hardcoded blue predating the indigo primary — preserved exactly as it was
     inline; changing it is a design decision, not a refactor. */
  box-shadow: 0 0 0 3px rgba(29, 78, 216, 0.1);
}
.dt-search-icon { color: var(--color-text-faint, #4F6B9E); }
.dt-search-wrap:focus-within .dt-search-icon { color: var(--color-primary, #4F46E5); }
.dt-search-clear { color: var(--color-text-faint, #4F6B9E); }

/* The recents popover's enter animation. Shared: TableSearch uses it here, and
   the app's CollapsibleFilters uses the same name — which is why this lives in
   the package rather than being duplicated on both sides. */
@keyframes filterDropdownEnter {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ── pagination controls (.pf-page-ctl family) — VERBATIM from propflowai/src/app/globals.css ── */
/* ── TablePagination ─────────────────────────────────────────────────────────
   Lifted VERBATIM out of the component, where it lived in a
   `<style href="pf-table-pagination" precedence="medium">` element. It was
   always real CSS — it simply could not leave the .tsx, which is the whole
   problem this effort is fixing: another surface cannot import a string
   literal out of a React component.

   The container tiers still resolve against `container-name: pfpagination`,
   declared inline on the wrapper (that one IS dynamic scoping, not styling).

   The comment the component carried, kept because it is the reason this is a
   stylesheet and not inline styles in the first place: all page-control
   colouring lives here so :hover and :focus-visible actually apply — an inline
   backgroundColor would outrank a stylesheet :hover and kill the affordance.
   Three tiers: hover = subtle tint, focus = border highlight, active (current
   page) = filled. */
.pf-page-ctl {
  font-size: 12px;
  font-weight: 500;
  border-radius: 9999px;
  border: 1px solid var(--color-border);
  background: transparent;
  color: var(--color-text-muted);
  transition: background-color var(--motion-duration-fast) var(--motion-ease-standard),
              border-color var(--motion-duration-fast) var(--motion-ease-standard),
              color var(--motion-duration-fast) var(--motion-ease-standard);
}
.pf-page-ctl:not(:disabled):not(.pf-page-ctl--active):hover {
  background-color: var(--color-bg-hover);
  color: var(--color-text);
}
.pf-page-ctl:not(:disabled):focus-visible {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 1px var(--color-primary);
}
.pf-page-ctl:disabled { opacity: 0.3; cursor: default; }
.pf-page-ctl--num {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.pf-page-ctl--nav { padding: 6px 12px; cursor: pointer; }
.pf-page-ctl--active {
  border-color: var(--color-primary);
  background: var(--color-primary);
  color: var(--color-on-primary);
  cursor: default;
}
.pf-page-ellipsis {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  color: var(--color-text-faint);
  user-select: none;
}
/* Container-width tiers (container: pfpagination, declared on the
   wrapper). Wide shows the full 7-slot window; a narrow bar swaps to
   the compact 3-page window; the narrowest drops numbers entirely so
   the bar is one row at every width. Rules are ordered widest-first
   so the narrower query wins where they overlap. */
.pf-pgn-wide { display: flex; }
.pf-pgn-cmp { display: none; }
.pf-pgn-sizer { display: inline-flex; }
@container pfpagination (max-width: 719px) {
  .pf-pgn-wide { display: none; }
  .pf-pgn-cmp { display: flex; }
}
@container pfpagination (max-width: 599px) {
  .pf-pgn-sizer { display: none; }
}
@container pfpagination (max-width: 479px) {
  .pf-pgn-cmp { display: none; }
}
      

/* The footer bar's own border colour and the range text, previously inline. */
.pf-pgn-bar { border-color: var(--color-border); }
.pf-pgn-range { color: var(--color-text-faint); }

/* ── row states + the cross-link cell — VERBATIM from propflowai/src/app/globals.css ── */
.dt-row { transition: background 80ms ease; }
.dt-row:hover { background-color: var(--color-bg-subtle, #fafafa); }
/* Standard in-table cross-link cell (LinkCell.tsx): neutral at rest, underline
   on hover — the underline + pointer carry "this cell navigates", NOT a hue
   jump (the neutral --color-link treatment; accent is never a text color).
   Inline style sets rest color; :hover can only live here. */
.dt-link-cell { text-decoration: none; transition: color 80ms ease; }
.dt-link-cell:hover { color: var(--color-link-hover) !important; text-decoration: underline; text-underline-offset: 2px; }
.dt-row--active { background-color: var(--color-hover-overlay, rgba(0, 0, 0, 0.04)); }
.dt-row--tint:hover,
.dt-row--tint.dt-row--active {
  background-image: linear-gradient(var(--color-hover-overlay, rgba(0, 0, 0, 0.04)), var(--color-hover-overlay, rgba(0, 0, 0, 0.04)));
}

/* ── sticky first column — VERBATIM from propflowai/src/app/globals.css ── */
/* ── Standard table (DataTableCore) opt-in sticky first column ───────────────
   stickyFirstColumn pins the first th/td/tfoot cell over the horizontal
   scroll. Pinned cells need their own opaque background (a row tint reaches
   them via the inherited --dt-row-tint custom property); hover + selection
   repaint the pinned cell so it never looks detached from its row. The tfoot
   cell carries its bg-subtle inline (DataTableCore), so no rule is needed
   here for it. */
.dt-col-sticky { position: sticky; left: 0; z-index: var(--z-local); }
thead .dt-col-sticky { background-color: var(--color-bg-subtle, #fafafa); }
tbody .dt-col-sticky { background-color: var(--dt-row-tint, var(--color-bg-card, #fff)); }
.dt-row--active > .dt-col-sticky {
  background-image: linear-gradient(var(--color-hover-overlay, rgba(0, 0, 0, 0.04)), var(--color-hover-overlay, rgba(0, 0, 0, 0.04)));
}
.dt-row:hover > .dt-col-sticky { background-color: var(--color-bg-subtle, #fafafa); background-image: none; }
.dt-row--tint:hover > .dt-col-sticky {
  background-color: var(--dt-row-tint, var(--color-bg-card, #fff));
  background-image: linear-gradient(var(--color-hover-overlay, rgba(0, 0, 0, 0.04)), var(--color-hover-overlay, rgba(0, 0, 0, 0.04)));
}

/* ── mobile card tier — VERBATIM from propflowai/src/app/globals.css ── */
/* ── Standard table (DataTableCore) opt-in mobile card renderer ──────────────
   A table with renderMobileCard renders BOTH the table and a stacked-card
   list; at/below the card tier the table hides and the cards show. Pure CSS
   visibility switch — no ResizeObserver. display-based hiding keeps the
   inactive representation out of the a11y tree + tab order. The 560px is a
   literal (a @container query can't read a CSS var) — mirrored as
   MOBILE_CARD_TIER_PX in DataTableCore.tsx, drift-fenced by
   data-table-tiers.drift.test.ts. Tables without renderMobileCard never get
   the .dt-has-cards class, so they keep scroll-with-compaction at any width. */
.dt-cards { display: none; }
@container datatable (max-width: 560px) {
  .dt-has-cards .dt-table { display: none; }
  .dt-has-cards .dt-cards { display: block; }
}

/* ── Data table — a static mirror of the app's DataTable stack ───────────────
   ⚠️ NOT THE SOURCE OF TRUTH.
   Source: propflowai/src/components/ui/data/DataTable.tsx (the shell),
           DataTableCore.tsx (header + body), StatusDot.tsx, LinkCell.tsx,
           TimeCell.tsx, IconCell.tsx, TickerCell.tsx,
           propflowai/src/components/ui/table/FilterBar.tsx (pills + bar),
           TableCount.tsx, TablePagination.tsx,
           propflowai/src/components/ui/TableSearch.tsx,
           propflowai/src/app/globals.css (tokens + .dt-* row rules),
           propflowai/docs/planning/datatable-column-conventions.md (cells).
   Full extraction with file:line for every value: /tmp/dt-port.md

   WHAT THIS IS. propflowai.co/clara/conversations renders the standard
   <DataTable> with zero bespoke chrome (ConversationsLog.tsx:2293), so
   mirroring the standard stack mirrors that page exactly. What ports is the
   SHELL, the FILTER CHROME and the CELL VOCABULARY. What does not port —
   measured column compaction, the ghost-measured "+N" overflow split, every
   popover, cold-load skeletons — is listed in §7 of the .md rather than
   half-faked here.

   TWO TOKEN VOCABULARIES, same contract as segmented.css. The docs chrome
   speaks --color-*; artifacts speak the short palette (--panel --text --muted
   --border --accent --accent-soft). Every colour is a fallback chain, and the
   chains are hoisted into one private --pfdt-* layer below so each is written
   once instead of forty times. The final fallback is the app's LIGHT literal.

   WHY THERE IS A DARK BLOCK. The docs chrome re-points every --color-* under
   html.dark, and an artifact re-points its own short palette, so a host that
   HAS the token needs nothing from us. But four of the values the table needs
   exist in neither vocabulary — the bg-subtle ground, the badge-solid dot
   hues, --color-text-faint, --color-hover-overlay — and their chains bottom
   out in a light literal. The dark block restates those SAME chains with dark
   literals, so a token-bearing host is untouched (its token still wins the
   chain) and a token-less artifact stops painting light values on a dark
   ground. Values from globals.css:663+ (html.dark). */

.pfdt {
  /* Surfaces */
  --pfdt-card:    var(--color-bg-card,   var(--panel, #FFFFFF));
  --pfdt-inset:   var(--color-bg,        var(--panel, #FFFFFF));
  --pfdt-subtle:  var(--color-bg-subtle, #F4F4F6);
  --pfdt-hoverbg: var(--color-bg-hover,  #EBEBEF);
  /* The selected-row wash. rgba, not a hex — it must LAYER over a tinted row
     rather than replace it (globals.css:2168-2177). */
  --pfdt-overlay: var(--color-hover-overlay, rgba(0, 0, 0, 0.04));

  /* Ink */
  --pfdt-text:  var(--color-text,       var(--text,  #0F2060));
  --pfdt-mid:   var(--color-text-mid,   var(--muted, #6680B0));
  --pfdt-muted: var(--color-text-muted, var(--muted, #4F6B9E));
  /* faint is its OWN token in the app (globals.css:51) and is the empty-cell
     em-dash, the count and the neutral dot. The docs chrome does not define
     it, hence the extra rung through --color-text-muted before the literal. */
  --pfdt-faint: var(--color-text-faint, var(--color-text-muted, var(--muted, #4F6B9E)));

  /* Lines. --border is the artifact name; #E8E8EB is globals.css:184. */
  --pfdt-border: var(--color-border, var(--border, #E8E8EB));
  /* The ACTIVE pill's border and the inactive pill's hover border are
     --v-border-strong, which is an alias for --color-text (globals.css:457).
     Not a border token — the pill goes to full ink to read as "set". */
  --pfdt-strong: var(--pfdt-text);

  /* Brand. Only two things wear it: the focused search input and the current
     page button. Never a link, never a cell (LinkCell.tsx:16-19). */
  --pfdt-primary:    var(--color-primary,    var(--accent, #4F46E5));
  --pfdt-on-primary: var(--color-on-primary, #FFFFFF);

  /* Dot hues — the badge `-solid` tokens, NOT the `-text` ones: a -text colour
     reads muddy at 7px and green/blue collide (StatusDot.tsx:23-28). */
  --pfdt-ok:   var(--color-badge-green-solid,  #16A34A);
  --pfdt-warn: var(--color-badge-yellow-solid, #D97706);
  --pfdt-bad:  var(--color-badge-red-solid,    #DC2626);
  --pfdt-info: var(--color-badge-blue-solid,   #2563EB);

  /* Geometry. 8px is the RESOLVED value: FilterBar writes
     var(--v-radius-md, 6px), but --v-radius-md always resolves to --radius-md
     = 8px (globals.css:412,470), so the 6px inline fallback is dead code. */
  --pfdt-radius: var(--radius-md, 8px);
  /* The Card frame. Tailwind `rounded-lg` → var(--radius-lg), and the app's
     unlayered :root (globals.css:413, 12px) beats Tailwind's @layer theme
     default (0.5rem), so the card is 12px — not 8. */
  --pfdt-radius-card: var(--radius-lg, 12px);
  --pfdt-dur:  var(--motion-duration-fast, 120ms);
  --pfdt-ease: var(--motion-ease-standard, cubic-bezier(0.4, 0, 0.2, 1));
  /* --v-bg-hover-strong is USED twice in FilterBar (:1001, :1042) and DEFINED
     nowhere in the repo, so the app paints this literal in both themes — a
     black wash on dark. Mirrored, not "fixed": a token here would make the
     docs pill stop matching the product. */
  --pfdt-press: rgba(0, 0, 0, 0.10);
}

/* ── The card ───────────────────────────────────────────────────────────────
   <Card padding={false}> — Card.tsx:65-72 via DataTable.tsx:557. The Core
   itself is `borderless` (DataTable.tsx:499): no border, no radius, no ground
   of its own. The frame is entirely the card's. shadow-sm is Tailwind v4's
   (theme.css:408); the app does not redefine it. */
.pfdt {
  background: var(--pfdt-card);
  border: 1px solid var(--pfdt-border);
  border-radius: var(--pfdt-radius-card);
  box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
  color: var(--pfdt-text);
  /* Named size container, mirroring DataTableCore.tsx:1642-1646 — the stacked
     tier below binds to the TABLE's width, not the viewport's, so a table in a
     narrow column stacks and a wide one does not. */
  container: pfdt / inline-size;
}

/* The horizontal scroller. Separate from .pfdt so the card's radius survives:
   the app scrolls the Core's own div (DataTableCore.tsx:1640), never the card. */
/* ⚠️ overflow AND position:sticky are in conflict, and the conflict is silent.
   An ancestor with any overflow other than `visible` becomes the sticky
   element's containing block — so with `overflow-x: auto` here, the sticky
   header sticks to a box that does not scroll vertically, i.e. it does not
   stick at all. It still computes `position: sticky`, which is why this
   shipped looking correct: the declaration is present and does nothing.

   Since the table went to `table-layout: fixed` with percentage columns it
   always fits its container — columns get narrower, they do not overflow — so
   the horizontal scroll is only wanted where the fixed minimums would squeeze.
   Below that width the sticky header is not what anyone is missing. */
.pfdt-scroll { overflow: visible; }
@media (max-width: 700px) {
  .pfdt-scroll { overflow-x: auto; }
}

/* ── The table ──────────────────────────────────────────────────────────────
   13px and border-collapse are set once on <table> (DataTableCore.tsx:1670).
   Colour is restated because artifacts are served with no doctype and render
   in QUIRKS mode, where a table does not inherit body colour. */
.pfdt table {
  /* `display: table` is RESTATED, not inherited. style.css carries a site-wide
     `table { display: block; overflow-x: auto }` so ADR markdown tables scroll
     on a narrow screen — and a <table> displayed as a block is no longer a
     table box, so `table-layout` and percentage column widths stop governing.
     That is what left this table 1686px wide with 1272px of columns and a
     414px dead band down the right: every width rule was being written against
     a box that had stopped being a table. The horizontal scroll it was buying
     is provided here by .pfdt-scroll instead. */
  display: table;
  width: 100%;
  max-width: none;
  border-collapse: collapse;
  font-size: 13px;
  color: var(--pfdt-text);
}

/* The same site-wide block puts a full 1px box on every th and td — right for
   a markdown table, wrong for this one, which is hairline-between-rows only.
   Reset here rather than narrowing the global rule: ADR pages are entitled to
   their own table styling, and this component should not depend on it. */
/* ⚠️ SPECIFICITY: this reset is (0,2,1) and the cell rules below are (0,1,1),
   so a blanket `border: 0` here BEATS the hairline they set — which is how the
   horizontal lines between rows disappeared. Kill only the side borders the
   site-wide markdown-table rule adds; the bottom hairline is the component's
   and is set below. */
.pfdt table th,
.pfdt table td {
  border-top: 0;
  border-left: 0;
  border-right: 0;
  background: none;
}

/* ── the cell, as DataTableCore actually renders it ─────────────────────────
   THESE VALUES ARE INLINE STYLES IN THE APP (DataTableCore.tsx:1815-1827 for
   the body cell, :1409-1432 for the header), not CSS — so they are the one
   part of the table that could never be copied, only transcribed. That is why
   the docs table looked "slightly different" everywhere at once: the copied
   stylesheet never carried them, and every hand-written approximation of a
   padding or a hairline was a guess standing in for a number that exists.

   `cellBorder` is the app's own constant (:1313) and its fallback is #ebebeb —
   NOT the #E8E8EB of --color-border. Transcribed as-is rather than
   "corrected": a mirror that improves on its source has stopped being one. */
/* Right-aligned numerics keep the app's alignment rule. */
.pfdt td.pfdt-num, .pfdt th.pfdt-num { text-align: right; }
/* The tag cell is the one place the app's nowrap is wrong for us — three chips
   need to wrap rather than have the third vanish under an ellipsis. */
.pfdt td[data-label="Tags"] { white-space: normal; overflow: visible; }

/* ── Header row — DataTableCore.tsx:1405-1432, ground at :1704 ───────────────
   11.5px is not a typo; neither is the 0.02em. Headers never wrap — a wrapped
   header makes tall ragged rows when the pane narrows, so the table scrolls
   instead (:1415-1418). */
/* ⚠️ ONE definition of the header cell, same story as .pfdt td: there were two
   — this one and an earlier copy against the app's RAW fallbacks
   (`--color-text-muted, #4d4d4d`) instead of the --pfdt-* chain. The later
   block won on colour and border, the earlier one on line-height, overflow and
   box-shadow, so neither read as the real rule. `.pfdt-head` rode along on the
   selector and styles nothing — no element ever carried that class.

   box-shadow deliberately does NOT live here: the app paints it only when the
   header is pinned, and ours is on `.pfdt thead th` with the sticky rule, which
   is the same condition. */
/* ⚠️ ONLY what `.dt-th` does NOT set. ADR-0131: padding, font-size/weight,
   letter-spacing, text-transform, colour, nowrap, the hairline and user-select
   are now COPIED from the app into app-verbatim.css and applied by `.dt-th`,
   which every header cell here carries. Re-declaring them would just shadow the
   copied rule with a hand-typed one — `.pfdt th` is (0,1,1) and `.dt-th` is
   (0,1,0), so this selector wins every collision. That is exactly the drift
   this effort removes, so it must stay empty of anything the app owns.

   These three are ours: the app sets text-align per COLUMN inline, and gets
   line-height/overflow from its own table context. */
.pfdt th {
  text-align: left;
  line-height: normal;
  overflow: hidden;
}



/* Sort caret — 8×8, and 0.35 opacity is what says "sortable but not sorted"
   (DataTableCore.tsx:1391). Cursor lives with the [data-sort] rule below. */
/* The app centres the caret against the label with inline-flex + gap:4
   (DataTableCore.tsx:1388). Baseline alignment would hang an 8px box off the
   bottom of 11.5px uppercase text, so middle here does the same job. */
.pfdt-sort { display: inline-block; width: 8px; height: 8px; margin-left: 4px; opacity: 0.35; vertical-align: middle; }
.pfdt th[aria-sort="ascending"] .pfdt-sort,
.pfdt th[aria-sort="descending"] .pfdt-sort { opacity: 1; }

/* ── Body cells + rows — DataTableCore.tsx:1813-1827, globals.css:2198-2204 ──
   NO ZEBRA. Rows are separated by the hairline alone — grepped, there is no
   nth-child rule on any table row in the app. */
/* The app's rows compute line-height `normal`; ours inherited the docs body
   1.65 and rendered visibly taller rows for the same font size. Set on the ROW
   as well as the cell — a cell-only rule leaves the <tr> itself at 21.45px,
   which is what the computed-style diff caught. */
.pfdt thead, .pfdt tbody, .pfdt tfoot,
.pfdt tbody tr, .pfdt thead tr, .pfdt tfoot tr { line-height: normal; }
/* ⚠️ ONLY what `.dt-td` does NOT set — see the note on `.pfdt th`. Padding,
   nowrap, the hairline and the text colour are copied from the app and applied
   by `.dt-td`, which every body cell carries. */
.pfdt td {
  text-align: left;
  line-height: normal;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pfdt tbody tr { transition: background 80ms ease; }
.pfdt tbody tr:hover { background-color: var(--pfdt-subtle); }
/* Selection is a translucent WASH so it composes with a row tint instead of
   replacing it; hover deliberately outranks it (globals.css:2168-2177). */
.pfdt tbody tr.is-selected { background-color: var(--pfdt-overlay); }

/* Footer/totals row — DataTableCore.tsx:1856-1869. border-collapse merges this
   top border with the last body row's bottom, so no double hairline. */
.pfdt tfoot td {
  font-weight: 500;
  background: var(--pfdt-subtle);
  border-top: 1px solid var(--pfdt-border);
  border-bottom: none;
}

.pfdt caption {
  text-align: left;
  padding: 10px 14px;
  font-weight: 500;
  color: var(--pfdt-muted);
}

/* ── Filter bar — FilterBar.tsx:952-956 ─────────────────────────────────────
   px-6 py-3 → 12px 24px. nowrap, not wrap: the app never lets the bar take a
   second row; it drops the count instead (see .pfdt-count below). */
.pfdt-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: nowrap;
  padding: 12px 24px;
  border-bottom: 1px solid var(--pfdt-border);
}
/* The pill row takes the slack and CLIPS — pills that don't fit collapse into
   the "+N" pill in the app; statically they are simply hidden (FilterBar.tsx:915). */
/* ⚠️ An explicit `display` BEATS the UA's `[hidden] { display: none }`, so a
   flex row that the script hides with the hidden attribute stays on screen.
   The tag pill is artifact-only and was showing on every view because of it. */
.pfdt-pillrow[hidden] { display: none; }
.pfdt-pillrow {
  display: flex;
  align-items: center;
  gap: 8px;              /* PILL_GAP — FilterBar.tsx:111 */
  flex: 1;
  min-width: 0;
  overflow: hidden;
}

/* ── Filter pills — FilterBar.tsx:964-1002 (box), :353-361 (trigger) ─────────
   The two states are a whole design decision, not a shade change: INACTIVE is
   a DASHED hairline saying "nothing set here, click me"; ACTIVE is a SOLID
   full-ink border on a filled ground saying "this is constraining your view".
   Both are 28px tall so the row never shifts when one turns on. */
.pfdt-pill {
  display: inline-flex;
  align-items: center;
  height: 28px;
  flex-shrink: 0;
  border-radius: var(--pfdt-radius);
  background: var(--pfdt-card);
  border: 1px dashed var(--pfdt-border);
  transition: border-color var(--pfdt-dur) var(--pfdt-ease),
              background-color var(--pfdt-dur) var(--pfdt-ease);
}
.pfdt-pill:not(.is-active):hover {
  background: var(--pfdt-hoverbg);
  border-color: var(--pfdt-strong);
}
.pfdt-pill.is-active {
  background: var(--pfdt-subtle);
  border: 1px solid var(--pfdt-strong);
}

/* The clickable face. 26px inside a 28px box so the 1px border shows on both
   edges. Left-rounded when active because the ✕ finishes the right side. */
.pfdt-pill-trigger {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 26px;
  padding: 0 8px;
  border: 0;
  background: transparent;
  cursor: pointer;
  font: inherit;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--pfdt-muted);
  border-radius: var(--pfdt-radius);
  white-space: nowrap;
}
.pfdt-pill.is-active .pfdt-pill-trigger {
  color: var(--pfdt-text);
  border-radius: var(--pfdt-radius) 0 0 var(--pfdt-radius);
}
/* v-focus-ring is on these buttons in the app and DEFINED NOWHERE (confirmed
   by the app's own note at SegmentedControl.tsx:117), so the product ships the
   UA default. This ring is a deliberate improvement, not a mirror. */
.pfdt-pill-trigger:focus-visible {
  outline: 2px solid var(--pfdt-primary);
  outline-offset: 1px;
}

/* The ▾ renders ONLY on an inactive pill — an active pill's face is its VALUE,
   and a caret next to a value reads as "more values", which is wrong
   (FilterBar.tsx:587). */
.pfdt-caret { flex-shrink: 0; font-size: 11px; line-height: 1; opacity: 0.55; }
.pfdt-pill.is-active .pfdt-caret { display: none; }

/* The ✳ mark — "some rows are withheld by this filter". Superscripted tight to
   the label; the -5px cancels all but ~1px of the trigger's 6px gap, so it
   reads as a mark ON the word rather than a separate element
   (FilterBar.tsx:571-585). Keep in step with .pfdt-pill-trigger's gap. */
/* NOT EXERCISED HERE: the asterisk marks a filter whose value is an EXCLUSION
   set (FilterBar.tsx:571). The tag filter is single-select, so nothing to mark. */
.pfdt-mark {
  flex-shrink: 0;
  align-self: flex-start;
  margin: 3px 0 0 -5px;
  font-size: 7px;
  font-weight: 700;
  opacity: 0.7;
}

/* The ✕. Width 0 at rest — not display:none — so the pill's natural width is
   identical whether or not you are hovering it, which is what stops the row
   from reflowing under the pointer (FilterBar.tsx:665-667, 987-998). */
.pfdt-x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 18px;
  width: 0;
  opacity: 0;
  padding: 0;
  overflow: hidden;
  border: 0;
  background: transparent;
  cursor: pointer;
  border-radius: 4px;
  color: var(--pfdt-muted);
  font-size: 11px;
  line-height: 1;
  transition: width var(--pfdt-dur) var(--pfdt-ease),
              opacity var(--pfdt-dur) var(--pfdt-ease),
              margin var(--pfdt-dur) var(--pfdt-ease),
              color var(--pfdt-dur) var(--pfdt-ease),
              background-color var(--pfdt-dur) var(--pfdt-ease);
}
/* Only an ACTIVE pill has a ✕ at all — the app renders no node for an inactive
   one (FilterBar.tsx:632). */
.pfdt-pill:not(.is-active) .pfdt-x { display: none; }
.pfdt-pill:hover .pfdt-x,
.pfdt-pill:focus-within .pfdt-x { width: 18px; opacity: 1; margin-right: 4px; }
.pfdt-x:hover { color: var(--pfdt-text); background: var(--pfdt-press); }
/* The ✕ EXPANDS on hovering the active pill — it is not toggled in and out of
   the DOM. That is the app's behaviour (FilterBar: the active pill has no
   hover rule of its own; only its ✕ appears) and the reason .pfdt-x ships
   width:0/opacity:0 rather than display:none. Toggling `hidden` instead left
   a button that could never be clicked, because `display:inline-flex` here
   beats the UA stylesheet's `[hidden]{display:none}` — the same collision
   style.css already documents for .tag-chips. */
.pfdt-pill.is-active:hover .pfdt-x,
.pfdt-x:focus-visible {
  width: 18px;
  opacity: 1;
  margin-right: 4px;
}
/* Keyboard users get no hover, so the ✕ must be reachable without one. */
.pfdt-pill.is-active .pfdt-x:focus-visible { outline: 2px solid var(--pfdt-strong); outline-offset: -2px; }

/* ── "+N" overflow pill — FilterBar.tsx:687-711 ─────────────────────────────
   Same box; only the content differs. With nothing applied it is a funnel plus
   the count. With applied filters hidden inside it, each one gets a filled
   MUTED-GRAY chip (not near-black — the heavy dark chip was flagged), so an
   active filter is never invisible just because the bar ran out of room. */
/* NOT EXERCISED HERE: the "+N" chip appears when pills overflow the bar. The
   docs bar has exactly one pill, so there is never an overflow to collapse. */
.pfdt-more-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 4px;
  background: var(--pfdt-muted);
  color: var(--pfdt-card);
  flex-shrink: 0;
}

/* Clear — a quiet reset, never a CTA and never accent blue. Zero horizontal
   padding so it steals no room from the pills (FilterBar.tsx:1035-1046). */
.pfdt-clear {
  border: 0;
  background: transparent;
  cursor: pointer;
  font: inherit;
  font-size: 12px;
  font-weight: 500;
  padding: 4px 0;
  border-radius: var(--pfdt-radius);
  color: var(--pfdt-mid);
  transition: color 150ms var(--pfdt-ease), background-color 150ms var(--pfdt-ease);
}
.pfdt-clear:hover { color: var(--pfdt-text); background-color: var(--pfdt-press); }

/* ── Search — TableSearch.tsx:139-152, width from DataTable.tsx:487 ──────────
   Ground is --color-bg, NOT --color-bg-card: the input is inset into the card,
   so on a white card it is the same white and the border does the work, while
   in dark it is genuinely darker than the card. */
.pfdt-search {
  width: 100%;
  max-width: 200px;
  font-size: 14px;
  border-radius: 999px;
  padding: 8px 12px;
  outline: none;
  background-color: var(--pfdt-inset);
  color: var(--pfdt-text);
  border: 1px solid var(--pfdt-border);
  transition: all 200ms var(--pfdt-ease);
}
@media (min-width: 1024px) { .pfdt-search { max-width: 280px; } }
.pfdt-search:focus {
  border-color: var(--pfdt-primary);
  /* A hardcoded blue in the app too — rgba(29,78,216,.1) predates the indigo
     primary and was never updated (TableSearch.tsx:150). Mirrored as-is. */
  box-shadow: 0 0 0 3px rgba(29, 78, 216, 0.1);
}
.pfdt-search::placeholder { color: var(--pfdt-faint); }

/* Wrap the input in .pfdt-searchwrap with a 16px <svg> to get the magnifier.
   The icon is a real element (not a background-image) so it can inherit the
   colour token and flip to primary on focus — a data-URI cannot read a var. */
.pfdt-searchwrap {
  position: relative;
  display: inline-block;
  width: 100%;
  max-width: 200px;      /* the constraint moves to the wrapper so the icon rides along */
}
@media (min-width: 1024px) { .pfdt-searchwrap { max-width: 280px; } }
/* pl-10 pr-8 — room for the magnifier left and the clear ✕ right. */
.pfdt-searchwrap .pfdt-search { padding: 8px 32px 8px 40px; max-width: none; }
.pfdt-searchwrap > svg {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  pointer-events: none;
  color: var(--pfdt-faint);
  transition: color 150ms var(--pfdt-ease);
}
.pfdt-searchwrap:focus-within > svg { color: var(--pfdt-primary); }

/* ── Count — TableCount.tsx:57-61, divider at FilterBar.tsx:1053 ────────────
   Reads "N of M", or "M label" when nothing is filtered out. The leading rule
   is a 1px × 20px hairline separating it from the controls. */
.pfdt-count {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  /* FilterBar's count is its last child and the pill row (flex:1) pushes it to
     the far edge. Our pill row is artifact-only, so on the other four views
     nothing takes up the slack — this keeps the count pinned right regardless. */
  margin-left: auto;
  font-size: 12px;
  white-space: nowrap;
  text-align: right;
  color: var(--pfdt-faint);
}
/* The count is the least-important control on the bar, so it is the first
   thing to bow out when the bar tightens — the app's own threshold
   (FilterBar.tsx:1006-1008), raised 620→760 so pills get their room first. */
@container pfdt (max-width: 760px) { .pfdt-count { display: none; } }

/* ── Pagination — TablePagination.tsx:120, :132-175 ─────────────────────────
   px-4 md:px-6 py-3. Never allowed a second row: the app compacts the number
   window instead (:176-193), which a static bar does by hiding numbers. */
.pfdt-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 16px;
  border-top: 1px solid var(--pfdt-border);
}
@media (min-width: 768px) { .pfdt-foot { padding: 12px 24px; } }

/* Narrow footer — TablePagination.tsx:178-194. The app runs three container
   tiers: full 7-slot window, a COMPACT 3-page window at <=719px, and at <=479px
   no numbers at all so the footer is one row at every width. It can switch
   because it renders both windows and hides one; a static page has a single
   window, so the compact tier is the one thing not portable here — this takes
   the app's narrowest tier and drops the numbers, which is what keeps the row
   from wrapping. Without it the page itself scrolled sideways on a phone: the
   button run measured 385px inside a ~358px card. The footer comment two
   blocks up already claimed a static bar "does by hiding numbers" — it just
   never had the rule. */
@container pfdt (max-width: 560px) {
  /* ⚠️ `.pfdt` qualifier, not bare classes: `.pfdt-page--num` sets
     `display: inline-flex` for its 32px slot and `.pfdt-ellipsis` does the
     same, both LATER in this file at the same (0,1,0). A bare
     `.pfdt-page--num { display: none }` here ties and loses on source order —
     the numbers stayed and the footer kept overflowing. (0,2,0) settles it.
     Third specificity tie in this file today; the pattern is a later rule at
     equal weight, and the fix is always to qualify rather than to reorder. */
  .pfdt .pfdt-page--num,
  .pfdt .pfdt-ellipsis { display: none; }
}
.pfdt-range { font-size: 12px; white-space: nowrap; color: var(--pfdt-faint); }
.pfdt-pages { display: flex; align-items: center; gap: 4px; }

/* ⚠️ ONLY what `.pf-page-ctl` does NOT set. ADR-0131: the button treatment —
   size, radius, border, ground, colour, transition, hover, focus-visible,
   disabled, the 32x32 number slot, the Previous/Next padding and the filled
   current page — is COPIED from the app into app-verbatim.css and applied by
   `.pf-page-ctl*`, which every control here carries alongside its `.pfdt-page*`
   hook (kept for the narrow-width tier and the tests).

   These two are ours, and both follow from the DOCS body differing from the
   app's:
     - font-family, because the app's buttons inherit ITS stack, not ours.
       Narrowed from the `font: inherit` shorthand that used to be here, which
       would reset the copied font-size and weight along with the family.
     - line-height, because the docs body is 1.65 and would make Previous/Next
       ~34px beside the 32px number slots. The app has no body line-height, so
       it lands on 1.5 and the two forms match exactly. */
.pfdt-page {
  font-family: inherit;
  line-height: 1.5;
}
/* Sizing and colour come from the copied `.pf-page-ellipsis`. */
.pfdt-ellipsis { line-height: 1.5; }

/* ── Cell vocabulary ────────────────────────────────────────────────────────
   Each rule below was a decision, recorded in
   propflowai/docs/planning/datatable-column-conventions.md. */

/* NO BADGES IN TABLES — a status is a coloured dot plus a NEUTRAL label, never
   a filled pill ("it's a bad mix", owner 2026-07-12, StatusDot.tsx:5-8). The
   dot carries the scan; the label stays --color-text so the status column
   doesn't compete with the identity column. 7px is the table-cell size. */
.pfdt-dot {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
  font-size: 14px;
  color: var(--pfdt-text);
}
.pfdt-dot::before {
  content: "";
  flex-shrink: 0;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--pfdt-faint);      /* neutral tone — StatusDot.tsx:30 */
}
.pfdt-dot--ok::before   { background: var(--pfdt-ok); }
.pfdt-dot--warn::before { background: var(--pfdt-warn); }
.pfdt-dot--bad::before  { background: var(--pfdt-bad); }
.pfdt-dot--info::before { background: var(--pfdt-info); }

/* Cross-link cell. NEUTRAL in both states: --color-link-hover resolves to
   --color-text (globals.css:209), so rest and hover are the same ink and the
   UNDERLINE plus the pointer carry "clickable". A blue table cell is the
   flagged regression — link-color-drift.test.ts fences it (LinkCell.tsx:16-19).
   Use only for a CROSS-reference; if the row click already goes there, render
   plain text. */
.pfdt-link {
  color: var(--pfdt-text);
  font-weight: 500;
  text-decoration: none;
  transition: color 80ms ease;
}
.pfdt-link:hover {
  color: var(--color-link-hover, var(--pfdt-text));
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* Money and counts: right-aligned with tabular numerals so digits line up down
   the column (column-conventions §4, §5). Identity/text columns stay left. */
.pfdt-num { text-align: right; font-variant-numeric: tabular-nums; }
.pfdt th.pfdt-num, .pfdt td.pfdt-num { text-align: right; }

/* Empty is a FAINT em-dash — not blank (the cell looks broken) and not
   full-strength (it reads as data). column-conventions §1; live at
   ConversationsLog.tsx:1918 and TimeCell.tsx:87. */
.pfdt-empty { color: var(--pfdt-faint); }
.pfdt-empty:empty::before { content: "—"; }

/* Timestamps — muted and nowrap. Deliberately NOT tabular-nums: the app's
   TimeCell sets neither (TimeCell.tsx:117), and the label is usually a word
   ("2h ago"), not a number. */
.pfdt-time { color: var(--pfdt-muted); white-space: nowrap; }

/* Compact code with the full value on hover — TickerCell.tsx:20-23. */
/* NOT EXERCISED HERE: no column renders a TickerCell. */
.pfdt-ticker { color: var(--pfdt-muted); font-weight: 500; letter-spacing: 0.02em; }
/* Icon cell — IconCell.tsx:19-27. */
/* NOT EXERCISED HERE: no column renders an IconCell. */
.pfdt-icon { display: inline-flex; color: var(--pfdt-muted); }

/* ── Stacked-card tier ──────────────────────────────────────────────────────
   560px, not 620: MOBILE_CARD_TIER_PX = 560 (DataTableCore.tsx:186), CSS at
   globals.css:2245-2249, drift-fenced by data-table-tiers.drift.test.ts:34.
   A @container query on .pfdt, mirroring the app — the tier binds to the
   TABLE's width, so a table in a narrow column stacks and a wide one doesn't.

   The app renders a SECOND list of real cards (renderMobileCard); static CSS
   can't produce a second DOM tree, so the same look comes from re-laying-out
   the same <td>s and pulling the column name out of data-label. Every td that
   should show its label needs data-label="…" in the markup; a td without one
   (an actions cell, an icon) just stacks bare, which is correct.

   Opt-in in the app, and the conversations table does NOT opt in — it scrolls
   horizontally instead. This is here for docs tables narrow enough to need it;
   drop .pfdt-stack from the root to get the app's scroll-instead behaviour. */
@container pfdt (max-width: 560px) {
  .pfdt-stack .pfdt-scroll { overflow-x: visible; }
  .pfdt-stack thead { display: none; }
  .pfdt-stack table,
  .pfdt-stack tbody,
  .pfdt-stack tr,
  .pfdt-stack td { display: block; width: auto; }
  /* The card boundary moves from the CELL to the ROW — one hairline per row,
     the way the app's .dt-card carries it (DataTableCore.tsx:1904). */
  .pfdt-stack tbody tr {
    padding: 12px 16px;
    border-bottom: 1px solid var(--pfdt-border);
  }
  .pfdt-stack td {
    padding: 0;
    border-bottom: 0;
    /* nowrap is a HORIZONTAL-scroll defence; stacked, it just truncates. */
    white-space: normal;
  }
  /* ⚠️ RE-SHOW the columns the narrow-width rule drops. Below the two-column
     tier a SCROLLING table sheds By and Tags to fit (the @media max-width:900px
     block further down) — the right call when the constraint is horizontal.
     Stacked, there is no horizontal constraint, so dropping them just deletes
     data: the cards rendered Title and Updated only, silently losing every tag
     and author. That rule is (0,2,2) and `.pfdt-stack td` is (0,1,1), so it
     wins on specificity no matter which comes later — hence the table.* here,
     which outranks it (0,3,2).

     `.pfdt.pfdt-stack` (both classes on the one element) is what does it:
     `.pfdt-stack table.pfdt-no-num td:nth-child(3)` TIES the drop rule at
     (0,3,2) and loses on source order, which is why Tags stayed hidden while
     By — matched by a (0,3,2) variant against a (0,2,2) rule — came back. */
  .pfdt.pfdt-stack table td:nth-child(3),
  .pfdt.pfdt-stack table td:nth-child(4),
  .pfdt.pfdt-stack table.pfdt-no-num td:nth-child(3),
  .pfdt.pfdt-stack table.pfdt-no-num td:nth-child(4) { display: block; }
  /* An empty cell would otherwise render its label with nothing under it —
     an orphan "UPDATED" heading on a row that has no date. */
  .pfdt-stack td:empty { display: none; }
  .pfdt-stack td + td { margin-top: 6px; }
  /* The label wears the header's exact type so the stacked row still reads as
     the same table, not a different component. */
  .pfdt-stack td[data-label]::before {
    content: attr(data-label);
    display: block;
    margin-bottom: 2px;
    font-size: 11.5px;
    font-weight: 500;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--pfdt-muted);
  }
  .pfdt-stack tfoot td { background: transparent; border-top: 0; }
  /* Stacked, the bar and footer lose their side gutters to match the rows. */
  .pfdt-stack .pfdt-bar,
  .pfdt-stack .pfdt-foot { padding-left: 16px; padding-right: 16px; }
}

/* ── Dark ───────────────────────────────────────────────────────────────────
   Same chains, dark literals. A host that defines the token is unaffected —
   its value still wins the chain before the literal is reached. Only the
   values whose chains bottom out light are restated; radii, motion, the
   primary and --pfdt-press are theme-independent in the app and stay put.
   Sources: globals.css:663+ (html.dark).

   Three selectors because three hosts: the docs site toggles `html.dark`,
   an artifact stamps `[data-theme="dark"]`, and an artifact left on "system"
   stamps nothing at all — only prefers-color-scheme separates it, guarded so
   an explicit light choice still wins. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .pfdt {
    --pfdt-card:    var(--color-bg-card,   var(--panel, #14141D));
    --pfdt-inset:   var(--color-bg,        var(--panel, #0A0A0F));
    --pfdt-subtle:  var(--color-bg-subtle, #0F0F17);
    --pfdt-hoverbg: var(--color-bg-hover,  #1C1C28);
    --pfdt-overlay: var(--color-hover-overlay, rgba(255, 255, 255, 0.06));
    --pfdt-text:  var(--color-text,       var(--text,  #F4F4F6));
    --pfdt-mid:   var(--color-text-mid,   var(--muted, #888897));
    --pfdt-muted: var(--color-text-muted, var(--muted, #888897));
    --pfdt-faint: var(--color-text-faint, var(--color-text-muted, var(--muted, #888897)));
    --pfdt-border: var(--color-border, var(--border, #232331));
    --pfdt-ok:   var(--color-badge-green-solid,  #4ADE80);
    --pfdt-warn: var(--color-badge-yellow-solid, #FBBF24);
    --pfdt-bad:  var(--color-badge-red-solid,    #F87171);
    --pfdt-info: var(--color-badge-blue-solid,   #60A5FA);
  }
}
html.dark .pfdt,
[data-theme="dark"] .pfdt {
  --pfdt-card:    var(--color-bg-card,   var(--panel, #14141D));
  --pfdt-inset:   var(--color-bg,        var(--panel, #0A0A0F));
  --pfdt-subtle:  var(--color-bg-subtle, #0F0F17);
  --pfdt-hoverbg: var(--color-bg-hover,  #1C1C28);
  --pfdt-overlay: var(--color-hover-overlay, rgba(255, 255, 255, 0.06));
  --pfdt-text:  var(--color-text,       var(--text,  #F4F4F6));
  --pfdt-mid:   var(--color-text-mid,   var(--muted, #888897));
  --pfdt-muted: var(--color-text-muted, var(--muted, #888897));
  --pfdt-faint: var(--color-text-faint, var(--color-text-muted, var(--muted, #888897)));
  --pfdt-border: var(--color-border, var(--border, #232331));
  --pfdt-ok:   var(--color-badge-green-solid,  #4ADE80);
  --pfdt-warn: var(--color-badge-yellow-solid, #FBBF24);
  --pfdt-bad:  var(--color-badge-red-solid,    #F87171);
  --pfdt-info: var(--color-badge-blue-solid,   #60A5FA);
}

/* The row/pill transitions are affordance, not decoration, but they are all
   sub-200ms colour fades — nothing here needs to move for anyone. */
@media (prefers-reduced-motion: reduce) {
  .pfdt tbody tr,
  .pfdt-pill,
  .pfdt-x,
  .pfdt-page,
  .pfdt-search,
  .pfdt-link { transition: none; }
}

/* ── docs-site additions ─────────────────────────────────────────────────────
   Not in the app. The docs index reuses ONE table for five views, so two
   things exist here that a single-purpose app table never needs. */

/* A neutral cell that is not a link and not a number — the app expresses this
   by simply not adding a class, but the docs index needs to say it explicitly
   because the same column is a link on one view and plain text on another. */
.pfdt-mutedcell { color: var(--pfdt-mid); }

/* The first column holds an ADR's number and a project's page count; on the
   other three views it holds nothing, so it is removed rather than left as an
   empty gutter the eye still has to cross. */
.pfdt table.pfdt-no-num th:first-child,
.pfdt table.pfdt-no-num td:first-child { display: none; }

/* ── column sizing — the static stand-in for the app's measured colgroup ─────
   The app runs `table-layout: fixed` over a <colgroup> it MEASURES: natural
   widths off-screen, then water-filled to the pane with the last column as a
   fixed right anchor (DataTableCore). That measurement pass needs React and a
   layout effect, so it does not port.

   What was here instead was `table-layout: auto`, and it left the table 1686px
   wide with its columns occupying 1272 — 414px of dead space INSIDE the table,
   which is what "doesn't use the space well" was. Auto layout sizes to content
   and is under no obligation to spend the remainder.

   The static equivalent: fixed layout, every column that holds a KNOWN shape
   gets a width, and the one column holding prose gets none — so it absorbs
   everything left over. That is the same behaviour the water-fill produces,
   arrived at by declaration instead of measurement.

   `fixed` also stops a long title from widening the column and shoving the
   dates off the edge: the widths are decided by the first row, not by the
   longest cell anywhere in 563 of them. */
.pfdt table { table-layout: fixed; }

/* Column positions are IDENTICAL in every view — only the first column is
   hidden on the non-ADR tabs, and `display: none` does not renumber
   nth-child. An earlier version added a shifted set of overrides for
   `.pfdt-no-num` and ended up sizing Title as if it were Tags, which is how a
   fix for dead space produced a differently-wrong table.

   So: one set of rules, and the only view-dependent width is the third
   column, because it holds a status dot on ADRs and up to three tag chips
   everywhere else. */
.pfdt table th:nth-child(1) { width: 5rem; }    /* No / Pages */
/* :nth-child(2) — Title — takes NO width, deliberately: it is the slack
   absorber, and the whole point of fixed layout here. */
.pfdt table th:nth-child(3) { width: 11rem; }   /* Status: a dot and one word */
.pfdt table th:nth-child(4) { width: 7rem; }    /* By: a first name */
.pfdt table th:nth-child(5) { width: 10.5rem; } /* Updated: date, sometimes an hour */

/* Tags need room for three chips; status does not. */
.pfdt table.pfdt-no-num th:nth-child(3) { width: 24%; }

/* Fixed layout clips rather than pushes, so anything that could overflow says
   so with an ellipsis instead of vanishing under the next column. */
.pfdt table td { overflow: hidden; text-overflow: ellipsis; }
/* …except the tag cell, which should wrap to a second line rather than hide
   the third tag. */
.pfdt table td[data-label="Tags"] { overflow: visible; white-space: normal; }

@media (max-width: 900px) {
  /* Below the two-column tier the dates are the first thing worth losing. */
  .pfdt table th:nth-child(4), .pfdt table td:nth-child(4),
  .pfdt table.pfdt-no-num th:nth-child(3), .pfdt table.pfdt-no-num td:nth-child(3) { display: none; }
}

/* ── sticky header — DataTableCore.tsx:1704 + :1425-1431 ────────────────────
   `stickyHeader` defaults to TRUE in the app, so this is the normal state of
   the product's table, not an option. It was documented in the port spec
   (§2f) and then not implemented — the kind of gap a written spec makes
   findable, which is the argument for having written it down.

   The bottom hairline is an inset box-shadow rather than a border because a
   collapsed border does not travel with a sticky element: it stays behind
   with the row it was collapsed into, and the header floats over the rows
   with no line under it. */
/* The STRUCTURE is the app's, not merely the look: DataTableCore.tsx:1704 puts
   `position: sticky` + the bg-subtle ground on the <thead> ELEMENT, and leaves
   every <th> transparent. Pinning the th instead renders the same grey band, so
   a screenshot cannot tell them apart — but it is a different mechanism, and a
   mirror that agrees only by coincidence is the thing that drifts. Sticky on
   the section, ground on the section, cells transparent. */
.pfdt thead {
  position: sticky;
  top: var(--pfdt-sticky-top, 0px);
  z-index: 1;
  background: var(--pfdt-subtle);
}
.pfdt thead th {
  background: transparent;
  /* The bottom hairline stays on the CELL as an inset shadow. Under
     border-collapse the header's border belongs to the table's collapsed grid,
     not the cell, so it stays behind with the rows when the header pins and the
     header floats over them edgeless. A shadow is painted by the cell and rides
     along. The app's own reason, DataTableCore.tsx:1425-1431. */
  box-shadow: inset 0 -1px 0 var(--pfdt-border);
}

/* ── in-table cross-link cell — LinkCell.tsx:39 ─────────────────────────────
   ⚠️ TRANSLATED TIER, and it has to be. `.dt-link-cell`'s hover lives in
   globals.css (so app-verbatim.css carries it verbatim), but its REST colour is
   an INLINE style on the component — `style={{ color: 'var(--color-text)' }}` —
   and an inline style is invisible to a CSS mirror. Without this rule the cell
   falls through to the site's prose `a { color: var(--color-link) }`, which on
   the docs site is a BLUE, and a table of cross-links renders as a wall of blue.

   That is the exact outcome LinkCell's own header forbids ("the pointer and the
   underline are what carry clickable, not a paint color") and that
   link-color-drift.test.ts fences in the app. The app's --color-link is itself
   neutral (globals.css:208 → --color-text-body); the docs site's is not, which
   is why this cannot just inherit. */
.pfdt .dt-link-cell,
.pfdt a.dt-link-cell { color: var(--pfdt-text); text-decoration: none; }

/* ── row states — globals.css:2198-2210 ─────────────────────────────────────
   80ms is the app's own figure. Hover deliberately outranks selection there
   (0,1,1 vs 0,1,0), so the same order is kept here. NO ZEBRA STRIPING — the
   app has none, and rows are separated by the hairline alone. */
.pfdt tbody tr { transition: background 80ms ease; }
.pfdt tbody tr:hover { background: var(--pfdt-subtle); }
.pfdt tbody tr[aria-selected="true"] { background: var(--pfdt-overlay); }

/* A row that navigates says so. The app gets this from an onClick handler on
   the row; a static page has an anchor in the title cell instead, so the
   cursor is the only affordance the whole row can offer. */
.pfdt tbody tr:has(a[href]) { cursor: pointer; }

/* The sortable header is a control — the app sets cursor:pointer and
   user-select:none on it (DataTableCore.tsx:1432). Ours also carries
   data-sort, so bind to that rather than to aria-sort, which only exists
   AFTER the first click and left every header un-clickable-looking until
   somebody guessed. */
.pfdt th[data-sort] { cursor: pointer; user-select: none; }
/* No :hover recolour. The app's header stays --color-text-muted throughout;
   only the cursor says it is clickable (DataTableCore.tsx:1413 with nothing
   overriding it). A darkening header was ours alone. */

/* ── the tag menu — docs-only ────────────────────────────────────────────────
   The app anchors a popover to the pill's LEFT edge (so the anchor does not
   move as the value grows) using a React portal. A static page has no portal,
   so this is absolutely positioned against the pill row instead — same anchor
   rule, simpler machinery. */
.pfdt-pillrow { position: relative; }
.pfdt-menu {
  position: absolute; top: 34px; left: 0; z-index: 20;
  min-width: 14rem; max-height: 22rem; overflow-y: auto;
  padding: 4px;
  background: var(--pfdt-card);
  border: 1px solid var(--pfdt-border);
  border-radius: var(--pfdt-radius);
  box-shadow: 0 4px 16px rgba(0,0,0,.10), 0 1px 3px rgba(0,0,0,.06);
}
.pfdt-opt {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  width: 100%; padding: 5px 8px;
  border: 0; background: none; cursor: pointer;
  font: inherit; font-size: 12.5px; color: var(--pfdt-text);
  border-radius: 5px; text-align: left;
}
.pfdt-opt:hover { background: var(--pfdt-hoverbg); }
/* The checkmark is the app's single-select affordance — a selected row is
   marked, not merely tinted, because a tint alone disappears under hover. */
.pfdt-opt[aria-selected="true"]::after { content: '✓'; color: var(--pfdt-text); font-size: 11px; }
.pfdt-optn { color: var(--pfdt-faint); font-variant-numeric: tabular-nums; font-size: 11.5px; }
.pfdt-opt[aria-selected="true"] .pfdt-optn { margin-left: auto; }

