/* design-system/tokens/fonts.css */
/* Refdeck webfonts. No brand fonts were supplied; these are the Google Fonts
   chosen as the system's own voice. Swap the URL if real files arrive. */
/* design-system/tokens/colors.css */
:root {
  /* ---- Neutrals: warm paper, cool-green ink ---- */
  --paper:            #FBFAF8;
  --paper-tint:       #F4F2ED;
  --white:            #FFFFFF;
  --ink-1:            #161A19;
  --ink-2:            #454E4B;
  --ink-3:            #6E7873;
  --ink-4:            #98A19C;
  --line-1:           #E7E4DD;
  --line-2:           #D6D2C9;
  --line-3:           #BFBBB1;

  /* ---- Primary: deep teal. Trust, ledger-ink, not techy blue ---- */
  --teal-900:         #06322F;
  --teal-700:         #0C4A46;
  --teal-600:         #12615C;
  --teal-500:         #1B7A73;
  --teal-200:         #A8CFCA;
  --teal-100:         #DCEBE8;
  --teal-50:          #EEF5F3;

  /* ---- Secondary: bronze. Warm counterweight for referral/highlight ---- */
  --bronze-700:       #5E4526;
  --bronze-600:       #7C5A34;
  --bronze-500:       #9A7444;
  --bronze-200:       #E0CDB2;
  --bronze-100:       #F1E7D9;
  --bronze-50:        #F8F3EB;

  /* ---- Semantic hues (matched lightness/chroma to the primaries) ---- */
  --green-600:        #1F6B41;
  --green-100:        #DFEEE4;
  --amber-600:        #8A6410;
  --amber-100:        #F6EBD2;
  --red-600:          #A33228;
  --red-100:          #F6E3E0;
  --blue-600:         #2A5C8C;
  --blue-100:         #E1EAF3;

  /* ---- Semantic aliases: use THESE in components ---- */
  --bg-page:          var(--paper);
  --bg-sunken:        var(--paper-tint);
  --surface-card:     var(--white);
  --surface-raised:   var(--white);
  --surface-inverse:  var(--teal-900);
  --surface-soft:     var(--teal-50);
  --surface-hover:    #F2F1EC;

  --text-heading:     var(--ink-1);
  --text-body:        var(--ink-2);
  --text-muted:       var(--ink-3);
  --text-faint:       var(--ink-4);
  --text-on-dark:     #F2F5F4;
  --text-link:        var(--teal-600);
  --text-link-hover:  var(--teal-700);

  --border-subtle:    var(--line-1);
  --border-default:   var(--line-2);
  --border-strong:    var(--line-3);
  --border-focus:     var(--teal-600);

  --accent:           var(--teal-600);
  --accent-hover:     var(--teal-700);
  --accent-press:     var(--teal-900);
  --accent-soft:      var(--teal-100);
  --accent-quiet:     var(--teal-50);
  --accent-on:        #FFFFFF;

  --highlight:        var(--bronze-600);
  --highlight-soft:   var(--bronze-100);
  --highlight-quiet:  var(--bronze-50);

  --status-success:   var(--green-600);
  --status-success-bg:var(--green-100);
  --status-warning:   var(--amber-600);
  --status-warning-bg:var(--amber-100);
  --status-danger:    var(--red-600);
  --status-danger-bg: var(--red-100);
  --status-info:      var(--blue-600);
  --status-info-bg:   var(--blue-100);

  --focus-ring:       0 0 0 3px rgba(18, 97, 92, 0.22);
}
/* design-system/tokens/typography.css */
:root {
  --font-sans:    "Instrument Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
  --font-serif:   "Instrument Serif", "Iowan Old Style", Georgia, serif;
  --font-serif-alt: "Newsreader", "Iowan Old Style", Georgia, serif;
  --font-mono:    "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Scale — 1.25 major third at the top, tightening to 1.125 in UI sizes */
  --text-2xs:     11px;
  --text-xs:      12px;
  --text-sm:      13px;
  --text-base:    15px;
  --text-md:      17px;
  --text-lg:      20px;
  --text-xl:      24px;
  --text-2xl:     30px;
  --text-3xl:     38px;
  --text-4xl:     48px;
  --text-5xl:     62px;

  --leading-tight:   1.12;
  --leading-snug:    1.28;
  --leading-normal:  1.5;
  --leading-relaxed: 1.62;

  --weight-regular:  400;
  --weight-medium:   500;
  --weight-semibold: 600;

  --tracking-tight:  -0.02em;
  --tracking-snug:   -0.01em;
  --tracking-normal: 0;
  --tracking-wide:   0.04em;
  --tracking-caps:   0.09em;

  /* Semantic type roles */
  --type-display:   var(--weight-regular) var(--text-4xl)/var(--leading-tight) var(--font-serif);
  --type-title:     var(--weight-semibold) var(--text-2xl)/var(--leading-snug) var(--font-sans);
  --type-heading:   var(--weight-semibold) var(--text-lg)/var(--leading-snug) var(--font-sans);
  --type-subhead:   var(--weight-semibold) var(--text-md)/var(--leading-snug) var(--font-sans);
  --type-body:      var(--weight-regular) var(--text-base)/var(--leading-relaxed) var(--font-sans);
  --type-body-sm:   var(--weight-regular) var(--text-sm)/var(--leading-normal) var(--font-sans);
  --type-label:     var(--weight-medium) var(--text-sm)/1.3 var(--font-sans);
  --type-button:    var(--weight-semibold) var(--text-sm)/1 var(--font-sans);
  --type-meta:      var(--weight-medium) var(--text-2xs)/1.3 var(--font-mono);
}
/* design-system/tokens/spacing.css */
:root {
  --space-0:  0px;
  --space-1:  2px;
  --space-2:  4px;
  --space-3:  6px;
  --space-4:  8px;
  --space-5:  12px;
  --space-6:  16px;
  --space-7:  20px;
  --space-8:  24px;
  --space-9:  32px;
  --space-10: 40px;
  --space-11: 48px;
  --space-12: 64px;
  --space-13: 80px;
  --space-14: 112px;

  --radius-xs:   3px;
  --radius-sm:   5px;
  --radius-md:   8px;
  --radius-lg:   12px;
  --radius-xl:   16px;
  --radius-pill: 999px;

  --border-width: 1px;

  /* Layout */
  --page-max:      1200px;
  --prose-max:     68ch;
  --deck-max:      1040px;
  --sidebar-w:     248px;
  --topbar-h:      60px;
  --control-h-sm:  30px;
  --control-h-md:  38px;
  --control-h-lg:  46px;
  --tap-min:       44px;
}
/* design-system/tokens/elevation.css */
:root {
  /* Elevation is quiet: a hairline first, shadow only where something floats. */
  --shadow-none: none;
  --shadow-xs:   0 1px 1px rgba(22, 26, 25, 0.04);
  --shadow-sm:   0 1px 2px rgba(22, 26, 25, 0.05), 0 1px 1px rgba(22, 26, 25, 0.03);
  --shadow-md:   0 1px 2px rgba(22, 26, 25, 0.05), 0 8px 18px -10px rgba(22, 26, 25, 0.14);
  --shadow-lg:   0 2px 4px rgba(22, 26, 25, 0.05), 0 18px 40px -18px rgba(22, 26, 25, 0.20);
  --shadow-inset:inset 0 1px 0 rgba(255, 255, 255, 0.6);
  --scrim:       rgba(22, 26, 25, 0.42);
  --blur-veil:   saturate(1.4) blur(10px); /* @kind other */
  /* The picture behind a branded band, when one is uploaded. It is a token
     rather than a per-page style attribute so the band component can be
     written once against a name, and "none" is the default every surface
     renders with until an owner uploads something. Sits beside --scrim
     because the two are always used together: the scrim is what keeps text
     legible over whatever this turns out to be. */
  --band-image:  none;                     /* @kind other */
  /* Where that picture's people are, as a background-position, for a band
     too narrow to show all of a wide picture. Center unless a page that
     knows better says otherwise (a deck's gallery scene). */
  --band-focus:  center;                   /* @kind other */
}
/* design-system/tokens/motion.css */
:root {
  --dur-instant: 80ms; /* @kind other */
  --dur-fast:    140ms; /* @kind other */
  --dur-base:    200ms; /* @kind other */
  --dur-slow:    320ms; /* @kind other */
  --ease-out:    cubic-bezier(0.22, 0.61, 0.36, 1); /* @kind other */
  --ease-in-out: cubic-bezier(0.45, 0.05, 0.55, 0.95); /* @kind other */
  --ease-entrance: cubic-bezier(0.16, 0.84, 0.44, 1); /* @kind other */
  --transition-control: background-color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out);
}
/* design-system/tokens/base.css */
*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg-page);
  color: var(--text-body);
  font: var(--type-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, h5, h6 { margin: 0; color: var(--text-heading); letter-spacing: var(--tracking-snug); }
p { margin: 0; text-wrap: pretty; }

a { color: var(--text-link); text-decoration: none; }
a:hover { color: var(--text-link-hover); text-decoration: underline; text-underline-offset: 2px; }

::selection { background: var(--accent-soft); color: var(--teal-900); }

:focus-visible { outline: 2px solid var(--border-focus); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}
/* assets/shell.css */
/* Refdeck page shell — the app-owned layer that sits on top of the imported
   design-system tokens (design-system/tokens/*.css, concatenated ahead of
   this file into the same served stylesheet).

   SCOPE, deliberately narrow: this file styles the page FRAME only — the
   container width, and the vertical rhythm a page of plain semantic markup
   needs. Styling the signed-in app's own components (tables, forms, cards,
   nav) is separate work. Do not grow this file into a component library:
   read design-system/SKILL.md and put component CSS with the markup that
   needs it.

   Every value below is a token. If a literal is ever tempting here, the token
   set is missing something — add it in design-system/ instead. */

/* The default shell, applied by templates/layout.html to every page that does
   not opt out. Public deck pages opt out (they run full-bleed colour bands to
   the viewport edge) by overriding the layout's body_class block. */
.app-shell {
  padding: var(--space-9) var(--space-6) var(--space-12);
}

.app-shell > main {
  max-width: var(--page-max);
  margin-inline: auto;
}

/* [hidden] is a UA rule at specificity (0,1,0) and loses to any class that
   sets display — .rd-formnote (display:flex), .rd-avatar, .rd-dialog. The
   image uploader renders its error note on EVERY load and keeps it hidden
   until it has something to say, so without this the signed-in settings
   screens greeted a member with a blank red box under "Your picture". The
   public page and the auth card each carry the same rule for the same tie;
   this is the signed-in shell's, and the admin shell's, so nothing inside
   either has to remember it. !important because a compound selector further
   down the cascade (.rd-field .rd-input__control) would out-rank a plain
   descendant rule, and "hidden" has to mean hidden regardless. */
.app-shell [hidden],
.admin-shell [hidden] {
  display: none !important;
}

/* design-system/tokens/base.css zeroes the margins on p and h1-h6, on the
   assumption that components own their own spacing. The app's pages are still
   plain semantic markup that relied on the browser's default margins, so the
   shell restores a rhythm — in tokens. Without this, linking the stylesheet
   would collapse every app page into a wall of text: a regression caused by
   the reset, not a restyle of the app. */
.app-shell :is(p, ul, ol, dl, table, form, section, nav, blockquote) {
  margin-block: var(--space-5);
}

.app-shell :is(h1, h2, h3, h4) {
  margin-block: var(--space-8) var(--space-5);
}

.app-shell > main > :first-child {
  margin-top: 0;
}

/* ─── The signed-in app shell ────────────────────────────────────────────
   Chrome for every /app screen: a fixed top bar carrying the global
   quick-search field, and the primary navigation. The markup is the
   layout's "app_chrome" template.

   ONE nav, TWO presentations. The sidebar and the mobile tab bar are the
   same <nav class="rd-nav"> re-laid by the media query below, not two
   blocks of markup behind a device check. Two blocks is how the desktop
   nav gains an item the mobile one never gets, and how one of them stops
   marking the current page.

   Both the top bar and the nav are FIXED, which is what lets any page
   wear this chrome without a wrapper element or a body class: the layout
   cannot branch on whether a page is an /app page (Go templates cannot
   test a block's output), so the pages opt in by invoking the template,
   and the sibling combinator below is what makes room for it.

   The values are tokens, per this file's rule, with one documented
   exception: icon pixel sizes. The design system has no icon scale —
   components/core/Icon.jsx takes a numeric `size` prop, so 16 / 17 / 19
   below are the exact numbers SidebarNav, SearchField and BottomNav pass
   it. Adding an icon token would mean editing design-system/, which this
   PRD requires to stay byte-identical to the prototype's bundle. */

.rd-topbar {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 20;
  height: var(--topbar-h);
  display: flex;
  align-items: center;
  gap: var(--space-6);
  padding: 0 var(--space-7);
  background: var(--surface-card);
  border-bottom: var(--border-width) solid var(--border-subtle);
}

.rd-topbar__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
  font-family: var(--font-serif);
  font-size: var(--text-lg);
  color: var(--ink-1);
  text-decoration: none;
  flex: 0 0 auto;
}

/* The mark holds its size while the bar tightens. Everything else up here
   gives way before the brand does — the search field has a max-width and the
   account name ellipses — so a shrinking mark would be the first thing to go
   blurry for no benefit. Sized by the tag's width/height, like the nav
   icons, so the reserved box and the painted box cannot disagree. */
.rd-topbar__mark {
  flex: 0 0 auto;
}

/* The global quick search. It is a real GET form aimed at the dashboard's
   universal search, not a decorative box: /app?q= is the one place that
   searches contacts, places and tags together. */
.rd-quicksearch {
  flex: 1 1 auto;
  min-width: 0;
  max-width: var(--prose-max);
  display: flex;
  align-items: center;
  gap: var(--space-5);
  height: var(--control-h-md);
  padding: 0 var(--space-6);
  margin-block: 0;
  background: var(--surface-card);
  border: var(--border-width) solid var(--border-default);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-xs);
  transition: var(--transition-control);
}

.rd-quicksearch:focus-within {
  border-color: var(--border-focus);
  box-shadow: var(--focus-ring);
}

.rd-quicksearch__icon {
  flex: 0 0 auto;
  color: var(--text-muted);
}

.rd-quicksearch__input {
  flex: 1;
  min-width: 0;
  border: none;
  outline: none;
  background: transparent;
  font: var(--type-body);
  color: var(--text-heading);
}

/* The account block: who is signed in, from any screen. It sits at the far
   end of the bar on its own auto margin rather than on a spacer, because the
   quick search between it and the brand grows to a max-width and then stops.

   The element is a <details> and these are the styles a menu keeps rather
   than ones it has to undo: the disclosure layer below adds to them, and the
   name and hat inside are untouched by it.

   position:relative at every width, because the dropped panel is absolutely
   positioned against it at every width. It used to be relative only inside
   the media query, back when the menu lay open along the bar above the
   breakpoint and had nothing to hang from. */
.rd-account {
  position: relative;
  flex: 0 1 auto;
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-width: 0;
}

/* A long name gives way before the search field does: the field is the one
   thing on this bar a member came here to use. */
.rd-account__name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: var(--type-body);
  color: var(--text-heading);
}

/* The same pill the admin bar wears, so which hat is on reads identically
   whichever surface you are standing on. */
.rd-account__hat {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  padding: var(--space-1) var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--highlight);
  color: var(--accent-on);
  font: var(--type-meta);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}

/* The user icon. It is the design system's Avatar at circle/sm, so all this
   layer adds is the promise that it never shrinks: under the breakpoint it is
   the ONLY thing left of the trigger, and an avatar that gave way there would
   leave a control with nothing in it. */
.rd-account__avatar {
  flex: 0 0 auto;
}

/* The control's accessible name, present at both widths and visible at
   neither. Under the breakpoint the name and the caret are display:none —
   which takes them out of the accessibility tree — so without this the
   trigger would be an icon-only control announcing nothing at all.

   Same recipe as .rd-inbox__unread in admin.css, which hides the word behind
   the operator's unread dot for the same reason. */
.rd-account__label {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The caret is the affordance that says this row opens. It is the one part of
   the trigger that is purely about the disclosure, so it is also the first
   thing dropped when the bar runs out of room. */
.rd-account__caret {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  color: var(--text-muted);
}

/* A shut disclosure hides its own contents, and engines spell that hiding two
   different ways: the older one puts display none on the child, Chrome 131 and
   later put content-visibility hidden on the details-content slot. Both are
   stated, at both widths, because the menu is now genuinely shut until it is
   opened — spelling only one of them would leave it hanging open on half the
   browsers in use, and no Go test can see that: the stylesheet is served
   either way. */
.rd-account:not([open]) > .rd-accountmenu {
  display: none;
}

.rd-account:not([open])::details-content {
  content-visibility: hidden;
}

/* The trigger is the row that CARRIES the avatar, the name and the hat.

   It is a control at BOTH widths now — the menu no longer lies open along the
   bar above the breakpoint — so the cursor and the hover state live here in
   the base rules rather than in the media query, where they used to apply
   only to the narrow layout.

   The marker rules keep the disclosure from drawing a triangle beside the
   name at either width. */
.rd-account__trigger {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-width: 0;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-sm);
  list-style: none;
  cursor: pointer;
  transition: var(--transition-control);
}

.rd-account__trigger::-webkit-details-marker {
  display: none;
}

.rd-account__trigger:hover {
  background: var(--surface-hover);
}

.rd-account__trigger:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

/* The account menu inside the disclosure: the actions, stacked in a panel
   that drops OUT of the bar rather than growing it. The top bar is a fixed
   --topbar-h, so a menu that made it taller would push every page's content
   down behind it and leave the first heading unreadable.

   Right-aligned because the block it hangs from is at the right end of the
   bar; the nav's menu at the other end hangs left, for the same reason. */
.rd-accountmenu {
  position: absolute;
  top: 100%;
  right: 0;
  z-index: 30;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-2);
  min-width: var(--sidebar-w);
  margin-block-start: var(--space-4);
  padding: var(--space-4);
  background: var(--surface-card);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
}

.rd-accountmenu__action {
  display: inline-flex;
  align-items: center;
  height: var(--control-h-sm);
  padding: 0 var(--space-5);
  border-radius: var(--radius-sm);
  font: var(--weight-medium) var(--text-sm)/1 var(--font-sans);
  color: var(--text-body);
  text-decoration: none;
  white-space: nowrap;
  transition: var(--transition-control);
}

.rd-accountmenu__action:hover {
  background: var(--surface-hover);
  color: var(--text-heading);
}

.rd-accountmenu__action:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

/* The nav's disclosure. Above the breakpoint it contributes NO BOX AT ALL —
   display:contents — and that is deliberate rather than tidy: the top bar is a
   flex row with a gap, so an empty wrapper would still open a gap's worth of
   space where a hamburger is not, shunting the wordmark off the bar's left
   edge. The nav inside is position:fixed, which resolves against the viewport
   and not against this element, so removing the box costs the sidebar
   nothing. */
.rd-navmenu {
  display: contents;
}

/* No hamburger above the breakpoint: the sidebar is already on screen, and a
   burger that collapsed nothing would be a control with no effect. Hiding the
   TRIGGER rather than the disclosure is what keeps the nav inside it visible.

   The marker rules keep the disclosure from drawing a triangle at either
   width. */
.rd-navmenu__trigger {
  display: none;
  list-style: none;
}

.rd-navmenu__trigger::-webkit-details-marker {
  display: none;
}

/* 19px per this file's documented icon-pixel exception: the burger only ever
   shows under the breakpoint, so it is sized to the nav icons it opens. */
.rd-navmenu__burger {
  flex: 0 0 auto;
  width: 19px;
  height: 19px;
}

/* A shut <details> hides its own contents in two spellings, and above the
   breakpoint BOTH have to be undone or the sidebar is gone on half the
   browsers in use — the same pair, for the same reason, as the account block
   above. Undoing only one is invisible to a Go test: the stylesheet is served
   either way. */
.rd-navmenu::details-content {
  content-visibility: visible;
}

.rd-navmenu > .rd-nav {
  display: flex;
}

.rd-nav {
  position: fixed;
  top: var(--topbar-h);
  left: 0;
  bottom: 0;
  z-index: 10;
  width: var(--sidebar-w);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-6);
  margin-block: 0;
  overflow-y: auto;
  background: var(--bg-sunken);
  border-right: var(--border-width) solid var(--border-subtle);
}

.rd-nav__item {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  min-height: var(--control-h-md);
  padding: 0 var(--space-5);
  border: var(--border-width) solid transparent;
  border-radius: var(--radius-sm);
  color: var(--text-body);
  font: var(--weight-medium) var(--text-sm)/1 var(--font-sans);
  text-decoration: none;
  transition: var(--transition-control);
}

.rd-nav__icon {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  color: var(--text-muted);
}

.rd-nav__item:hover {
  background: var(--surface-hover);
  color: var(--text-heading);
}

.rd-nav__item:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-color: var(--border-focus);
}

.rd-nav__item[aria-current="page"] {
  background: var(--surface-card);
  border-color: var(--border-subtle);
  box-shadow: var(--shadow-xs);
  color: var(--text-heading);
}

.rd-nav__item[aria-current="page"] .rd-nav__icon {
  color: var(--accent);
}

/* The nav is out of flow, so the page's own <main> is what makes room for
   it. Matching on the sibling keeps this scoped to pages that actually
   invoked the chrome — a page without it is untouched.

   The sibling is the TOP BAR rather than the nav, because the nav is no
   longer a sibling of anything: it moved inside the bar's <details> so the
   hamburger could collapse it. The bar is the equally reliable marker — the
   two render together or not at all, from the same template. */
.app-shell > .rd-topbar ~ main {
  margin-block: var(--topbar-h) 0;
  margin-inline: var(--sidebar-w) auto;
}

/* ─── Mobile: the bar grows a hamburger, the nav becomes its dropdown ───
   720px is the design system's own breakpoint
   (ui_kits/dashboard/responsive.js sets REFDECK_MOBILE_MAX), and a test
   reads it from that file so the two cannot drift.

   The nav USED to become a bottom tab bar here. Two things were wrong with
   that once the bar grew a second disclosure: a fixed strip along the bottom
   costs every screen a band of height on the device that has least of it,
   and eight sections in a row that fits seven is how the last tab ends up
   marked current and off-screen. The hamburger costs one tap and no height,
   and it holds all eight at their full sidebar size.

   The nav's own item styles are therefore NOT overridden below: the dropdown
   is the sidebar, re-anchored. That is the point — one nav, one set of item
   styles, and no second presentation to keep in step. */
@media (max-width: 720px) {
  .rd-topbar {
    padding: 0 var(--space-6);
    gap: var(--space-5);
  }

  /* The disclosure becomes a real box again, and a positioning context: the
     panel below hangs from it. It is FIRST in the bar, so the panel hangs
     from the leading edge — the account's hangs from the trailing one. */
  .rd-navmenu {
    position: relative;
    display: flex;
    flex: 0 0 auto;
    align-items: center;
  }

  .rd-navmenu__trigger {
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: var(--tap-min);
    min-height: var(--tap-min);
    border-radius: var(--radius-sm);
    color: var(--text-body);
    cursor: pointer;
    transition: var(--transition-control);
  }

  .rd-navmenu__trigger:hover {
    background: var(--surface-hover);
    color: var(--text-heading);
  }

  .rd-navmenu__trigger:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
  }

  /* Behind the hamburger, in both spellings — the mirror of the pair the
     base rules undo. */
  .rd-navmenu:not([open]) > .rd-nav {
    display: none;
  }

  .rd-navmenu:not([open])::details-content {
    content-visibility: hidden;
  }

  /* Opened, the nav drops OUT of the bar rather than growing it: the top bar
     is a fixed --topbar-h, so a panel that made it taller would push every
     page's content down behind it.

     It keeps the sidebar's own column layout and item styles and changes only
     where it is anchored — fixed-to-the-viewport becomes absolute-to-the-
     disclosure. The max-height is what keeps all eight sections reachable on
     a short phone in landscape: the panel scrolls, the page behind it does
     not have to. */
  .rd-navmenu > .rd-nav {
    position: absolute;
    top: 100%;
    left: 0;
    right: auto;
    bottom: auto;
    z-index: 30;
    width: max-content;
    min-width: var(--sidebar-w);
    max-height: calc(100vh - var(--topbar-h) - var(--space-8));
    margin-block-start: var(--space-4);
    padding: var(--space-4);
    background: var(--surface-card);
    border: var(--border-width) solid var(--border-subtle);
    border-right: var(--border-width) solid var(--border-subtle);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-md);
  }

  /* The account block keeps its dropdown and loses everything beside the
     icon. The name, the hat and the caret all go: at this width the bar has
     room for the hamburger, the brand, the search and one 28px circle, and
     the icon is the part that identifies the account without spelling it. */
  .rd-account__name,
  .rd-account__hat,
  .rd-account__caret {
    display: none;
  }

  /* What is left of the trigger once they are gone — a tap target around the
     avatar, at the size the design system asks for. */
  .rd-account__trigger {
    justify-content: center;
    min-width: var(--tap-min);
    min-height: var(--tap-min);
    padding: 0;
  }

  /* No sidebar to clear, and no tab bar to sit above: the nav is a dropdown
     now and takes no room in the layout at all. */
  .app-shell > .rd-topbar ~ main {
    margin-inline: auto;
  }
}

/* The nav label is a real element rather than a bare text node so it can be
   constrained. It has to be: "Businesses" is long enough to wrap the mobile
   tab bar onto a second line, and one wrapped tab is taller than its
   neighbours, which shunts the row's icons out of alignment. Ellipsis rather
   than wrap keeps every tab the same height at any width. */
.rd-nav__label {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
/* assets/admin.css */
/* Refdeck admin shell — the site-operator hat.

   WHY THIS IS ITS OWN LAYER. docs/SITE-STRUCTURE.md §3: "Same layout system as
   the app but visually distinct (e.g., darker shell or an 'Admin' badge) so
   it's always obvious which hat you're wearing." The admin surface and the
   signed-in app are the same person with different powers, and until now they
   rendered identically — an operator with two tabs open could not tell which
   one was about to disable an account. So this file does both things the doc
   offers rather than either: the chrome is dark, and it says Admin on it.

   It is a SHELL, not a screen restyle. The admin tables, forms and filters
   below the chrome are their own criteria (PRD 377, phase P3); this layer
   stops at the frame and the navigation, exactly as assets/shell.css stops at
   the frame for the app.

   WHY NOT A .app-shell MODIFIER. The two shells are meant to diverge — that is
   the whole criterion — so .admin-shell carries its own frame rather than
   inheriting one it would then have to override. The duplication below is four
   rules, and it buys a surface that cannot be changed by accident from the
   other shell's file.

   ORDER. Concatenated AFTER assets/shell.css and BEFORE assets/components.css
   — see appAdminCSS in assets.go. The middle slot is load-bearing: the flow-
   margin restoration below lands at the same specificity (0,1,1) as the
   component layer's reset of it, so the tie is broken by document order. Ship
   this file after the components and every card inside admin quietly regains
   the outer margins that reset was there to remove.

   THE VALUE RULE, as everywhere: colour, type, radius, elevation and motion
   come from design-system/tokens/ and nowhere else. */

/* ---- The frame ----------------------------------------------------------
   The chrome runs full-bleed to the viewport edge; only the page content is
   held to the reading width. */
.admin-shell {
  background: var(--bg-page);
}

.admin-shell > main {
  max-width: var(--page-max);
  margin-inline: auto;
  padding: var(--space-9) var(--space-6) var(--space-12);
}

/* design-system/tokens/base.css zeroes the margins on p and h1-h6 on the
   assumption that components own their own spacing. Admin screens are still
   plain semantic markup, so the shell restores a rhythm — in tokens.
   Unlike .app-shell this list omits `nav`, because the admin nav is chrome
   sitting outside main and spacing it here would push it off the bar. */
.admin-shell :is(p, ul, ol, dl, table, form, section, blockquote) {
  margin-block: var(--space-5);
}

.admin-shell :is(h1, h2, h3, h4) {
  margin-block: var(--space-8) var(--space-5);
}

.admin-shell > main > :first-child {
  margin-top: 0;
}

/* ---- The bar ------------------------------------------------------------
   The inverse surface is the design system's own dark ground, so the "darker
   shell" the site structure asks for is a token rather than a new shade. */
.rd-adminbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-6);
  min-height: var(--topbar-h);
  padding-inline: var(--space-6);
  background: var(--surface-inverse);
  color: var(--text-on-dark);
}

.rd-adminbar__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
  font: var(--type-heading);
  color: inherit;
  text-decoration: none;
}

/* The same mark the app bar wears, holding its size for the same reason. */
.rd-adminbar__mark {
  flex: 0 0 auto;
}

/* The badge. A dark bar alone is a mood; the word is what makes the hat
   unambiguous, so it is set in the mono meta face and given the bronze
   highlight — the one colour the app chrome never uses. */
.rd-adminbar__hat {
  display: inline-flex;
  align-items: center;
  padding: var(--space-1) var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--highlight);
  color: var(--accent-on);
  font: var(--type-meta);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}

.rd-adminbar__exit {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  font: var(--type-label);
  color: var(--text-on-dark);
  text-decoration: none;
}

.rd-adminbar__exit:hover {
  text-decoration: underline;
}

/* ---- The navigation -----------------------------------------------------
   One row, wrapping rather than scrolling, sharing the bar's dark ground so
   the whole top block reads as a single piece of chrome. */
.rd-adminnav {
  display: flex;
  flex-wrap: wrap;
  padding-inline: var(--space-4);
  background: var(--surface-inverse);
  box-shadow: var(--shadow-md);
}

.rd-adminnav__item {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
  padding-inline: var(--space-5);
  /* 3px: an underline weight, not a border — the token scale has no step for
     it, and --border-width would read as a hairline rather than a marker. */
  border-bottom: 3px solid transparent;
  font: var(--type-label);
  color: var(--text-on-dark);
  text-decoration: none;
  transition: var(--transition-control);
}

.rd-adminnav__item:hover {
  background: var(--accent-hover);
}

/* Where you are. Without it every admin screen looks like every other admin
   screen, which is most of what "which hat am I wearing" is really asking on
   a page of tables. */
.rd-adminnav__item[aria-current="page"] {
  background: var(--accent-hover);
  border-bottom-color: var(--highlight);
  font-weight: var(--weight-semibold);
}

.rd-adminbar__brand:focus-visible,
.rd-adminbar__exit:focus-visible,
.rd-adminnav__item:focus-visible {
  outline: 2px solid var(--highlight-soft);
  outline-offset: -2px;
}

/* ---- The admin screens --------------------------------------------------
   Everything above is the frame. From here down is the surface INSIDE it, and
   the first screen to need it is Groups.

   WHY A TABLE AND NOT CARDS. The app surface lists groups as cards because a
   member is choosing one of the two or three they belong to. An operator is
   reading a register: how many members, created when, compared down the
   column across every chapter on the site. Cards make that comparison harder.
   So the admin list stays a table and the table gets designed — which is also
   what the users, invitations and email-log screens will each want, so this
   block is written to be theirs too.

   HEADS AND PANELS ARE NOT REDEFINED HERE. .rd-pagehead and .rd-panel already
   exist in assets/app.css and are already the right shapes. The served
   stylesheet is one file, so the admin screens simply use them: the roster
   panel on a group's drill-in renders identically to the same roster on the
   member-facing group home, which is the correct outcome — one list of the
   same people should not read differently because of which hat is looking. */

/* THE COUNTERPART TO THE FRAME'S FLOW RULE, which .app-shell has had in
   assets/app.css since the app screens were ported and the admin shell never
   got. An element carrying a design-system class already spaces its own
   children — .rd-pagehead gaps, .rd-panel gaps, .rd-empty-state pads — so the
   restored flow margin lands ON TOP of that spacing and every designed block
   is spaced twice. It showed the moment the SAME .rd-pagehead was rendered
   under both shells: tight on /app/groups, and an extra --space-5 loose on
   every admin screen.

   The descendant selector is what makes it stick. The restoration above is
   (0,1,1), so a block's own margin-block:0 at (0,1,0) loses however late its
   layer ships — the one case where document order does not save you. This is
   (0,2,1) and wins. The attribute pair is the BEM-safe way to say "a class
   starting with rd-": *= alone would also match a class merely containing it.

   Screens still in plain semantic markup are untouched, because they carry no
   rd- class to match.

   It sits down here with the screens rather than up beside the rule it
   answers, so that everything this bead adds to the file is one contiguous
   block — the admin shell above is another bead's, and two workers appending
   to the same section is how a clean merge turns into 25 conflicts. */
.admin-shell :is(p, h1, h2, h3, h4, ul, ol, dl, table, form, section, blockquote)[class^="rd-"],
.admin-shell :is(p, h1, h2, h3, h4, ul, ol, dl, table, form, section, blockquote)[class*=" rd-"] {
  margin-block: 0;
}

/* The scroll container carries the card, not the table: a border-radius on a
   table does not clip its own cells reliably, and this needs the overflow
   anyway so a narrow viewport scrolls the columns instead of the page. */
.rd-admintable__scroll {
  overflow-x: auto;
  background: var(--surface-card);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-xs);
}

/* No margin here: the shell rule above already takes the restored flow margin
   back off any rd- block, which is what keeps the table flush inside its
   card. */
.rd-admintable {
  width: 100%;
  border-collapse: collapse;
}

.rd-admintable thead th {
  padding: var(--space-4) var(--space-5);
  background: var(--bg-sunken);
  border-bottom: var(--border-width) solid var(--border-default);
  font: var(--type-meta);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
  text-align: left;
  white-space: nowrap;
}

.rd-admintable tbody th,
.rd-admintable tbody td {
  padding: var(--space-5);
  border-top: var(--border-width) solid var(--border-subtle);
  font: var(--type-body-sm);
  color: var(--text-body);
  text-align: left;
  vertical-align: baseline;
}

.rd-admintable tbody tr:first-child th,
.rd-admintable tbody tr:first-child td {
  border-top: none;
}

.rd-admintable tbody tr:hover {
  background: var(--surface-hover);
}

/* Every modifier below is scoped to the block. The cell rules above are
   (0,1,2) and set colour, weight and alignment through a font shorthand, so a
   bare modifier class at (0,1,0) would be overridden by them and silently do
   nothing — the same specificity trap the flow-margin note describes. */
.rd-admintable .rd-admintable__rowhead {
  font-weight: var(--weight-semibold);
}

.rd-admintable .rd-admintable__link {
  color: var(--text-link);
  font-weight: var(--weight-semibold);
  text-decoration: none;
  transition: var(--transition-control);
}

.rd-admintable .rd-admintable__link:hover {
  color: var(--text-link-hover);
  text-decoration: underline;
}

.rd-admintable .rd-admintable__link:focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: 2px;
}

/* Counts line up under one another so two chapters can be compared down the
   column rather than read one at a time. */
.rd-admintable .rd-admintable__num {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.rd-admintable .rd-admintable__meta {
  color: var(--text-muted);
  white-space: nowrap;
}

/* ---- Per-record controls ------------------------------------------------
   A row of one-button forms sitting together in a table cell or under a
   drill-in. Each control is its own form because each posts to its own route
   — the route IS the direction of the change — so they need a flex row to
   read as one group of choices rather than a stack. wrap so a narrow column
   breaks between buttons instead of overflowing the cell. */
.rd-adminactions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

/* The operator's own row, marked. Disabling your own account ends your
   session, and this screen offers that button beside everyone else's — so the
   register says which one is yours. Set in the mono meta face like the other
   identifiers here, and deliberately quiet: it is a label, not a warning. */
.rd-adminuserrow__you {
  display: inline-block;
  margin-left: var(--space-3);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-xs);
  background: var(--bg-sunken);
  font: var(--type-meta);
  color: var(--text-muted);
  vertical-align: middle;
}

/* A slug is an identifier, not prose, and --type-meta is the mono face. */
.rd-adminslug {
  display: inline-block;
  padding: var(--space-1) var(--space-3);
  background: var(--bg-sunken);
  border-radius: var(--radius-xs);
  font: var(--type-meta);
  color: var(--text-muted);
}

/* ---- The owner column ---------------------------------------------------
   docs/SITE-STRUCTURE.md §3.3 asks the register for name, OWNER, members and
   created. The cell is a link to /admin/users/{id}, because the question after
   "who runs this chapter" is always "what is that account", and that is the
   screen holding the answer.

   Deliberately NOT .rd-admintable__link, which is the same colour at
   --weight-semibold. That weight is what makes the group name read as the
   row's subject; a second semibold link beside it would set the owner
   competing with it for the eye, and the owner is the row's attribute, not
   its subject. Same hue, ordinary weight — the hierarchy does the talking.

   Scoped inside .rd-admintable for the specificity reason the block above
   states: the (0,1,2) cell rules set colour, so a bare (0,1,0) modifier here
   would parse, validate, and do nothing. */
.rd-admintable .rd-adminowner {
  white-space: nowrap;
}

.rd-admintable .rd-adminowner__link {
  color: var(--text-link);
  text-decoration: none;
  transition: var(--transition-control);
}

.rd-admintable .rd-adminowner__link:hover {
  color: var(--text-link-hover);
  text-decoration: underline;
}

.rd-admintable .rd-adminowner__link:focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: 2px;
}

/* Words, not an em dash. A chapter with no owner-ranked member is a state an
   operator has to ACT on, and "—" in a register is read as "nothing to say
   here" — the same mark a column uses for a value that is merely absent. */
.rd-admintable .rd-adminowner__none {
  color: var(--text-muted);
  font-style: italic;
}

/* ---- A record's facts ---------------------------------------------------
   The short row of identifying values under a drill-in's title: what this
   record IS, before the detail below it. Its outer spacing comes from the
   .rd-workspace column gap the screen sits in, like every other block. */
.rd-adminfacts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4) var(--space-9);
  padding: var(--space-5) var(--space-6);
  background: var(--surface-soft);
  border-radius: var(--radius-md);
}

/* Inside the sunken facts strip the slug chip's own ground is redundant, and
   its padding pushes the value out of line with the term above it. */
.rd-adminfacts .rd-adminslug {
  padding-inline: 0;
  background: none;
}

.rd-adminfacts__pair {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.rd-adminfacts__term {
  font: var(--type-meta);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* dd carries a browser default indent that has nothing to do with this
   layout. */
.rd-adminfacts__value {
  margin: 0;
  font: var(--type-body);
  color: var(--text-heading);
}

/* ---- The account block --------------------------------------------------
   The shared chrome, re-inked for a dark ground.

   assets/shell.css styles the disclosure with UNSCOPED selectors, so the
   whole of it — the trigger, the dropdown, the collapse at the design
   system's breakpoint — arrives here already working. What does NOT arrive is
   the colour: every one of those rules is written for the app's light bar, so
   without the overrides below the operator's own name renders in
   --text-heading on --surface-inverse and is simply not on the screen.

   TWO GROUNDS, and the line between them is the disclosure. The TRIGGER sits
   on the dark bar and takes the bar's ink. The opened MENU drops off the bar
   onto a light --surface-card panel (assets/shell.css) and takes the app's.
   That split used to be a ≤720px affair, because above the breakpoint the
   menu lay open along the bar itself; it is a dropdown at every width now, so
   the split applies throughout and the media query that used to carry it is
   gone. Getting this backwards paints white on white — present, styled, and
   invisible, which nothing reading the stylesheet as text can see.

   The hat is deliberately not restated here. The badge beside the wordmark
   above already wears it, and it is the one that survives the collapse —
   see WearsOperatorPill in internal/web/chrome.go. */
.rd-adminbar .rd-account__name {
  color: var(--text-on-dark);
}

/* The trigger is a control on the dark bar at BOTH widths now, so its ink and
   its hover are base rules rather than narrow-only ones. */
.rd-adminbar .rd-account__trigger {
  color: var(--text-on-dark);
}

.rd-adminbar .rd-account__trigger:hover {
  background: var(--accent-hover);
  color: var(--text-on-dark);
}

/* The caret would otherwise keep --text-muted, which is a grey drawn to sit
   quietly on the app's light bar and reads as nearly nothing on this one. */
.rd-adminbar .rd-account__caret {
  color: var(--text-on-dark);
}

/* The dropped panel is light, so its actions take the app's ink — see the
   two-grounds note above. */
.rd-adminbar .rd-accountmenu__action {
  color: var(--text-body);
}

.rd-adminbar .rd-accountmenu__action:hover {
  background: var(--surface-hover);
  color: var(--text-heading);
}

/* The bar's own focus treatment on the TRIGGER, matching its brand, exit and
   nav items rather than the app's ring — that ring is drawn for a light
   ground. The menu's actions keep the app's ring, because they are on the
   app's surface. */
.rd-adminbar .rd-account__trigger:focus-visible {
  outline: 2px solid var(--highlight-soft);
  outline-offset: -2px;
  box-shadow: none;
}

/* ---- Overview dashboard ----------------------------------------------
   The four tiles and the activity feed at /admin. Appended as one
   contiguous block for the reason the escape-hatch rule above gives: the
   sections higher up belong to other beads, and two workers appending into
   the same section is how a clean merge turns into a wall of conflicts.

   Every rule here relies on that escape hatch. These tiles are built from
   <p> elements carrying rd- classes, so the restored .admin-shell flow
   margins are already taken back off them and spacing is the flex gap
   below — a bare block class would have LOST that tie on specificity. */
.rd-stattiles {
  display: grid;
  /* auto-fit collapses the track count as the shell narrows, so four tiles
     become two and then one without a breakpoint of their own. */
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 11rem), 1fr));
  gap: var(--space-6);
}

/* A tile is a LINK to the records it counts. It is styled as the card it
   sits in rather than as a button: an operator reads these four numbers
   before deciding anything, and four buttons would ask to be pressed. */
.rd-stattile {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-6);
  background: var(--surface-card);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-xs);
  color: inherit;
  text-decoration: none;
  transition: var(--transition-control);
}

.rd-stattile:hover {
  border-color: var(--border-default);
  background: var(--surface-hover);
}

.rd-stattile:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

/* The number is the point of the tile, so it is the one thing set at display
   size — the label underneath explains it and should not compete. */
.rd-stattile__value {
  font: var(--type-display);
  color: var(--text-heading);
}

.rd-stattile__label {
  font: var(--type-body);
  color: var(--text-heading);
}

.rd-stattile__note {
  font: var(--type-meta);
  color: var(--text-muted);
}

/* ---- Deck themes ----
   The theme table is the register's table with one difference: the family
   and motion columns are words a theme is described by, not counts, and
   they read better in lowercase than shouting beside the numbers. */
.rd-themestats .rd-admintable__meta {
  text-transform: capitalize;
}

/* ---- Recent activity ---- */
.rd-activity {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  list-style: none;
  padding-inline-start: 0;
}

/* One row per event, wrapping rather than truncating: the three kinds carry
   different amounts of text, and a fixed row that clipped an email address
   would hide the only identifying part of a contact-form message. */
.rd-activity__item {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  border: var(--border-width) solid var(--border-subtle);
}

.rd-activity__who {
  font: var(--type-body);
  color: var(--text-heading);
}

.rd-activity__action {
  font: var(--type-body);
  color: var(--text-body);
}

.rd-activity__detail {
  font: var(--type-meta);
  color: var(--text-muted);
}

/* The timestamp is pushed to the end on its own auto margin rather than on a
   spacer, so a wrapped row drops it onto the next line intact. */
.rd-activity__when {
  margin-inline-start: auto;
  font: var(--type-meta);
  color: var(--text-muted);
  white-space: nowrap;
}

.rd-activity__empty {
  font: var(--type-body);
  color: var(--text-muted);
}

/* ---- The one-line result of an action ------------------------------------
   "Invitation resent to sam@example.com." — what just happened, said once,
   above the register it happened in. It shipped as a bare <p>, which on a
   designed screen reads as a stray sentence rather than as the answer to the
   button the operator just pressed.

   Scoped under .admin-shell, not written bare: assets/shell.css restores flow
   margins on the shell's children at (0,1,1), so a bare .rd-adminnotice at
   (0,1,0) would lose its own margin rule to it and sit at the wrong spacing.
   That trap has now caught three separate blocks in this repo. */
.admin-shell .rd-adminnotice {
  margin: 0;
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-sm);
  border: var(--border-width) solid var(--status-success);
  background: var(--status-success-bg);
  color: var(--status-success);
  font: var(--type-body-sm);
}

/* A failure is the same shape in a different ink: same place, same weight, so
   the eye lands on it in the spot it already learned. */
.admin-shell .rd-adminnotice--error {
  border-color: var(--status-danger);
  background: var(--status-danger-bg);
  color: var(--status-danger);
}

/* An identifier in a table cell must not break across lines. RD-LAPS-3456
   wrapped to three lines is a code an operator cannot read out, and reading it
   out is the entire reason the column exists — the hyphens give the browser
   three break opportunities, and a squeezed column takes all of them. The
   scroll wrapper the table already sits in is what absorbs the width instead.
   Scoped one level deeper than .rd-adminslug so it wins on specificity without
   editing the shared chip. */
.rd-admintable .rd-adminslug {
  white-space: nowrap;
}

/* ---- Filter bar ----------------------------------------------------------
   The controls that narrow a register, sitting above it. A row rather than a
   stack: two selects and a button are one decision, and stacking them reads
   as three separate questions being asked in sequence.

   align-items: end so the controls line up along their BOTTOM edge — each
   field is a label above a control, and aligning on the top would leave the
   button floating level with the labels rather than with the inputs it sits
   beside. */
.rd-filterbar {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: var(--space-4) var(--space-5);
}

/* The fields size to their content rather than splitting the bar: a status
   select does not need the same width as a template name, and stretching both
   to fill would make the bar look like a form to complete rather than a filter
   to adjust. */
.rd-filterbar__field {
  flex: 0 1 auto;
}

/* The actions ride with the controls, and wrap with them at narrow widths
   rather than being pushed to a line of their own by a margin-left: auto. */
.rd-filterbar__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

/* Why a send failed. Set in the danger ink because it is the one thing on the
   row that needs reading, and the badge two cells left has already told the
   operator to look here. */
.rd-emaillog__error {
  color: var(--status-danger);
}

/* "Showing the 200 most recent" — the log is read with a cap and this screen
   has no pagination, so past it the page is a window and has to say so.

   The shared notice's shape, in a third ink. Success green would congratulate
   the operator on a page that is hiding rows from them and danger red would
   read as a failure, when this is neither: it is the screen reporting its own
   extent. Scoped under .admin-shell to match .rd-adminnotice, which is where
   the shape it is re-inking is defined.

   Deliberately NOT written as a general .rd-adminnotice--info modifier. This
   is its only site, and a shared modifier invented for one caller is a
   component decision made without a second case to check it against. */
.admin-shell .rd-emaillog__cap {
  border-color: var(--status-info);
  background: var(--status-info-bg);
  color: var(--status-info);
}

/* ---- The mail inbox -----------------------------------------------------
   TWO PANES, ONE SCREEN. The contact-form queue used to be a table that
   linked away to a separate page, so reading a message cost you your place in
   the list. Here the list and the message sit side by side and the selection
   lives in the URL, which is what lets this work with no script at all:
   opening a message is an ordinary link and the back button behaves.

   Below the bar the two panes stack, list first. A 22rem column and a reading
   column do not both fit on a phone, and a squeezed two-up is worse than an
   honest one-up.

   Everything this bead adds to this file is this one contiguous block, for
   the reason the flow-margin note above gives: two workers appending to the
   same section is how a clean merge turns into a pile of conflicts. */
.rd-inbox {
  display: grid;
  gap: var(--space-6);
  align-items: start;
}

@media (min-width: 720px) {
  .rd-inbox {
    grid-template-columns: minmax(0, 22rem) minmax(0, 1fr);
  }
}

/* Both panes are the same card, so neither reads as the subordinate one. */
.rd-inbox__list,
.rd-inbox__reading {
  background: var(--surface-card);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-xs);
  overflow: hidden;
}

.rd-inbox__reading {
  padding: var(--space-7);
}

.rd-inbox__listhead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-5) var(--space-6);
  border-bottom: var(--border-width) solid var(--border-subtle);
}

.rd-inbox__listtitle {
  margin: 0;
  font: var(--type-subhead);
  color: var(--text-heading);
}

/* The count summarises the dots below it, so the state of the queue is
   legible before a single row is scanned. */
.rd-inbox__count {
  font: var(--type-meta);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--accent);
}

.rd-inbox__items {
  margin: 0;
  padding: 0;
  list-style: none;
  max-height: 32rem;
  overflow-y: auto;
}

/* The whole row is the target, not the name inside it — a link the width of a
   word in a list this size is a hard thing to hit. */
.rd-inbox__item {
  position: relative;
  display: grid;
  gap: var(--space-2);
  /* The right padding is a gutter for the unread dot, reserved on EVERY row
     rather than only on unread ones, so a read row and an unread one still
     line their dates up. */
  padding: var(--space-5) var(--space-8) var(--space-5) var(--space-6);
  border-bottom: var(--border-width) solid var(--border-subtle);
  color: inherit;
  text-decoration: none;
}

.rd-inbox__item:hover {
  background: var(--surface-hover);
}

.rd-inbox__item:focus-visible {
  outline: var(--focus-ring);
  outline-offset: calc(var(--border-width) * -2);
}

/* The open message, marked the same way the nav marks the current section. */
.rd-inbox__item[aria-current] {
  background: var(--accent-soft);
  box-shadow: inset 3px 0 0 0 var(--accent);
}

.rd-inbox__itemtop {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-4);
}

.rd-inbox__from {
  font: var(--type-label);
  color: var(--text-heading);
}

/* Unread is carried by WEIGHT as well as by the dot: colour alone fails
   anybody who cannot see it, and a dot alone is easy to miss down a column. */
.rd-inbox__item--unread .rd-inbox__from {
  font-weight: var(--weight-semibold);
}

.rd-inbox__when {
  flex: 0 0 auto;
  font: var(--type-meta);
  color: var(--text-faint);
}

/* Set in the meta face, which is the mono one: an address is a string being
   read character by character, not prose. */
.rd-inbox__itemaddress {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: var(--type-meta);
  color: var(--text-muted);
}

/* One line of the message, clipped, so a queue can be triaged without opening
   every item in it. */
.rd-inbox__preview {
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font: var(--type-body-sm);
  color: var(--text-muted);
}

.rd-inbox__dot {
  position: absolute;
  top: var(--space-6);
  right: var(--space-3);
  width: 8px;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--accent);
}

/* The word behind the dot. A coloured dot announces nothing, so the state is
   spelled out for a screen reader and hidden from everyone else. */
.rd-inbox__unread {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.rd-inbox__none {
  padding: var(--space-7) var(--space-6);
  font: var(--type-body-sm);
  color: var(--text-muted);
}

/* ---- The reading pane --- */
/* overflow-wrap is inherited, so setting it on the article covers everything a
   visitor wrote into it — name, address and body. pre-wrap below wraps only at
   spaces, and the pane is overflow:hidden, so without this a pasted URL or a
   long address ran past the edge and was cut off: on a phone the message was
   in the markup and not on the screen. adminmail_criterion_test.go resolves it
   for every element in the article. */
.rd-inbox__message {
  margin-block: 0;
  overflow-wrap: anywhere;
}

.rd-inbox__messagehead {
  padding-bottom: var(--space-5);
  border-bottom: var(--border-width) solid var(--border-subtle);
}

.rd-inbox__sender {
  margin: 0;
  font: var(--type-title);
  color: var(--text-heading);
}

.rd-inbox__address {
  font: var(--type-body-sm);
  color: var(--text-link);
}

/* Three classes, (0,3,0), which is what it takes to beat the shell's (0,2,1)
   margin reset above — a bare .rd-inbox__received would be silently inert.
   The guard in adminmargin_test.go is what stops that being rediscovered by
   screenshot a fourth time. */
.admin-shell .rd-inbox__messagehead .rd-inbox__received {
  margin: var(--space-2) 0 0;
  font: var(--type-meta);
  color: var(--text-faint);
}

/* pre-wrap keeps the paragraphs the visitor typed without trusting a line of
   their markup — the message is escaped text, laid out by CSS. */
.rd-inbox__body {
  margin-block: var(--space-6);
  white-space: pre-wrap;
  font: var(--type-body);
  color: var(--text-body);
}

.admin-shell .rd-inbox__reading .rd-inbox__sent {
  margin-block: var(--space-5);
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-sm);
  background: var(--status-success-bg);
  color: var(--status-success);
  font: var(--type-body-sm);
}

.admin-shell .rd-inbox__reading .rd-inbox__error {
  margin-block: var(--space-5);
  padding: var(--space-4) var(--space-5);
  border-radius: var(--radius-sm);
  background: var(--status-danger-bg);
  color: var(--status-danger);
  font: var(--type-body-sm);
}

/* The reply sits under the message it answers, divided from it rather than
   boxed again — a card inside a card reads as a different screen. */
.rd-inbox__reply {
  margin-block: 0;
  padding-top: var(--space-6);
  border-top: var(--border-width) solid var(--border-subtle);
}

.admin-shell .rd-inbox__reply .rd-inbox__replytitle {
  margin: 0 0 var(--space-5);
  font: var(--type-subhead);
  color: var(--text-heading);
}
/* assets/components.css */
/* Refdeck component layer — the design system's 22 primitives, ported.

   WHY THIS FILE EXISTS. The .jsx files under design-system/components/ are
   React components that style themselves with inline style objects, and
   design-system/styles.css is seven token includes with no classes in it.
   This app is server-rendered html/template with no build step (AGENTS.md
   hard constraint 4), so the primitives cannot be imported — they are ported
   here once, as classes, and every screen is written against these classes
   rather than restating a component's styling inline.

   (That sentence named those files with a glob until 2026-08. A glob holding
   a star next to a slash spells this comment's terminator, so the comment
   ended on line 3 and the parser ate forward to the next brace — which
   silently deleted the component-boundary rule below for as long as it had
   existed. Kept out of every layer now by
   TestNoAppCSSLayerClosesACommentByAccident.)

   HOW A PORT IS DERIVED. Each block below is a straight reading of one .jsx:
   the same layout, the same tokens, the same states. The one systematic
   difference is that React tracks hover/focus/checked in component state, which
   here becomes :hover, :focus-within, :checked and :disabled — no JavaScript,
   which is the point.

   THE VALUE RULE. Colour, type, radius, elevation and motion come from
   design-system/tokens/ and nowhere else; tests enforce that this file writes
   no colour by value and defines no custom property of its own. Spacing is
   snapped to the nearest step on the spacing scale, so a few paddings sit a
   pixel or two off the prototype's ad-hoc numbers — the scale is the source of
   truth, not the prototype. Component GEOMETRY that the token set has no scale
   for (an 18px checkbox box, a 38x22 switch track, avatar diameters) is carried
   from the .jsx as written, and is called out where it appears.

   ORDER. This file is concatenated LAST, after assets/shell.css — see
   appComponentsCSS in assets.go for why that is load-bearing. */

/* ---- Component boundary -------------------------------------------------
   shell.css restores browser-default margins on flow elements inside
   .app-shell, because the app's pages are plain semantic markup. Components
   own their own spacing, so that restoration has to stop at a component edge.
   :is() takes the highest specificity of its arguments, so this matches the
   shell's rule exactly (0,1,1) and wins the tie by being later in the bundle. */
:is(.rd-card, .rd-contact-card, .rd-empty-state, .rd-dialog, .rd-toast,
    .rd-field, .rd-sidebar-nav, .rd-tabs, .rd-tooltip)
  :is(p, ul, ol, dl, table, form, section, nav, blockquote) {
  margin-block: 0;
}

@keyframes rd-spin {
  to { transform: rotate(1turn); }
}

@keyframes rd-dialog-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* ---- Icon (components/core/Icon.jsx) ------------------------------------
   The .jsx inlines Lucide path data and takes a numeric size; here the glyph
   markup is written by the template and this class carries the sizing and the
   layout behaviour (display:block, never shrink) that every caller wanted.
   Sizes are the ones the primitives actually pass. */
.rd-icon {
  display: block;
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
}

.rd-icon--xs { width: 13px; height: 13px; }
.rd-icon--sm { width: 15px; height: 15px; }
.rd-icon--md { width: 18px; height: 18px; }
.rd-icon--lg { width: 22px; height: 22px; }

/* ---- Avatar (components/display/Avatar.jsx) -----------------------------
   Diameters are Avatar.jsx's SIZES map; the type size is its d * 0.4, snapped
   to the type scale. */
.rd-avatar {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  width: 40px;
  height: 40px;
  border: var(--border-width) solid var(--teal-100);
  border-radius: var(--radius-md);
  background: var(--accent-quiet);
  color: var(--accent);
  font-family: var(--font-serif);
  font-size: var(--text-md);
  line-height: 1;
}

.rd-avatar--sm { width: 28px; height: 28px; font-size: var(--text-2xs); }
.rd-avatar--md { width: 40px; height: 40px; font-size: var(--text-md); }
.rd-avatar--lg { width: 56px; height: 56px; font-size: var(--text-xl); }
.rd-avatar--xl { width: 80px; height: 80px; font-size: var(--text-2xl); }

.rd-avatar--circle { border-radius: var(--radius-pill); }

/* A logo sits on white behind a hairline; initials sit on the accent tint. */
.rd-avatar--image {
  background: var(--surface-card);
  border-color: var(--border-subtle);
}

.rd-avatar__img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* A member's PHOTO fills its frame, where a logo fits inside one. The two
   want opposite fits and it is not a matter of taste: cropping a wordmark
   loses the word, and letterboxing a face inside a circle — bars above and
   below a head — reads as a mistake rather than as a portrait. */
.rd-avatar--photo {
  background: var(--surface-card);
  border-color: var(--border-subtle);
}

.rd-avatar--photo .rd-avatar__img {
  object-fit: cover;
}

/* ---- Badge (components/display/Badge.jsx) ------------------------------- */
.rd-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  border: var(--border-width) solid transparent;
  border-radius: var(--radius-xs);
  font: var(--type-meta);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  white-space: nowrap;
}

.rd-badge--neutral {
  background: var(--bg-sunken);
  color: var(--text-body);
  border-color: var(--border-subtle);
}

.rd-badge--accent {
  background: var(--accent-soft);
  color: var(--teal-900);
  border-color: var(--teal-200);
}

.rd-badge--bronze {
  background: var(--highlight-soft);
  color: var(--bronze-700);
  border-color: var(--bronze-200);
}

.rd-badge--success { background: var(--status-success-bg); color: var(--status-success); }
.rd-badge--warning { background: var(--status-warning-bg); color: var(--status-warning); }
.rd-badge--danger  { background: var(--status-danger-bg);  color: var(--status-danger); }
.rd-badge--info    { background: var(--status-info-bg);    color: var(--status-info); }

/* ---- Card (components/display/Card.jsx) --------------------------------- */
.rd-card {
  display: block;
  padding: var(--space-7);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--surface-card);
  box-shadow: var(--shadow-xs);
  transition:
    box-shadow var(--dur-base) var(--ease-out),
    transform var(--dur-base) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out);
}

.rd-card--pad-none { padding: var(--space-0); }
.rd-card--pad-sm   { padding: var(--space-6); }
.rd-card--pad-md   { padding: var(--space-7); }
.rd-card--pad-lg   { padding: var(--space-9); }

.rd-card--default   { background: var(--surface-card);    border-color: var(--border-subtle); }
.rd-card--soft      { background: var(--accent-quiet);    border-color: var(--teal-100); }
.rd-card--highlight { background: var(--highlight-quiet); border-color: var(--bronze-200); }
.rd-card--sunken    { background: var(--bg-sunken);       border-color: var(--border-subtle); }

.rd-card--interactive {
  cursor: pointer;
}

/* A card is sometimes an <a>, and base.css underlines and recolours links. */
a.rd-card,
a.rd-card:hover {
  color: inherit;
  text-decoration: none;
}

.rd-card--interactive:hover {
  border-color: var(--border-default);
  box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}

/* ---- ContactCard (components/display/ContactCard.jsx) -------------------
   Composed on .rd-card in the markup, exactly as the .jsx renders a <Card>. */
.rd-contact-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.rd-contact-card__head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-5);
}

.rd-contact-card__identity {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.rd-contact-card__business {
  overflow: hidden;
  font: var(--type-heading);
  color: var(--text-heading);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rd-contact-card__person {
  font: var(--type-body-sm);
  color: var(--text-body);
}

.rd-contact-card__profession {
  margin-top: var(--space-1);
  font: var(--type-meta);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* Trade-first identity (crit:d97ef0300ae8): on public cards the TRADE takes
   the heading type the company used to hold, the person keeps the body line,
   and the company drops to the muted meta line — trade → person → company,
   matching how a visitor scans a referral list. The __business/__person/
   __profession trio above remains for the company-led surfaces (the group
   directory). */
.rd-contact-card__trade {
  font: var(--type-heading);
  color: var(--text-heading);
  /* In full, wrapping (PRD #446): the trade is the one thing a visitor is
     scanning for, and "Residential Mortg…" is not it. */
  overflow-wrap: anywhere;
}

.rd-contact-card__company {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-1);
  font: var(--type-meta);
  letter-spacing: var(--tracking-wide);
  color: var(--text-muted);
}

/* The company's mark (PRD #446, re-placed by PRD #718): never the card's
   portrait — that slot is the person's — and now the lead of the identity
   rather than a sign beside the company name. It sits centred in a slot of
   fixed height, scaled to fit, so a wide wordmark and a square mark take
   the same room and a company with no mark leaves the same room empty: the
   grid's baselines hold whatever each company uploaded. */
.rd-contact-card__logoblock {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 64px;
}

.rd-contact-card__logo {
  display: block;
  width: auto;
  height: auto;
  max-width: 72%;
  max-height: 48px;
  object-fit: contain;
}

/* Under a centred mark the identity centres too: person, then company, as
   the trade-first reading fixed them, both centred under the slot. The
   modifier is written by the partial on a head with a company line, so a
   head with none (nothing for a mark to belong to) keeps the left-aligned
   stack; a class rather than :has() because the band-scrim guards resolve
   the cascade by class and cannot read a relational selector. */
.rd-contact-card__identity--logoled {
  width: 100%;
  align-items: center;
  text-align: center;
}

.rd-contact-card__identity--logoled .rd-contact-card__company {
  justify-content: center;
}

.rd-contact-card__flag { margin-left: auto; }

/* ---- Trade band (PRD #446) ----------------------------------------------
   The head every visitor-facing reference surface shares: a coloured band
   across the top of a card or a detail header carrying the type of business
   in full and the person's picture; the person's name below it; the company
   beneath that with its logo as a secondary mark. The layout's trade_head
   block renders it for the deck cards, the deck's detail page and both
   chapter surfaces, so there is one markup and one set of rules; deck.css
   only rescales the block's classes on the detail sheet.

   The band bleeds to the card's edge: it undoes the small card padding on
   three sides and takes the card's own radius on top, so it reads as the
   card's cap rather than as a chip inside it. A head with no trade renders
   no band at all — the markup, not a modifier, decides that. */
.rd-tradeband {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  margin: calc(-1 * var(--space-6)) calc(-1 * var(--space-6)) 0;
  padding: var(--space-5) var(--space-6);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  background: var(--accent);
  color: var(--text-on-dark);
}

/* Inside the band the trade keeps the heading type and takes the band's
   text colour; the portrait's hairline is the on-dark one the deck band's
   photo wears. */
.rd-tradeband .rd-contact-card__trade { color: inherit; }

/* The category's icon at the band's far end (PRD #718), the one its filter
   chip wears, in the band's own on-dark text. */
.rd-tradeband__icon {
  margin-left: auto;
  width: 18px;
  height: 18px;
  opacity: 0.9;
}

.rd-tradeband .rd-avatar {
  border-color: color-mix(in srgb, var(--text-on-dark) 25%, transparent);
}

/* The twelve tones (crit:846af1244ba3). Chosen in Go from the folded
   category key (tradeTone in tradehead.go), so one trade is one colour
   everywhere. Six are the design system's own 600-level hues — the same six
   the deck accent palette draws from, which is what makes on-dark text safe
   on them — and six are pairwise mixes of those, no lighter than their
   parents. Fixed-hue tokens on purpose: none of these reads a surface or
   body-text token that a theme would invert, so the band is the same pair
   of colours in the light theme and in any dark theme the tokens define.
   TestEveryTradeToneKeepsItsTextReadableInEveryTheme computes each one's
   contrast against the band's text per theme rather than trusting this
   comment, and holds this list to tradeToneCount. */
.rd-tradeband--tone-0 { background: var(--teal-600); }
.rd-tradeband--tone-1 { background: var(--bronze-600); }
.rd-tradeband--tone-2 { background: var(--green-600); }
.rd-tradeband--tone-3 { background: var(--amber-600); }
.rd-tradeband--tone-4 { background: var(--blue-600); }
.rd-tradeband--tone-5 { background: var(--red-600); }
.rd-tradeband--tone-6 { background: color-mix(in srgb, var(--teal-600) 50%, var(--blue-600)); }
.rd-tradeband--tone-7 { background: color-mix(in srgb, var(--bronze-600) 50%, var(--red-600)); }
.rd-tradeband--tone-8 { background: color-mix(in srgb, var(--green-600) 50%, var(--amber-600)); }
.rd-tradeband--tone-9 { background: color-mix(in srgb, var(--amber-600) 50%, var(--red-600)); }
.rd-tradeband--tone-10 { background: color-mix(in srgb, var(--blue-600) 50%, var(--red-600)); }
.rd-tradeband--tone-11 { background: color-mix(in srgb, var(--green-600) 50%, var(--blue-600)); }

/* ContactCard.jsx passes tone="highlight" when featured, so the modifier
   carries the tone rather than making every caller remember to pair it with
   .rd-card--highlight. Declared after .rd-card--*, so it wins the tie. */
.rd-contact-card--featured {
  background: var(--highlight-quiet);
  border-color: var(--bronze-200);
}

.rd-contact-card__description {
  font: var(--type-body-sm);
  color: var(--text-body);
}

.rd-contact-card__foot {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  margin-top: auto;
  padding-top: var(--space-5);
  border-top: var(--border-width) solid var(--border-subtle);
}

/* Wraps: a badge never breaks inside (white-space: nowrap), so a card with
   several tags would otherwise push the last one past its edge. min-width
   lets the row shrink inside the foot's flex line rather than size it. */
.rd-contact-card__facts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3) var(--space-5);
  min-width: 0;
}

.rd-contact-card__area {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  font: var(--type-body-sm);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.rd-contact-card__area .rd-icon { color: var(--text-faint); }

.rd-contact-card__action { margin-left: auto; }

/* Stacked is the phone layout: the facts row and a full-width action. */
.rd-contact-card--stacked .rd-contact-card__foot {
  flex-direction: column;
  align-items: stretch;
}

/* ---- EmptyState (components/display/EmptyState.jsx) --------------------- */
.rd-empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-11) var(--space-7);
  border: var(--border-width) dashed var(--border-default);
  border-radius: var(--radius-lg);
  background: var(--bg-sunken);
  text-align: center;
}

.rd-empty-state > .rd-icon { color: var(--text-faint); }

.rd-empty-state__title {
  font: var(--type-subhead);
  color: var(--text-heading);
}

.rd-empty-state__body {
  max-width: 42ch;
  font: var(--type-body-sm);
  color: var(--text-muted);
}

/* ---- Tag (components/display/Tag.jsx) ----------------------------------- */
.rd-tag {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  height: var(--control-h-sm);
  padding: 0 var(--space-5);
  border: var(--border-width) solid var(--border-default);
  border-radius: var(--radius-pill);
  background: var(--surface-card);
  color: var(--text-body);
  font: var(--weight-medium) var(--text-sm)/1 var(--font-sans);
  white-space: nowrap;
  user-select: none;
  cursor: default;
  transition: var(--transition-control);
}

.rd-tag--clickable { cursor: pointer; }

.rd-tag--clickable:hover { background: var(--surface-hover); }

.rd-tag--selected,
.rd-tag--clickable.rd-tag--selected:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-on);
}

.rd-tag__count {
  font: var(--type-meta);
  color: var(--text-faint);
}

/* The .jsx writes rgba(255,255,255,.75) here; the same result from the token
   plus an opacity, so no colour is restated by value. */
.rd-tag--selected .rd-tag__count {
  color: var(--accent-on);
  opacity: 0.75;
}

/* A category chip's icon (PRD #718): the category's drawing ahead of its
   label, in the accent on a resting chip and the chip's own text when
   selected. The same drawing rides the trade band (below). */
.rd-tag__icon {
  width: 14px;
  height: 14px;
  color: var(--accent);
}

.rd-tag--selected .rd-tag__icon {
  color: inherit;
}

.rd-tag__remove {
  display: inline-flex;
  padding: 0;
  border: none;
  background: transparent;
  color: inherit;
  opacity: 0.6;
  cursor: pointer;
}

.rd-tag__remove:hover { opacity: 1; }

/* ---- Button (components/forms/Button.jsx) ------------------------------- */
.rd-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  height: var(--control-h-md);
  padding: 0 var(--space-6);
  border: var(--border-width) solid transparent;
  border-radius: var(--radius-sm);
  font: var(--type-button);
  letter-spacing: var(--tracking-snug);
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  transition: var(--transition-control), transform var(--dur-instant) var(--ease-out);
}

.rd-button--sm {
  gap: var(--space-3);
  height: var(--control-h-sm);
  padding: 0 var(--space-5);
  font: var(--weight-semibold) var(--text-xs)/1 var(--font-sans);
}

.rd-button--md {
  gap: var(--space-4);
  height: var(--control-h-md);
  padding: 0 var(--space-6);
  font: var(--type-button);
}

.rd-button--lg {
  gap: var(--space-4);
  height: var(--control-h-lg);
  padding: 0 var(--space-8);
  font: var(--weight-semibold) var(--text-base)/1 var(--font-sans);
}

.rd-button--sm .rd-icon { width: 14px; height: 14px; }
.rd-button--md .rd-icon { width: 16px; height: 16px; }
.rd-button--lg .rd-icon { width: 18px; height: 18px; }

.rd-button--full {
  display: flex;
  width: 100%;
}

.rd-button--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-on);
}

.rd-button--secondary {
  background: var(--surface-card);
  border-color: var(--border-default);
  color: var(--text-heading);
}

.rd-button--ghost {
  background: transparent;
  border-color: transparent;
  color: var(--text-body);
}

.rd-button--danger {
  background: var(--status-danger);
  border-color: var(--status-danger);
  color: var(--accent-on);
}

.rd-button--inverse {
  background: var(--white);
  border-color: var(--white);
  color: var(--teal-900);
}

/* base.css underlines and recolours a:hover, and a button is often an <a>. */
.rd-button:hover { text-decoration: none; }

.rd-button--primary:enabled:hover,
a.rd-button--primary:not(.rd-button--disabled):hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
  color: var(--accent-on);
}

.rd-button--secondary:enabled:hover,
a.rd-button--secondary:not(.rd-button--disabled):hover {
  background: var(--surface-hover);
  border-color: var(--border-strong);
  color: var(--text-heading);
}

.rd-button--ghost:enabled:hover,
a.rd-button--ghost:not(.rd-button--disabled):hover {
  background: var(--surface-hover);
  color: var(--text-body);
}

/* The .jsx darkens danger to #8C2A21 on hover, a value the token set does not
   carry. Mixing the token toward the ink token lands on the same colour and
   keeps the palette single-sourced. */
.rd-button--danger:enabled:hover,
a.rd-button--danger:not(.rd-button--disabled):hover {
  background: color-mix(in srgb, var(--status-danger) 86%, var(--ink-1));
  border-color: color-mix(in srgb, var(--status-danger) 86%, var(--ink-1));
  color: var(--accent-on);
}

.rd-button--inverse:enabled:hover,
a.rd-button--inverse:not(.rd-button--disabled):hover {
  background: var(--accent-quiet);
  border-color: var(--accent-quiet);
  color: var(--teal-900);
}

.rd-button:enabled:active { transform: translateY(0.5px); }

/* Disabled last, so it wins the tie against every variant above. .jsx treats
   loading as disabled too, and an <a> cannot carry the attribute. */
.rd-button:disabled,
.rd-button--disabled,
.rd-button--loading {
  background: var(--paper-tint);
  border-color: var(--border-subtle);
  color: var(--text-faint);
  cursor: not-allowed;
  transform: none;
}

.rd-button__spinner {
  /* Period carried from Button.jsx; base.css collapses it under
     prefers-reduced-motion. */
  animation: rd-spin 900ms linear infinite;
}

/* ---- IconButton (components/forms/IconButton.jsx) -----------------------
   Square by design: the sizes are the control heights, and lg is the 44px
   minimum tap target. */
.rd-icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--control-h-md);
  height: var(--control-h-md);
  padding: 0;
  border: var(--border-width) solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-body);
  cursor: pointer;
  transition: var(--transition-control);
}

.rd-icon-button--sm { width: var(--control-h-sm); height: var(--control-h-sm); }
.rd-icon-button--md { width: var(--control-h-md); height: var(--control-h-md); }
.rd-icon-button--lg { width: var(--tap-min);      height: var(--tap-min); }

.rd-icon-button--sm .rd-icon { width: 15px; height: 15px; }

.rd-icon-button--ghost {
  background: transparent;
  border-color: transparent;
}

.rd-icon-button--outline {
  background: var(--surface-card);
  border-color: var(--border-default);
}

.rd-icon-button:enabled:hover {
  background: var(--surface-hover);
  color: var(--text-heading);
}

.rd-icon-button:disabled {
  color: var(--text-faint);
  cursor: not-allowed;
}

/* ---- Field: the label / hint / error scaffold shared by Input, Textarea
   and Select, which all three .jsx files render identically. --------------- */
.rd-field {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.rd-field__label {
  font: var(--type-label);
  color: var(--text-heading);
}

.rd-field__required { color: var(--text-faint); }

/* The counterpart marker: a field that may be left blank, said in the label
   itself because that is what someone filling the field reads. Same faint
   ink as __required so neither marker competes with the label it qualifies.
   Contacts need only a name — a friend you recommend has no company, and the
   form should not imply one is expected (REQ-0010). */
.rd-field__optional {
  color: var(--text-faint);
  font-weight: var(--weight-regular);
}

.rd-field__hint {
  font: var(--type-body-sm);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.rd-field__error {
  font: var(--type-body-sm);
  font-size: var(--text-xs);
  color: var(--status-danger);
}

/* ---- Input (components/forms/Input.jsx) --------------------------------
   The bordered shell is the wrapper so an optional leading icon sits inside
   the same box; the React focus state becomes :focus-within. */
.rd-input {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  height: var(--control-h-md);
  padding: 0 var(--space-5);
  border: var(--border-width) solid var(--border-default);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  transition: var(--transition-control);
}

.rd-input--sm { height: var(--control-h-sm); }
.rd-input--lg { height: var(--control-h-lg); }

.rd-input > .rd-icon { color: var(--text-muted); }

.rd-input:focus-within {
  border-color: var(--border-focus);
  box-shadow: var(--focus-ring);
}

.rd-input--error { border-color: var(--status-danger); }

/* A control shown but not editable — a linked contact's public half
   (PRD #454), the same idiom as .rd-settings__locked: sunken and muted so
   it reads as deliberately unavailable rather than broken. */
.rd-input--locked,
.rd-textarea--locked {
  background: var(--bg-sunken);
  color: var(--text-muted);
}

.rd-input--locked .rd-input__control,
.rd-textarea--locked {
  color: var(--text-muted);
}

.rd-input__control {
  flex: 1;
  min-width: 0;
  border: none;
  outline: none;
  background: transparent;
  font: var(--type-body-sm);
  color: var(--text-heading);
}

/* ---- Textarea (components/forms/Textarea.jsx) --------------------------- */
.rd-textarea {
  resize: vertical;
  padding: var(--space-5);
  border: var(--border-width) solid var(--border-default);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  font: var(--type-body-sm);
  color: var(--text-heading);
  outline: none;
  transition: var(--transition-control);
}

.rd-textarea:focus {
  border-color: var(--border-focus);
  box-shadow: var(--focus-ring);
}

.rd-textarea--error { border-color: var(--status-danger); }

/* ---- Select (components/forms/Select.jsx) ------------------------------- */
.rd-select {
  position: relative;
  display: flex;
  align-items: center;
}

.rd-select__control {
  appearance: none;
  width: 100%;
  height: var(--control-h-md);
  padding: 0 var(--space-9) 0 var(--space-5);
  border: var(--border-width) solid var(--border-default);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  font: var(--type-body-sm);
  color: var(--text-heading);
  outline: none;
  cursor: pointer;
  transition: var(--transition-control);
}

.rd-select--sm .rd-select__control { height: var(--control-h-sm); }

.rd-select__control:focus {
  border-color: var(--border-focus);
  box-shadow: var(--focus-ring);
}

.rd-select__chevron {
  position: absolute;
  right: var(--space-5);
  color: var(--text-muted);
  pointer-events: none;
}

/* ---- SearchField (components/forms/SearchField.jsx) --------------------- */
.rd-search {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  height: var(--control-h-lg);
  padding: 0 var(--space-6);
  border: var(--border-width) solid var(--border-default);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  box-shadow: var(--shadow-xs);
  transition: var(--transition-control);
}

.rd-search--md { height: var(--control-h-md); }

.rd-search > .rd-icon { color: var(--text-muted); }

.rd-search:focus-within {
  border-color: var(--border-focus);
  box-shadow: var(--focus-ring);
}

.rd-search__input {
  flex: 1;
  min-width: 0;
  border: none;
  outline: none;
  background: transparent;
  font: var(--type-body);
  color: var(--text-heading);
}

.rd-search__clear {
  display: inline-flex;
  padding: var(--space-1);
  border: none;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}

.rd-search__clear:hover { color: var(--text-heading); }

/* ---- Checkbox (components/forms/Checkbox.jsx) --------------------------
   The real input stays in the accessibility tree and drives every state; the
   18px box is Checkbox.jsx's geometry. */
.rd-checkbox {
  display: inline-flex;
  align-items: flex-start;
  gap: var(--space-5);
  cursor: pointer;
}

.rd-checkbox__input {
  position: absolute;
  width: 0;
  height: 0;
  opacity: 0;
}

.rd-checkbox__box {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  margin-top: var(--space-1);
  border: var(--border-width) solid var(--border-strong);
  border-radius: var(--radius-xs);
  background: var(--surface-card);
  color: var(--accent-on);
  transition: var(--transition-control);
}

.rd-checkbox__check {
  width: 13px;
  height: 13px;
  opacity: 0;
}

.rd-checkbox__input:checked + .rd-checkbox__box {
  background: var(--accent);
  border-color: var(--accent);
}

.rd-checkbox__input:checked + .rd-checkbox__box .rd-checkbox__check { opacity: 1; }

/* The input is visually hidden, so base.css's :focus-visible outline would be
   drawn on a zero-sized element. Move the ring onto the box instead. */
.rd-checkbox__input:focus-visible + .rd-checkbox__box {
  border-color: var(--border-focus);
  box-shadow: var(--focus-ring);
}

.rd-checkbox:has(.rd-checkbox__input:disabled) {
  opacity: 0.55;
  cursor: not-allowed;
}

.rd-checkbox__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.rd-checkbox__label {
  font: var(--type-label);
  color: var(--text-heading);
}

.rd-checkbox__description {
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/* ---- Switch (components/forms/Switch.jsx) ------------------------------
   Track and knob geometry (38x22, an 18px knob travelling 16px) is
   Switch.jsx's. */
.rd-switch {
  display: inline-flex;
  align-items: center;
  gap: var(--space-5);
  cursor: pointer;
}

.rd-switch__input {
  position: absolute;
  width: 0;
  height: 0;
  opacity: 0;
}

.rd-switch__track {
  display: inline-flex;
  flex: 0 0 auto;
  width: 38px;
  height: 22px;
  padding: var(--space-1);
  border-radius: var(--radius-pill);
  background: var(--line-2);
  transition: background-color var(--dur-base) var(--ease-out);
}

.rd-switch__knob {
  width: 18px;
  height: 18px;
  border-radius: var(--radius-pill);
  background: var(--white);
  box-shadow: var(--shadow-xs);
  transform: translateX(0);
  transition: transform var(--dur-base) var(--ease-out);
}

.rd-switch__input:checked + .rd-switch__track { background: var(--accent); }

.rd-switch__input:checked + .rd-switch__track .rd-switch__knob {
  transform: translateX(16px);
}

.rd-switch__input:focus-visible + .rd-switch__track { box-shadow: var(--focus-ring); }

.rd-switch:has(.rd-switch__input:disabled) {
  opacity: 0.55;
  cursor: not-allowed;
}

.rd-switch__label {
  font: var(--type-label);
  color: var(--text-heading);
}

/* ---- Tabs (components/navigation/Tabs.jsx) ----------------------------- */
.rd-tabs {
  display: flex;
  gap: var(--space-7);
  border-bottom: var(--border-width) solid var(--border-subtle);
}

.rd-tabs__tab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
  margin-bottom: calc(var(--border-width) * -1);
  padding: 0 var(--space-1) var(--space-5);
  border: none;
  border-bottom: 2px solid transparent;
  background: transparent;
  color: var(--text-muted);
  font: var(--weight-semibold) var(--text-sm)/1 var(--font-sans);
  text-decoration: none;
  cursor: pointer;
  transition: var(--transition-control);
}

.rd-tabs__tab:hover { color: var(--text-body); text-decoration: none; }

.rd-tabs__tab[aria-selected="true"],
.rd-tabs__tab[aria-current="page"] {
  border-bottom-color: var(--accent);
  color: var(--text-heading);
}

.rd-tabs__count {
  font: var(--type-meta);
  color: var(--text-faint);
}

/* ---- SidebarNav (components/navigation/SidebarNav.jsx) ------------------ */
.rd-sidebar-nav {
  display: flex;
  flex: 0 0 auto;
  flex-direction: column;
  gap: var(--space-2);
  width: var(--sidebar-w);
  padding: var(--space-6);
  border-right: var(--border-width) solid var(--border-subtle);
  background: var(--bg-sunken);
}

.rd-sidebar-nav__item {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  height: var(--control-h-md);
  padding: 0 var(--space-5);
  border: var(--border-width) solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-body);
  font: var(--weight-medium) var(--text-sm)/1 var(--font-sans);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
  transition: var(--transition-control);
}

.rd-sidebar-nav__item .rd-icon { color: var(--text-muted); }

.rd-sidebar-nav__item:hover {
  background: var(--surface-hover);
  color: var(--text-heading);
  text-decoration: none;
}

.rd-sidebar-nav__item[aria-current="page"] {
  border-color: var(--border-subtle);
  background: var(--surface-card);
  box-shadow: var(--shadow-xs);
  color: var(--text-heading);
}

.rd-sidebar-nav__item[aria-current="page"] .rd-icon { color: var(--accent); }

.rd-sidebar-nav__count {
  margin-left: auto;
  font: var(--type-meta);
  color: var(--text-faint);
}

.rd-sidebar-nav__footer { margin-top: auto; }

/* ---- Tooltip (components/feedback/Tooltip.jsx) -------------------------
   The .jsx toggles the bubble on mouse and focus; :hover and :focus-within do
   the same without script. */
.rd-tooltip {
  position: relative;
  display: inline-flex;
}

.rd-tooltip__bubble {
  position: absolute;
  left: 50%;
  bottom: calc(100% + var(--space-3));
  z-index: 30;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-xs);
  background: var(--surface-inverse);
  color: var(--text-on-dark);
  font: var(--weight-medium) var(--text-xs)/1.2 var(--font-sans);
  white-space: nowrap;
  box-shadow: var(--shadow-md);
  transform: translateX(-50%);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity var(--dur-fast) var(--ease-out), visibility var(--dur-fast) var(--ease-out);
}

.rd-tooltip__bubble--bottom {
  top: calc(100% + var(--space-3));
  bottom: auto;
}

.rd-tooltip:hover > .rd-tooltip__bubble,
.rd-tooltip:focus-within > .rd-tooltip__bubble {
  opacity: 1;
  visibility: visible;
}

/* ---- Toast (components/feedback/Toast.jsx) ----------------------------- */
.rd-toast {
  display: flex;
  align-items: flex-start;
  gap: var(--space-5);
  min-width: 300px;
  max-width: 420px;
  padding: var(--space-6);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--surface-card);
  box-shadow: var(--shadow-lg);
}

.rd-toast__icon { margin-top: var(--space-1); }

.rd-toast--success .rd-toast__icon { color: var(--status-success); }
.rd-toast--info    .rd-toast__icon { color: var(--status-info); }
.rd-toast--danger  .rd-toast__icon { color: var(--status-danger); }

.rd-toast__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.rd-toast__title {
  font: var(--type-label);
  color: var(--text-heading);
}

.rd-toast__body {
  font: var(--type-body-sm);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.rd-toast__close {
  display: inline-flex;
  margin-left: auto;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--text-faint);
  cursor: pointer;
}

.rd-toast__close:hover { color: var(--text-body); }

/* ---- InlineNote (components/feedback/InlineNote.jsx) --------------------
   A form's own message, attached to the card that raised it. Toast floats
   over the page; this belongs in the flow, above the fields it concerns. A
   failed sign-in is never a toast.

   This rule set arrived here from auth.css, where the app invented it before
   the primitive existed — the design system has since absorbed it, so it is a
   primitive's port now and belongs in the primitive layer with the others.
   The auth surface composes it like any other .rd-* it does not own.

   FRIENDLY, NOT ALARMING. A toned note is the semantic status colour on its
   own tint at a hairline — the same weight Badge gives a status — rather than
   a saturated bar. Someone who mistyped a password is not being warned, they
   are being told.

   The .jsx picks the icon from the tone (info / circle-alert / circle-check)
   and sets role="alert" on danger alone. Neither is a style, so both stay
   with the markup: a template writes the glyph it means and the role it
   means, and these rules colour whatever it wrote. */
.rd-inline-note {
  display: flex;
  align-items: flex-start;
  gap: var(--space-5);
  padding: var(--space-5);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
  font: var(--type-body-sm);
  color: var(--text-body);
}

.rd-inline-note .rd-icon {
  flex: 0 0 auto;
  margin-top: var(--space-1);
  color: var(--text-muted);
}

.rd-inline-note--danger {
  border-color: color-mix(in srgb, var(--status-danger) 30%, var(--surface-card));
  background: var(--status-danger-bg);
  color: var(--text-heading);
}

.rd-inline-note--danger .rd-icon {
  color: var(--status-danger);
}

.rd-inline-note--success {
  border-color: color-mix(in srgb, var(--status-success) 30%, var(--surface-card));
  background: var(--status-success-bg);
  color: var(--text-heading);
}

.rd-inline-note--success .rd-icon {
  color: var(--status-success);
}

/* ---- Dialog (components/feedback/Dialog.jsx) --------------------------- */
.rd-dialog {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-7);
  background: var(--scrim);
  backdrop-filter: var(--blur-veil);
  animation: rd-dialog-in var(--dur-base) var(--ease-entrance);
}

.rd-dialog__panel {
  width: 100%;
  max-width: 520px;
  max-height: 92%;
  overflow: auto;
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-xl);
  background: var(--surface-card);
  box-shadow: var(--shadow-lg);
}

.rd-dialog__panel--wide { max-width: 680px; }

.rd-dialog__head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-6);
  padding: var(--space-8) var(--space-8) var(--space-6);
}

.rd-dialog__heading {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.rd-dialog__title {
  font-family: var(--font-serif);
  font-size: var(--text-xl);
  line-height: var(--leading-tight);
  color: var(--text-heading);
}

.rd-dialog__subtitle {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

.rd-dialog__close { margin-left: auto; }

.rd-dialog__body { padding: 0 var(--space-8) var(--space-8); }

/* A dialog is rendered SHUT and opened by script (assets/dialog.js), so the
   attribute that shuts it has to win.

   [hidden] is a UA rule at specificity (0,1,0), and .rd-dialog above sets
   display:flex at exactly (0,1,0) as well. Equal specificity is broken by
   document order and this sheet ships after the UA's, so without the rule
   below a shut dialog paints its scrim over the whole page and swallows the
   screen it was waiting behind. Same tie the auth surface and the public page
   each hit before this one. */
.rd-dialog[hidden] { display: none; }

/* Set on <body> while a dialog is open. Scrolling the list behind the scrim
   is how a modal ends up off the top of a phone screen with no way back. */
.rd-dialog-open { overflow: hidden; }

/* ---- Email preview (ui_kits/invite, "IN THE EMAIL") --------------------
   The sentence an invitation's recipient will read, shown under the note
   field of every invite dialog (PRD #456). A sunken card so it reads as a
   quotation of something else rather than a field of this form; the kicker
   is the eyebrow's mono treatment, kept local because the dialogs live on
   the admin shell too, where app.css is not loaded. */
.rd-emailpreview {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-5) var(--space-6);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--bg-sunken);
}

.rd-emailpreview__kicker {
  font: var(--type-meta);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}

.rd-emailpreview__line {
  font: var(--type-body-sm);
  color: var(--text-body);
}

.rd-emailpreview__line strong { color: var(--text-heading); }

.rd-dialog__footer {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-5);
  padding: var(--space-6) var(--space-8);
  border-top: var(--border-width) solid var(--border-subtle);
  border-radius: 0 0 var(--radius-xl) var(--radius-xl);
  background: var(--bg-sunken);
}
/* assets/card.css */
/* Refdeck card layer — the business card itself.

   WHY ITS OWN LAYER. The card is the one object that appears on BOTH sides of
   the product: the member's My Cards list and editor preview (app.css), the
   deck header and the public card page a QR scan lands on (deck.css), and the
   contact surfaces in between. Putting it in either of those layers would
   make the other one reach across a surface boundary to style it, so it sits
   between the primitives it composes and the two surface layers that place
   it — each of which can still win a tie against it.

   THE ACCENT IS DATA, SCOPED TO THE CARD. deck.css redefines --accent once
   per request from the deck's palette colour; a card does the same thing one
   level down, because the My Cards list shows Paradigm Wellness's teal beside
   Belay Mortgage Group's bronze on one page. Every rule below reads
   var(--accent) and the template writes it on the card element:

       <article class="rd-refcard" style="--accent: {{.AccentHex}}">

   so the colour is the card's own without this layer knowing any colour by
   value. The hexes are the design system's 600-level hues (store.DeckAccents),
   which is what keeps white-on-accent legible.

   THE FLIP IS A DISCLOSURE, NOT SCRIPT. The QR button turns the card over.
   With no build step (AGENTS.md hard constraint 4) that is <details>/<summary>
   — the same primitive the nav menu is built from — so the back is reachable
   by keyboard, announced as the expandable region it is, and works before any
   script runs. .rd-refcard--static drops the control for the places that show
   a card as an illustration rather than as something to turn over. */

/* ---- The card ----------------------------------------------------------
   7:4 is a business card. The two faces stack in the same grid cell rather
   than being absolutely positioned, so the card's height is the taller face's
   height at every width and neither face can overflow the other. */
.rd-refcard {
  position: relative;
  display: grid;
  width: 100%;
  aspect-ratio: 7 / 4;
  min-height: 0;
  font-family: var(--font-sans);
  color: var(--text-body);
}

.rd-refcard__face {
  grid-area: 1 / 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  background: var(--surface-card);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
}

/* The accent rule along the top edge is the whole of the card's branding —
   the design system's "structure from hairlines, not colour blocks". */
.rd-refcard__rule {
  flex: none;
  height: 4px;
  background: var(--accent);
}

.rd-refcard__body {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding: var(--space-5) var(--space-6) var(--space-5);
}

/* ---- Person first ------------------------------------------------------
   The name leads and the company reads as an attribute beneath it. Refdeck
   refers people you know, not companies, so a card that led with the company
   would be describing the wrong object. */
.rd-refcard__who {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  min-width: 0;
}

/* 34px is carried from the prototype: the avatar scale's sm is 28 and md is
   36, and neither leaves the two lines of text beside it on the baseline the
   card's cramped height needs. */
.rd-refcard__monogram {
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: var(--radius-pill);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-serif);
  font-size: var(--text-base);
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, var(--surface-card));
}

/* The owner's headshot fills the monogram's own frame — the face changes,
   the geometry beside it does not. */
.rd-refcard__monogram--photo {
  overflow: hidden;
}

.rd-refcard__photoimg {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.rd-refcard__names {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.rd-refcard__person {
  font-family: var(--font-serif);
  font-size: var(--text-lg);
  line-height: var(--leading-tight);
  color: var(--text-heading);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.rd-refcard__tagline {
  font-size: var(--text-xs);
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ---- The company, last -------------------------------------------------
   margin-top:auto pins this block to the bottom of the body, so cards whose
   taglines wrap differently still line their company rows up across a grid. */
.rd-refcard__where {
  margin-top: auto;
  padding-top: var(--space-4);
  min-width: 0;
}

.rd-refcard__business {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font: var(--type-meta);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--accent);
  min-width: 0;
}

.rd-refcard__businessname {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The company's mark, contain-fit: a logo is a drawing, not a face, and
   cropping one is how a wordmark loses its word. */
.rd-refcard__logo {
  flex: none;
  width: 16px;
  height: 16px;
  object-fit: contain;
}

/* The word beside a number that says which number it is. */
.rd-refcard__reach-kind {
  color: var(--text-muted);
}

.rd-refcard__role {
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  font-weight: var(--weight-medium);
  line-height: var(--leading-snug);
  color: var(--text-body);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.rd-refcard__reach {
  margin-top: var(--space-4);
  display: flex;
  align-items: center;
  gap: var(--space-5);
  min-width: 0;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--text-body);
}

.rd-refcard__reach-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
  white-space: nowrap;
}

.rd-refcard__reach-item span {
  overflow: hidden;
  text-overflow: ellipsis;
}

.rd-refcard__icon {
  flex: none;
  width: 12px;
  height: 12px;
  color: var(--text-muted);
}

/* ---- The foot ----------------------------------------------------------
   Website and service area, on the sunken tint so the card reads as two
   zones without a second shadow. */
.rd-refcard__foot {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-4) var(--space-6);
  min-width: 0;
  border-top: var(--border-width) solid var(--border-subtle);
  background: var(--bg-page);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
}

.rd-refcard__site {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
  color: var(--accent);
}

.rd-refcard__site .rd-refcard__icon { color: inherit; }

.rd-refcard__site span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rd-refcard__area {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  white-space: nowrap;
  color: var(--text-muted);
}

/* ---- The flip ----------------------------------------------------------
   <details> wraps the whole card so the open state can select the back face.
   The marker is dropped on both engines; the summary is styled as the small
   square control in the card's top corner. */
.rd-refcard__flip {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: block;
}

.rd-refcard__flip > summary {
  position: absolute;
  top: var(--space-5);
  right: var(--space-6);
  z-index: 2;
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
  color: var(--text-muted);
  cursor: pointer;
  list-style: none;
  transition: var(--transition-control);
}

.rd-refcard__flip > summary::-webkit-details-marker { display: none; }
.rd-refcard__flip > summary::marker { content: ""; }

.rd-refcard__flip > summary:hover {
  background: var(--surface-hover);
  color: var(--text-body);
}

/* The back face is the disclosure's content, so it exists in the DOM only
   while the card is turned over — which is also what keeps its Save contact
   action out of the tab order on a card nobody has flipped. */
.rd-refcard__back {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
}

.rd-refcard__back-body {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-7);
  min-height: 0;
  padding: var(--space-5) var(--space-7);
}

/* 64px: the iconography rule reserves 54–68px for the QR mark alone, which is
   the one glyph allowed above the 24px ceiling. */
.rd-refcard__qr {
  flex: none;
  width: 64px;
  height: 64px;
  color: var(--text-heading);
}

.rd-refcard__back-lines {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-4);
  min-width: 0;
}

.rd-refcard__slug {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--accent);
}

.rd-refcard__note {
  font-size: var(--text-2xs);
  color: var(--text-muted);
}

/* A card shown as an illustration rather than as something to turn over —
   the deck editor's choices, the stacked cards on a contact page. */
.rd-refcard--static .rd-refcard__flip { display: none; }

/* ---- A stack -----------------------------------------------------------
   One business, more than one card. The second sheet is a shape, not a copy:
   it carries no content, so the count pill beside it is what says how many.
   The 14px top padding is the room the offset sheet leans into. */
.rd-refcard-stack {
  position: relative;
  display: block;
  padding-top: var(--space-5);
}

.rd-refcard-stack__sheet {
  position: absolute;
  inset: var(--space-5) 0 0 0;
  transform: translate(9px, -9px) rotate(0.6deg);
  background: var(--surface-card);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-xs);
}

.rd-refcard-stack__card {
  position: relative;
}

.rd-refcard-stack__count {
  position: absolute;
  top: 0;
  right: calc(var(--space-3) * -1);
  z-index: 2;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  background: var(--text-heading);
  color: var(--text-on-dark);
  border-radius: var(--radius-pill);
  padding: var(--space-1) var(--space-4);
}

/* ---- Phone -------------------------------------------------------------
   The single 720px breakpoint. A card keeps its proportions — it is a
   business card, and 7:4 at 358px is still a business card — but the type
   inside it stops assuming desktop width. */
@media (max-width: 720px) {
  .rd-refcard__body { padding: var(--space-5); }
  .rd-refcard__foot { padding: var(--space-4) var(--space-5); }
  .rd-refcard__reach { gap: var(--space-4); }
}
/* assets/app.css */
/* Refdeck signed-in app — the page INTERIORS.

   SCOPE, and the line between this file and shell.css: shell.css draws the
   chrome a member never leaves (the fixed top bar, the one nav that is a
   sidebar on desktop and the hamburger's dropdown on a phone) and the page
   frame. This file
   styles what sits INSIDE that chrome — the screens themselves.

   Like the deck, marketing and auth layers, this one COMPOSES the primitives
   rather than restating them: it puts .rd-card into a grid and reaches inside
   .rd-search to size it for a hero. That is why it is registered after the
   component layer in assets.go — a rule written against a .rd-* internal has
   to be able to win the tie against the component it is composing.

   Every value here is a token. A literal colour is a bug the design sync
   cannot reach; a literal length is only allowed where it is a measurement of
   the layout itself rather than a scale step.

   First screen in: the dashboard (.rd-dash*). Its siblings — contacts,
   places, decks, groups — belong in this file too as they are ported. */

/* ─── Spacing inside the app shell ─────────────────────────────────────
   shell.css restores browser-default flow margins on plain semantic markup
   inside .app-shell, because the screens not yet ported are exactly that and
   would otherwise collapse into a wall of text. Two consequences bite any
   screen that IS ported, and both are handled here rather than screen by
   screen:

   1. That restoration is `.app-shell :is(p, ul, section, ...)`, whose
      specificity is (0,1,1). A bare block class is (0,1,0), so a block's own
      `margin-block: 0` LOSES no matter how late its layer ships — the one
      case where document order does not save you. Hence the descendant
      selector below, which is (0,2,1).

   2. A design-system component already spaces its own children (.rd-card
      pads, .rd-empty-state gaps). Left alone the two compound, which is what
      made the empty states twice as tall as designed.

   So: an element carrying a design-system class owns its own spacing. The
   attribute pair is the BEM-safe way to say "a class starting with rd-" —
   *= alone would also match a class merely containing it. */
.app-shell :is(p, h1, h2, h3, h4, ul, ol, dl, table, form, section, nav, blockquote)[class^="rd-"],
.app-shell :is(p, h1, h2, h3, h4, ul, ol, dl, table, form, section, nav, blockquote)[class*=" rd-"] {
  margin-block: 0;
}

/* Which leaves ONE place for the dashboard's vertical rhythm: a single column
   gap on the wrapper, rather than a margin on each block that has to be
   argued for individually. */
.rd-dash {
  display: flex;
  flex-direction: column;
  gap: var(--space-10);
}

/* ─── Dashboard ────────────────────────────────────────────────────────
   Two screens wearing one URL. Until a member has a contact, a deck and a
   published deck the dashboard IS the onboarding checklist; after that it is
   the search home. Both are built from the blocks below. */

.rd-dashwelcome {
  font: var(--type-title);
  color: var(--text-heading);
}

/* ---- The search hero -------------------------------------------------
   The PRD's core promise is answering "who was the plumber at the Seattle
   house?" in seconds, so on the one screen a member lands on every session
   the search is the page rather than a field in the corner of it. The top
   bar's compact quicksearch stays — this is the same destination at the
   size the promise deserves. */
.rd-dashhero {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: var(--space-9);
  border: var(--border-width) solid var(--teal-100);
  border-radius: var(--radius-lg);
  background: var(--accent-quiet);
}

.rd-dashhero__form {
  display: flex;
  gap: var(--space-5);
  align-items: center;
}

/* The search box takes the room; the button keeps its own width. */
.rd-dashhero__form > .rd-search {
  flex: 1;
  min-width: 0;
}

.rd-dashhero__hint {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

/* ---- Shortcut cards --------------------------------------------------
   Three ways in, sized to stay side by side on a desktop and to stack on a
   phone without a breakpoint of their own: auto-fit collapses the track
   count as the container narrows. */
.rd-dashshortcuts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr));
  gap: var(--space-6);
  padding: 0;
  list-style: none;
}

.rd-dashshortcut {
  display: flex;
  align-items: center;
  gap: var(--space-5);
}

.rd-dashshortcut__icon {
  flex: 0 0 auto;
  color: var(--accent);
}

.rd-dashshortcut__label {
  font: var(--type-button);
  color: var(--text-heading);
}

/* Prose, so body type — not --type-meta, which the design system sets in the
   mono face for timestamps and counts. */
.rd-dashshortcut__hint {
  display: block;
  font: var(--type-body-sm);
  color: var(--text-muted);
}

/* ---- Panels ----------------------------------------------------------
   A titled section with an optional link out to the full screen. The heading
   and the link sit on one line so the panel reads as a summary OF something
   rather than as the thing itself. */
.rd-dashpanel {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

.rd-dashpanel__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-5);
}

.rd-dashpanel__title {
  font: var(--type-subhead);
  color: var(--text-heading);
}

.rd-dashpanel__link {
  font: var(--type-label);
  color: var(--text-link);
  white-space: nowrap;
}

/* ---- Recent referral activity ---------------------------------------
   A feed, not a table: the pipeline table with its status controls lives at
   /app/referrals, and squeezing five columns onto a phone here would only
   produce a worse copy of it. Each row answers who, for what, and where it
   came from — enough to decide whether to open the real screen. */
.rd-dashreferrals {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: 0;
  list-style: none;
}

/* Composed on .rd-card in the markup, the way .rd-contact-card is: the card
   owns the surface, the border and the radius, and this block owns only the
   arrangement of what sits on it. */
.rd-dashreferrals__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3) var(--space-5);
}

.rd-dashreferrals__who {
  font: var(--weight-semibold) var(--text-sm)/var(--leading-snug) var(--font-sans);
  color: var(--text-heading);
}

/* Takes the rest of the row when there is room and drops to a line of its own
   when there is not. The flex-basis is what makes that happen: with min-width
   0 this shrank instead of wrapping, and at a phone's width the subject line
   collapsed to one word per line beside a badge that kept its full size. */
.rd-dashreferrals__for {
  flex: 1 1 16rem;
  font: var(--type-body-sm);
  color: var(--text-body);
}

/* Status and time travel together as one trailing group, and are pushed to
   the end so they line up down the right edge whatever the names beside them
   are. Grouping them is what keeps adjacent rows consistent: left loose, the
   badge joined the subject line on a row whose subject happened to wrap short
   and dropped below it on a row whose did not. */
.rd-dashreferrals__meta {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  margin-left: auto;
}

.rd-dashreferrals__when {
  font: var(--type-meta);
  color: var(--text-muted);
  white-space: nowrap;
}

/* ---- Search results --------------------------------------------------
   Grouped by what was matched — contacts, places, tags — because the query
   is deliberately one box over three kinds of thing. */
.rd-dashresults {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
}

.rd-dashresults__group {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.rd-dashresults__title {
  font: var(--type-label);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}

.rd-dashresults__list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  padding: 0;
  list-style: none;
}

.rd-dashresults__item {
  font: var(--type-body-sm);
}

.rd-dashresults__meta {
  color: var(--text-muted);
}

/* ---- Onboarding checklist -------------------------------------------
   The empty state, and the only screen a brand-new member sees. It replaces
   the search home rather than sitting above it: a search box over an empty
   rolodex answers nothing, and three numbered steps say what to do instead.

   The steps are re-derived from real state on every render, so a done step is
   a fact rather than a stored flag — which is why the marker below is styled
   from a modifier the handler sets, not from a checkbox. */
.rd-dashsteps {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: 0;
  list-style: none;
  counter-reset: rd-dashstep;
}

/* Composed on .rd-card, like every other row on this screen. */
.rd-dashstep {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  counter-increment: rd-dashstep;
}

/* The marker carries the step's number from the counter, so reordering the
   list in the handler cannot leave a hand-typed "2." behind on step three. */
.rd-dashstep__marker {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: var(--control-h-sm);
  height: var(--control-h-sm);
  border-radius: var(--radius-pill);
  background: var(--bg-sunken);
  color: var(--text-muted);
  font: var(--type-label);
}

.rd-dashstep__marker::before {
  content: counter(rd-dashstep);
}

.rd-dashstep__label {
  font: var(--type-button);
  color: var(--text-heading);
}

/* Prose, so body type — see .rd-dashshortcut__hint. */
.rd-dashstep__hint {
  display: block;
  font: var(--type-body-sm);
  color: var(--text-muted);
}

/* A finished step still shows what it was — it is a record of progress, not a
   row to hide — so it goes quiet rather than away. */
.rd-dashstep--done {
  background: var(--bg-sunken);
  box-shadow: var(--shadow-none);
}

.rd-dashstep--done .rd-dashstep__marker {
  background: var(--accent);
  color: var(--accent-on);
}

/* The tick replaces the number on a done step, which is what makes the list
   scannable: every remaining step still reads as a number. */
.rd-dashstep--done .rd-dashstep__marker::before {
  content: "✓";
}

.rd-dashstep--done .rd-dashstep__label {
  color: var(--text-muted);
}

/* The onboarding panel's art (PRD #392): the starting-a-deck scene beside the
   checklist, sharing the hero's 1.05/0.95 split. The head spans both columns;
   the art keeps its own 4:3 (width from the slot, height auto — never an
   object-fit crop). The panel's flex gap becomes the grid gap unchanged.

   Every child is placed BY NAME rather than left to grid auto-flow, because
   auto-flow cannot build this layout: the note between the steps and the art
   spans the full width, which ends its row, so nothing declared after it can
   ever reach column two. Auto-flow put the art in column one BELOW the note —
   the panel reserved 0.95fr for a scene that was never in it, and the
   checklist stood next to nothing. Naming the areas also means a block added
   to this panel later has to say where it goes (dashboardlayout_test.go). */
.rd-dashpanel--onboarding {
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  grid-template-areas:
    "head  head"
    "steps art"
    "note  note";
  align-items: center;
}

/* The head spans both columns — and STACKS, unlike the summary panels'. The
   shared .rd-dashpanel__head is a space-between row built for "title ... link"
   ("Recent referrals" opposite "All referrals"), where pushing the second
   child to the far edge is the whole point. This panel's second child is not a
   link but a prose lede, and space-between treats it identically: measured on
   a running stack at 1280px it landed on the heading's own line, 527px to its
   right and flush against the panel edge, with .rd-dashpanel__lede's prose
   measure inert because a flex row sizes the item and never consults its
   max-width. Stacking puts the sentence under the heading it introduces and
   hands the measure back its job. */
.rd-dashpanel--onboarding .rd-dashpanel__head {
  grid-area: head;
  flex-direction: column;
  /* The base head is a space-between ROW, shaped for a heading and the short
     nowrap link that says where the panel's full screen is. This head carries
     a lede instead — prose, which wraps — and on that row space-between flung
     the sentence to the far edge of a 1000px panel and squeezed it into what
     the heading left, wrapping the heading itself in two on a phone. A lede
     belongs under the thing it introduces. */
  align-items: flex-start;
  justify-content: flex-start;
  gap: var(--space-4);
}

.rd-dashpanel--onboarding .rd-dashsteps {
  grid-area: steps;
}

/* The kit's lede in place of a panel title: the one sentence that says whose
   recommendation the member already has, and what the three steps are for. */
.rd-dashpanel__lede {
  max-width: var(--prose-max);
  font: var(--type-body);
  color: var(--text-body);
}

.rd-dashstep__body {
  flex: 1 1 auto;
  min-width: 0;
}

/* A step carries its named action and, where the same step can be done by
   asking the other person for their own details instead, the quieter invite
   beside it: two buttons on one row, not two rows. */
.rd-dashstep__actions {
  flex: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

.rd-dashstep__action {
  flex: none;
}

/* Where the referral questions wait — under the steps AND under the art,
   spanning the panel like the head does. */
.rd-dashsteps__note {
  grid-area: note;
  font: var(--type-body-sm);
  color: var(--text-muted);
}

.rd-dashsteps__art {
  grid-area: art;
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius-lg);
}

/* ---- Your Refdeck ------------------------------------------------------
   The deck the member is managing (homedecks.go), leading the search home:
   its name and address, its state, two numbers, and the actions a member
   comes back for, with the switcher under them for a member with several.
   One card, in the shape the Refdecks list's cards have. */
.rd-homedeck {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.rd-homedeck__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-5);
}

.rd-homedeck__identity {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.rd-homedeck__name {
  font: var(--type-heading);
  color: var(--text-heading);
  text-decoration: none;
  overflow-wrap: anywhere;
}

.rd-homedeck__name:hover { text-decoration: underline; }

.rd-homedeck__slug {
  font: var(--type-meta);
  color: var(--text-muted);
  text-decoration: none;
  overflow-wrap: anywhere;
}

.rd-homedeck__stats {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

.rd-homedeck__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

.rd-homedeck__actions form { margin-block: 0; }

/* The switcher: a labelled select and, with script off, its button. */
.rd-homedeck__switch {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-4);
  padding-top: var(--space-5);
  border-top: var(--border-width) solid var(--border-subtle);
}

.rd-homedeck__switch .rd-field {
  flex: 1 1 14rem;
}

/* ---- Refdecks from people you trust ------------------------------------
   Other members' published decks that this member has a reason to see
   (homedecks.go), as dense rows, and under them the people on more than one
   of them. */
.rd-trustdecks {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: 0;
  list-style: none;
}

.rd-trustdeck {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.rd-trustdeck__name {
  font: var(--type-label);
  color: var(--text-heading);
  text-decoration: none;
}

.rd-trustdeck__name:hover { text-decoration: underline; }

.rd-trustdeck__meta {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

.rd-trustshared {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding-top: var(--space-5);
  border-top: var(--border-width) solid var(--border-subtle);
}

.rd-trustshared__title {
  font: var(--type-label);
  color: var(--text-heading);
}

.rd-trustshared__lede {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

.rd-trustshared__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: 0;
  list-style: none;
}

.rd-trustshared__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3) var(--space-5);
}

.rd-trustshared__who {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.rd-trustshared__name {
  font: var(--type-label);
  color: var(--text-heading);
  text-decoration: none;
}

.rd-trustshared__name:hover { text-decoration: underline; }

.rd-trustshared__business {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

.rd-trustshared__count {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  font: var(--type-body-sm);
  color: var(--text-muted);
}

/* ---- Make another Refdeck ---------------------------------------------
   The step after the checklist (firstdeck.go): one card of prose and a
   secondary action, in a summary panel like the referrals'. Secondary
   because the member whose one Refdeck is finished has finished; a second is
   an offer, not the next thing to do. */
.rd-dashmore {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-5);
}

.rd-dashmore__body {
  max-width: var(--prose-max);
  font: var(--type-body);
  color: var(--text-body);
}

.rd-dashmore__action {
  flex: none;
}

/* ---- Groups ----------------------------------------------------------
   The nav's Groups item has no index route of its own yet and addresses this
   section's anchor, so it renders in BOTH dashboard states. */
.rd-dashgroups {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: 0;
  list-style: none;
}

/* Composed on .rd-card, like every other row on this screen. */
.rd-dashgroup {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3) var(--space-5);
}

.rd-dashgroup__name {
  font: var(--weight-semibold) var(--text-sm)/var(--leading-snug) var(--font-sans);
}

.rd-dashgroup__meta {
  font: var(--type-meta);
  color: var(--text-muted);
}

/* ---- Sign out --------------------------------------------------------
   A quiet footer action rather than a button in the flow: it is the one
   control on this page nobody comes here to press. (PRD 391 moves it into a
   shared account menu; until that ships, losing it would leave a session with
   no way out but expiry.) */
.rd-dashsignout {
  margin-block: var(--space-12) 0;
  padding-top: var(--space-8);
  border-top: var(--border-width) solid var(--border-subtle);
}

/* ═══ Contact add/edit form ══════════════════════════════════════════════
   The second screen into this layer. Its wrapper is .rd-workspace rather than
   the dashboard's .rd-dash because it is the generic one — a column of blocks
   with one gap — and the ports after this (places, decks, groups, settings)
   are the same shape.

   Nothing below takes its flow margins back by hand: the boundary rule at the
   top of this file already does it for anything carrying an rd- class, which
   is every flow element this screen renders. */

/* ─── The page itself ──────────────────────────────────────────────────── */

.rd-workspace {
  display: flex;
  flex-direction: column;
  gap: var(--space-9);
}

/* Forms read down a single measured column. The full --page-max width is for
   pages that lay out data; a form read at 1200px makes every label a
   sightline away from its field.

   shell.css sizes the page with `.app-shell > main` at (0,1,1), so this has to
   name the shell too or it would never apply. */
.app-shell .rd-workspace--form {
  max-width: var(--deck-max);
}

/* The welcome steps (PRD #454) put the auth surface's single card inside the
   app shell: the member is signed in, but the page is one question, not a
   workspace. .rd-authpage sizes and stacks the card; this centres it. */
.rd-welcome {
  align-items: center;
}

.rd-welcome__pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-6);
}

.rd-welcome__back {
  align-self: center;
}

.rd-pagehead {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.rd-pagehead__back {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  align-self: flex-start;
  font: var(--type-body-sm);
  color: var(--text-muted);
  text-decoration: none;
  transition: var(--transition-control);
}

.rd-pagehead__back:hover {
  color: var(--text-link-hover);
}

.rd-pagehead__title {
  font: var(--type-title);
  color: var(--text-heading);
}

.rd-pagehead__lede {
  max-width: var(--prose-max);
  font: var(--type-body);
  color: var(--text-body);
}

/* ─── Notices ──────────────────────────────────────────────────────────── */

.rd-formnote {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-6);
  border: var(--border-width) solid transparent;
  border-radius: var(--radius-md);
  font: var(--type-body-sm);
}

.rd-formnote--danger {
  background: var(--status-danger-bg);
  border-color: var(--red-100);
  color: var(--status-danger);
}

.rd-formnote--success {
  background: var(--status-success-bg);
  border-color: var(--green-100);
  color: var(--status-success);
}

/* The third tone the notices actually need: an outcome that is neither a win
   nor a fault — an import nobody picked anything from, a consent declined, a
   staging window that closed. Without it those all borrowed --success and
   read as the opposite of their own sentence. */
.rd-formnote--info {
  background: var(--status-info-bg);
  border-color: var(--blue-100);
  color: var(--status-info);
}

/* ─── The form stack ───────────────────────────────────────────────────── */

.rd-formstack {
  display: flex;
  flex-direction: column;
  gap: var(--space-7);
}

.rd-formstack__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-5);
}

/* ─── Field group: the public/private split ──────────────────────────────
   This is the criterion. Refdeck's promise is that one half of a contact
   travels — onto Refdecks, into group directories, onto the public profile
   page — and the other half never leaves the owner's account. The form is
   where a member decides which half a personal mobile number belongs to, so
   the two halves cannot be two <legend>s on one surface: the split has to be
   legible before anything is read.

   Three signals carry it, none of them the text alone: a different card
   surface (paper vs sunken), a coloured rail down the edge, and a marked
   icon — the globe and the padlock the dashboard kit already uses for exactly
   this distinction (ui_kits/dashboard/ContactsView.jsx badges private values
   and gives notes a lock eyebrow).

   Teal is the system's "shared" accent and bronze its "yours" highlight, so
   the colours are the ones the rest of the product already means by them. */

.rd-fieldgroup {
  display: flex;
  flex-direction: column;
  gap: var(--space-7);
  border-left-style: solid;
  border-left-width: var(--space-2);
}

.rd-fieldgroup__head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-5);
}

/* Unmodified, the mark is quiet: the group that carries a contact's name is
   not making a claim about who can see it. The two that are get a colour. */
.rd-fieldgroup__mark {
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: var(--space-10);
  height: var(--space-10);
  border-radius: var(--radius-pill);
  background: var(--bg-sunken);
  color: var(--text-muted);
}

.rd-fieldgroup__heading {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}

.rd-fieldgroup__title {
  font: var(--type-meta);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}

.rd-fieldgroup__note {
  max-width: var(--prose-max);
  font: var(--type-body-sm);
  color: var(--text-body);
}

/* Two columns of short fields; the long ones opt out and run the width. */
.rd-fieldgroup__fields {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-6);
}

/* The read-only pages render these fields as a <dl>, and a dd carries a
   default indent from the browser stylesheet — the one hanging indent in a
   product whose labels and values otherwise sit flush. Reset it here, where
   the dl lives, rather than globally: a dl somewhere else may want to be a
   definition list. */
dl.rd-fieldgroup__fields {
  margin: 0;
}

dl.rd-fieldgroup__fields dd {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--text-body);
  overflow-wrap: anywhere;
}

.rd-fieldgroup__wide {
  grid-column: 1 / -1;
}

.rd-fieldgroup--public {
  border-left-color: var(--accent);
}

.rd-fieldgroup--public .rd-fieldgroup__mark {
  background: var(--accent-soft);
  color: var(--accent);
}

.rd-fieldgroup--public .rd-fieldgroup__title {
  color: var(--teal-700);
}

.rd-fieldgroup--private {
  border-left-color: var(--bronze-500);
}

.rd-fieldgroup--private .rd-fieldgroup__mark {
  background: var(--highlight-soft);
  color: var(--bronze-600);
}

.rd-fieldgroup--private .rd-fieldgroup__title {
  color: var(--bronze-700);
}

/* ─── Secondary panels ─────────────────────────────────────────────────── */

.rd-panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

.rd-panel__title {
  font: var(--type-heading);
  color: var(--text-heading);
}

.rd-panel__body {
  max-width: var(--prose-max);
  font: var(--type-body-sm);
  color: var(--text-body);
}

/* A field and the button that acts on it, on one line. */
.rd-panel__row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-5);
}

.rd-panel__row > .rd-field {
  flex: 1 1 var(--sidebar-w);
  min-width: 0;
}

/* ─── Associated places ────────────────────────────────────────────────── */

.rd-placelist {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding-left: var(--space-0);
  list-style: none;
}

.rd-placerow {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-5) var(--space-6);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--surface-card);
}

.rd-placerow__icon {
  flex: 0 0 auto;
  color: var(--accent);
}

.rd-placerow__name {
  font: var(--type-label);
  color: var(--text-heading);
}

/* Pushed to the trailing edge rather than the row stretched, so the row keeps
   its height when the action is a one-button form. */
.rd-placerow__action {
  margin-left: auto;
}

/* ─── My Refdecks ─────────────────────────────────────────────────────────
   One card per deck, laid out from design-system/ui_kits/dashboard's
   RefdecksView: identity and status on the top line, the numbers and the QR
   in the middle, the actions on a ruled foot.

   The card COMPOSES .rd-card rather than restating it, so padding, radius and
   elevation stay the primitive's business and only the arrangement is here.

   The criterion also names a view count. Nothing in this product counts a
   deck view — no column, no counter, no increment on the public deck handler
   — and PRD #377 holds internal/store and every migration out of scope, so
   the stat row deliberately carries Contacts alone. It is a row rather than a
   single figure precisely so that the missing stat is one more child when the
   data exists, not a re-layout. */

/* The page head gains a row so a screen's primary action can sit beside the
   title instead of below it. .rd-pagehead itself stays a column: the lede
   still belongs under both. */
.rd-pagehead__row {
  display: flex;
  align-items: center;
  gap: var(--space-5);
}

.rd-pagehead__action {
  flex: none;
  margin-left: auto;
}

.rd-decklist {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 26rem), 1fr));
  gap: var(--space-6);
}

.rd-deckcard {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

.rd-deckcard__head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-5);
}

/* min-width on a flex child defaults to auto, which refuses to shrink below
   the longest word — a long deck name would push the badge off the card
   rather than ellipsing. Zero here is what lets the truncation below work. */
.rd-deckcard__identity {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

/* ─── The rolodex — /app/contacts ────────────────────────────────────────
   Ported from design-system/ui_kits/dashboard/ContactsView.jsx. The kit's
   ContactRow is one <button> with everything inside it; here the chips are
   links that filter, so the row is a flex line whose name is the link and
   whose chips, star and archive control are siblings. Same density, valid
   markup, every affordance independently reachable.

   The row is a grid rather than a plain flex line so the identity column can
   absorb every spare pixel while the star, the chips and the archive button
   keep their intrinsic widths — a flex row would let a long business name
   push the chips off the end. */

.rd-rolodex {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* The archived view is the same screen in a quieter register: these contacts
   are deliberately out of the working rolodex, and the list should say so
   without a banner. */
.rd-rolodex--archived .rd-rolodex__list {
  background: var(--bg-sunken);
}

.rd-rolodex--archived .rd-rolodex__name {
  color: var(--text-body);
}

.rd-rolodex__head {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  flex-wrap: wrap;
}

.rd-rolodex__title {
  font: var(--type-title);
  color: var(--text-heading);
  margin-block: 0;
}

/* The count is the kit's quiet number beside the heading, not a badge. */
.rd-rolodex__count {
  color: var(--text-faint);
  font-weight: var(--weight-regular);
}

.rd-rolodex__actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

.rd-rolodex__search {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  margin-block: var(--space-6);
}

.rd-rolodex__search > .rd-search {
  flex: 1 1 auto;
  min-width: 0;
}

/* What is currently narrowing the list, and the way out. */
.rd-rolodex__applied {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-4);
  margin-block: var(--space-6);
}

.rd-rolodex__appliedlabel {
  font: var(--type-meta);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-faint);
}

.rd-rolodex__list {
  list-style: none;
  margin-block: 0;
  padding: 0;
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--surface-card);
  overflow: hidden;
}

.rd-rolodex__row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-4) var(--space-6);
  border-bottom: var(--border-width) solid var(--border-subtle);
  /* The kit marks the active row with an accent rule down its left edge.
     Every row carries the transparent version so hovering cannot shift the
     content sideways by two pixels. */
  border-left: var(--space-1) solid transparent;
  transition: var(--transition-control);
}

.rd-rolodex__row:last-child {
  border-bottom: none;
}

.rd-rolodex__row:hover {
  background: var(--surface-hover);
  border-left-color: var(--accent);
}

/* ─── The favorite star ───────────────────────────────────────────────── */

.rd-rolodex__star {
  margin-block: 0;
  display: flex;
}

.rd-rolodex__starbtn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--control-h-sm);
  height: var(--control-h-sm);
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  /* Unpressed is a hollow outline in the faint ink; pressed fills it with
     the highlight, which is the one place this screen uses that colour. */
  color: var(--text-faint);
  cursor: pointer;
  transition: var(--transition-control);
}

.rd-rolodex__starbtn:hover {
  background: var(--surface-hover);
  color: var(--highlight);
}

.rd-rolodex__starbtn:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

.rd-rolodex__starbtn[aria-pressed="true"] {
  color: var(--highlight);
}

/* ─── Identity: avatar, name, and the business · profession line ──────── */

.rd-rolodex__link {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  min-width: 0;
  text-decoration: none;
  color: inherit;
}

.rd-rolodex__link:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--radius-sm);
}

.rd-rolodex__identity {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.rd-deckcard__name {
  overflow: hidden;
  font: var(--type-heading);
  color: var(--text-heading);
  text-decoration: none;
  text-overflow: ellipsis;
  white-space: nowrap;
  transition: var(--transition-control);
}

.rd-rolodex__name {
  font: var(--type-label);
  font-size: var(--text-base);
  color: var(--text-heading);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rd-rolodex__meta {
  font: var(--type-body-sm);
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ─── Chips: places, then tags ─────────────────────────────────────────── */

.rd-rolodex__chips {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--space-3);
}

.rd-rolodex__place {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  font: var(--type-meta);
  color: var(--text-muted);
  text-decoration: none;
  white-space: nowrap;
  transition: var(--transition-control);
}

.rd-deckcard__name:hover {
  color: var(--text-link-hover);
}

/* The slug is an address, so it is set in the mono face wherever it appears —
   the same treatment the public deck's own footer gives it. */
.rd-deckcard__slug {
  overflow: hidden;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-muted);
  text-decoration: none;
  text-overflow: ellipsis;
  white-space: nowrap;
}

a.rd-deckcard__slug:hover {
  color: var(--text-link-hover);
}

/* Reaching inside the badge primitive to place it. This layer ships after the
   component layer for exactly this: (0,2,1) beats the component's own rule. */
.rd-deckcard__head > .rd-badge {
  flex: none;
  margin-left: auto;
}

.rd-deckcard__body {
  display: flex;
  align-items: center;
  gap: var(--space-6);
}

.rd-deckcard__stats {
  display: flex;
  flex: 1 1 auto;
  gap: var(--space-9);
}

.rd-deckstat {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.rd-deckstat__value {
  font-family: var(--font-sans);
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  color: var(--text-heading);
}

.rd-deckstat__label {
  font: var(--type-meta);
  color: var(--text-faint);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

/* The QR is sized by the img's own width and height attributes, which also
   reserve the space before the PNG arrives. Only the box is styled here. */
.rd-deckcard__qrlink {
  display: block;
  flex: none;
  border-radius: var(--radius-sm);
  line-height: 0;
  transition: var(--transition-control);
}

.rd-deckcard__qrlink:hover {
  opacity: 0.72;
}

.rd-deckcard__qr {
  display: block;
  height: auto;
  max-width: 100%;
  border-radius: var(--radius-sm);
}

.rd-deckcard__foot {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding-top: var(--space-5);
  border-top: var(--border-width) solid var(--border-subtle);
}

/* ─── Phone ───────────────────────────────────────────────────────────────
   720px is the design system's own breakpoint (the responsive.js in each
   ui_kits screen sets REFDECK_MOBILE_MAX to it), the same one shell.css turns
   the nav on. Written without a glob on purpose: a path holding a star and a
   slash closes this comment early, and everything after it — this whole media
   block, once — is dropped by the parser without a word. */
@media (max-width: 720px) {
  .rd-fieldgroup__fields {
    grid-template-columns: minmax(0, 1fr);
  }

  .rd-formstack__actions > .rd-button {
    flex: 1 1 100%;
  }

  .rd-panel__row > .rd-field {
    flex-basis: 100%;
  }

  .rd-deckcard__stats {
    gap: var(--space-8);
  }

  .rd-pagehead__row {
    gap: var(--space-4);
  }

  /* One column on a phone, and the areas restated to match it: with the
     children placed by name, dropping to 1fr without redrawing the map would
     leave "art" pointing at a second column this breakpoint no longer has. */
  .rd-dashpanel--onboarding {
    grid-template-columns: 1fr;
    grid-template-areas:
      "head"
      "steps"
      "art"
      "note";
  }

  /* A step's action drops under its label and hint on a phone and takes the
     row's width: the checklist is the first thing a new member does, most
     of them on a phone, and a button squeezed beside two lines of hint is a
     thumb target only by accident. The marker keeps its column, so the
     button lines up under the words rather than under the number. */
  .rd-dashstep {
    flex-wrap: wrap;
  }

  /* With wrapping allowed, an auto basis is the hint's full unwrapped width,
     which pushes the body onto the line UNDER the number. A zero basis keeps
     it beside the number and lets it grow into the rest of the row. */
  .rd-dashstep__body {
    flex-basis: 0;
  }

  .rd-dashstep__actions {
    flex: 1 1 100%;
    margin-left: calc(var(--control-h-sm) + var(--space-6));
  }

  /* Each button takes the row rather than sharing it: two half-width
     targets under two lines of hint is a thumb target by accident. */
  .rd-dashstep__action {
    flex: 1 1 100%;
  }

  .rd-dashmore__action {
    align-self: stretch;
  }
}

.rd-rolodex__place:hover {
  color: var(--accent);
}

.rd-rolodex__place > .rd-icon {
  color: var(--text-faint);
}

.rd-rolodex__place:hover > .rd-icon {
  color: var(--accent);
}

/* The row's tag chips are smaller than a standalone .rd-tag: a row carrying
   four of them at full control height stops being a dense row.

   Size only — NOT the font shorthand. --type-meta is the design system's
   mono face, which is right for the place chip beside it (the kit's own
   ContactRow sets exactly that) and wrong for a tag: the shorthand would
   quietly re-family the primitive and the row would show two chip species
   that disagree about what a chip is. */
.rd-rolodex__chips > .rd-tag {
  height: auto;
  padding: var(--space-2) var(--space-4);
  font-size: var(--text-xs);
}

.rd-rolodex__rowaction {
  margin-block: 0;
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* The archive form inside the slot (PRD #456 made the slot a group so
   Invite can sit beside it); a form's default block margin would push the
   row's height. */
.rd-rolodex__rowaction form { margin-block: 0; }

/* ─── Mobile ──────────────────────────────────────────────────────────────
   720px is the design system's own breakpoint (ui_kits/dashboard/
   responsive.js), the same one assets/shell.css flips the nav at.

   The kit's phone row drops the trailing controls and stacks the place chips
   under the name. This keeps the star — a favorite is a one-tap action and
   the reason to reach for the phone — and moves the chips and the archive
   button onto a second line rather than crushing four columns into 390px. */
@media (max-width: 720px) {
  .rd-rolodex__head {
    gap: var(--space-4);
  }

  .rd-rolodex__actions {
    width: 100%;
    margin-left: 0;
  }

  .rd-rolodex__search {
    flex-wrap: wrap;
  }

  /* Three columns, two rows: the star, the identity and the archive control
     share the main line — the kit keeps its trailing control there — and the
     chips take a second line indented past the star, under the name. */
  .rd-rolodex__row {
    grid-template-columns: auto minmax(0, 1fr) auto;
    row-gap: var(--space-4);
    padding: var(--space-5) var(--space-5);
    min-height: var(--tap-min);
  }

  .rd-rolodex__chips {
    grid-column: 2 / -1;
    justify-content: flex-start;
  }

  .rd-rolodex__rowaction {
    grid-column: 3;
    grid-row: 1;
  }

  .rd-rolodex__starbtn {
    width: var(--control-h-md);
    height: var(--control-h-md);
  }
}

/* ═══ Places ══════════════════════════════════════════════════════════════
   The places index and the place page: "who do I know for this property?"
   Ported from design-system/ui_kits/dashboard/PlacesView.jsx.

   Built on the vocabulary the screens before it established — .rd-workspace,
   .rd-pagehead, .rd-placelist/.rd-placerow — and adds only what those do not
   have: a whole-row link on the index, the category eyebrow, the inline
   add-contact disclosure, and the person rows under each category. */

/* ─── The index ───────────────────────────────────────────────────────────
   The kit's rail is a button per place, so the whole row is the target here
   rather than the name alone: on a phone a name-sized target inside a
   full-width card is a miss waiting to happen. .rd-placerow keeps being the
   card; the link becomes its contents. */
.rd-placerow--link {
  padding: var(--space-0);
  overflow: hidden;
}

.rd-placerow__link {
  display: flex;
  flex: 1;
  align-items: center;
  gap: var(--space-5);
  min-width: 0;
  min-height: var(--tap-min);
  padding: var(--space-5) var(--space-6);
  text-decoration: none;
  transition: var(--transition-control);
}

.rd-placerow__link:hover {
  background: var(--accent-quiet);
}

.rd-placerow__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  flex: 1;
  min-width: 0;
}

.rd-placerow__meta {
  font: var(--type-body-sm);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.rd-placerow__chevron {
  color: var(--text-faint);
}

/* ─── The place page ──────────────────────────────────────────────────────
   A place page's title is a proper noun rather than a screen label, and the
   kit sets it in the serif display face (PlacesView.jsx, PlaceDetail). The
   index keeps the app's standard sans title, because "Places" IS a label. */
.rd-pagehead__title--subject {
  font-family: var(--font-serif);
  font-size: var(--text-3xl);
  font-weight: var(--weight-regular);
  line-height: var(--leading-tight);
}

/* The mono, letterspaced, uppercase label over each category — the kit's
   eyebrow. --type-meta re-families to the mono stack by design: that contrast
   is the eyebrow. */
.rd-eyebrow {
  font: var(--type-meta);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* ─── Inline add-contact ─────────────────────────────────────────────────
   A <details> disclosure. The summary is styled as a button because that is
   what it behaves like, and the marker is removed in both spellings —
   ::-webkit-details-marker for Safari, ::marker for everyone else — because
   only one of them fires per engine. */
.rd-linkform {
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-lg);
  background: var(--surface-card);
}

.rd-linkform__summary {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-5) var(--space-6);
  min-height: var(--tap-min);
  font: var(--type-label);
  color: var(--text-heading);
  cursor: pointer;
  list-style: none;
}

.rd-linkform__summary::-webkit-details-marker {
  display: none;
}

.rd-linkform__summary::marker {
  content: "";
}

.rd-linkform__summary:hover {
  color: var(--accent);
}

.rd-linkform[open] .rd-linkform__summary {
  border-bottom: var(--border-width) solid var(--border-subtle);
}

.rd-linkform__form {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-6);
  padding: var(--space-7) var(--space-6);
}

.rd-linkform__form .rd-button {
  align-self: flex-start;
}

/* What the last submit did, said between the summary and the form. It takes
   the form's own horizontal inset rather than the panel's full width: flush
   to the border it reads as a banner about the whole page, when it is an
   answer about this picker. The bottom gap is the form's own top padding. */
.rd-linkform > .rd-formnote {
  margin: var(--space-7) var(--space-6) 0;
}

.rd-linkform__note {
  padding: var(--space-6);
  font: var(--type-body-sm);
  color: var(--text-muted);
}

/* ─── Place detail: contacts grouped by category ─────────────────────────
   One section per profession, each headed by an eyebrow. The gap between
   sections is larger than the gap inside one, which is what makes the
   grouping readable without a rule between them. */
.rd-placegroup {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.rd-placegroup__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  list-style: none;
  padding: 0;
}

.rd-placeperson {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-5) var(--space-6);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--surface-card);
  text-decoration: none;
  transition: var(--transition-control);
}

/* ─── The deck editor ─────────────────────────────────────────────────────
   The one screen where an owner works rather than reads: three tabs over a
   deck they are assembling. Each tab is a real address, so the sections are
   server-rendered alternatives rather than panels hidden with a class.

   The shape that matters is the Contacts tab's TWO panes. What this replaced
   was three lists stacked down the page — lineup, then rolodex, then saved
   picks — so adding somebody meant scrolling past the order you were building
   and losing sight of it. Side by side, the deck stays visible while you pick.

   No colour value appears in this layer. The accent swatches below resolve
   their hues from the palette by NAME (--teal-600 and friends), so no hex
   ever enters the markup or this file. */

.rd-pagehead__slug {
  font: var(--type-meta);
  color: var(--text-muted);
}

/* ─── Draft notice ────────────────────────────────────────────────────────
   A deck spends its whole first session unpublished, so the fact is stated
   where the work happens rather than only on the tab that can change it. */
.rd-draftnote {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-5) var(--space-6);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--surface-card);
  text-decoration: none;
  transition: var(--transition-control);
}

.rd-placeperson:hover {
  border-color: var(--border-strong);
  box-shadow: var(--shadow-sm);
}

.rd-placeperson__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  flex: 1;
  min-width: 0;
}

.rd-placeperson__name {
  font: var(--type-label);
  color: var(--text-heading);
}

.rd-placeperson__business {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

/* The link note — "replaced breaker 2026". Meta type, which re-families to
   mono: it is a record of what happened, not part of the person's name, and
   the family change is what separates the two at a glance. */
.rd-placeperson__note {
  margin-top: var(--space-2);
  font: var(--type-meta);
  color: var(--text-faint);
}

.rd-placeperson__chevron {
  color: var(--text-faint);
}

@media (max-width: 720px) {
  /* The chevron is an affordance for a pointer; a whole row is the tap target
     on a phone, so it stops earning its width. */
  .rd-placerow__chevron,
  .rd-placeperson__chevron {
    display: none;
  }

  .rd-pagehead__title--subject {
    font-size: var(--text-2xl);
  }

}

/* ─── Draft notice ────────────────────────────────────────────────────────
   A deck spends its whole first session unpublished, so the fact is stated
   where the work happens rather than only on the tab that can change it. */
.rd-draftnote {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-5) var(--space-6);
  border: var(--border-width) solid var(--border-default);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
  color: var(--text-body);
  font: var(--type-body-sm);
}

.rd-draftnote > .rd-icon { flex: none; color: var(--text-muted); }

.rd-draftnote__text { flex: 1 1 auto; }

/* The draft's two ways forward — publish, add a professional — side by
   side after the sentence. The publish button is a form so the write stays
   a POST; a form's default block margin would push the row's height. */
.rd-draftnote__actions {
  display: flex;
  flex: none;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

.rd-draftnote__actions form { margin-block: 0; }

/* The draft note wraps on a phone: the sentence keeps the line beside the
   eye, and the actions take a full row under it, each a thumb's width
   rather than three small buttons crowding one line. These rules live AFTER
   the component rather than in the phone block further up, where an
   identical set sat behind a later .rd-draftnote__actions declaration and
   never applied: with two buttons the row happened to fit, and the third
   (Invite someone) pushed the page 16px wider than the phone. */
@media (max-width: 720px) {
  .rd-draftnote {
    flex-wrap: wrap;
  }

  .rd-draftnote__actions {
    flex: 1 1 100%;
  }

  .rd-draftnote__actions form,
  .rd-draftnote__actions .rd-button {
    flex: 1 1 auto;
  }
}

/* ─── Put them on my Refdeck ──────────────────────────────────────────────
   The new-contact form's tick (firstdeck.go), the last row of the PUBLIC
   group — a Refdeck is a public page, and the tick puts this person on one.
   A rule above it separates the decision from the fields it is about. */
.rd-deckoffer {
  display: block;
  padding-top: var(--space-6);
  border-top: var(--border-width) solid var(--border-subtle);
}

.rd-deckoffer .rd-checkbox {
  align-items: flex-start;
}

/* ─── Invited, not joined ─────────────────────────────────────────────────
   The contact page for a card whose person has an invitation pending
   (contact_page.html): a dashed, quiet avatar — somebody not here yet — and
   the invitation on a warning rail where the public details would be. */
.rd-avatar--pending {
  border-style: dashed;
  border-color: var(--border-strong, var(--border-subtle));
  background: transparent;
  color: var(--text-muted);
}

.rd-invitewait {
  border-left: 3px solid var(--status-warning);
}

.rd-invitewait__meta {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

/* A member with several Refdecks gets a titled list of ticks, one per deck
   (quickinvite.go). A fieldset, so the title is the group's accessible name;
   its default frame is dropped for the same top rule the single tick wears. */
.rd-deckoffer--list {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin: 0;
  padding-inline: 0;
  padding-bottom: 0;
  border-inline: 0;
  border-bottom: 0;
}

.rd-deckoffer--list legend {
  float: left;
  width: 100%;
  margin-bottom: var(--space-2);
  padding: 0;
}

/* ─── The live preview (PRD #785) ─────────────────────────────────────────
   The deck's real public page, framed at the top of the editor on every
   tab. The frame takes the editor's width, so the page inside lays itself
   out for that width by its own breakpoints: wide on a desktop, stacked on
   a phone, which is what a visitor on the same screen would get. Its height
   is a window onto the top of the page — the hero and the first row of the
   network — and the frame scrolls for the rest. */
.rd-deckpreview {
  margin-bottom: var(--space-7);
}

/* The label is the disclosure's summary: it puts the preview away and
   brings it back, so it reads as something to press. The browser's own
   marker says which way it will go. */
.rd-deckpreview__head {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  flex-wrap: wrap;
  cursor: pointer;
  border-radius: var(--radius-sm);
}

.rd-deckpreview__head:focus-visible { box-shadow: var(--focus-ring); outline: none; }

.rd-deckpreview[open] .rd-deckpreview__head { margin-bottom: var(--space-4); }

.rd-deckpreview__title {
  font: var(--type-meta);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}

.rd-deckpreview__note {
  font: var(--type-body-sm);
  color: var(--text-faint);
}

.rd-deckpreview__frame {
  display: block;
  width: 100%;
  height: clamp(20rem, 55vh, 34rem);
  border: var(--border-width) solid var(--border-default);
  border-radius: var(--radius-lg);
  background: var(--bg-page);
}

/* In view while the tab scrolls. A live preview has to be on screen when
   the thing it previews changes, and the Branding form is several screens
   long: at the top of the editor only, the preview is gone by the time the
   owner reaches the layout or the theme. Where there is room for both it
   is pinned under the top bar, on the page's own ground so the form passes
   beneath it rather than through it, and short enough to leave the form
   most of the screen. A phone has no such room (the phone-width block
   below), and anyone who wants the screen back can put it away. */
@media (min-width: 721px) {
  .rd-deckpreview {
    position: sticky;
    top: var(--topbar-h);
    z-index: 5;
    padding-block: var(--space-4);
    background: var(--bg-page);
  }

  .rd-deckpreview__frame { height: clamp(16rem, 40vh, 30rem); }

  /* What is pinned covers the top of the screen, so anything the browser
     scrolls into view — a field reached with Tab, an anchor, a validation
     message — would land underneath it. Reserve the pinned height in the
     page's scroll padding while the preview is open, so those land below
     it instead. The sum is the top bar, the frame above, and the label
     and padding around it. */
  html:has(.rd-deckpreview[open]) {
    scroll-padding-top: calc(var(--topbar-h) + clamp(16rem, 40vh, 30rem) + var(--space-11));
  }

  html:has(.rd-deckpreview:not([open])) {
    scroll-padding-top: calc(var(--topbar-h) + var(--space-9));
  }
}

/* The way out to the preview window (PRD #800): a quiet link at the end of
   the preview's label, pushed to the far edge so it reads as an action on
   the preview rather than part of its caption. */
.rd-deckpreview__pop {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  margin-left: auto;
  font: var(--type-label);
  color: var(--text-link);
  text-decoration: none;
  border-radius: var(--radius-sm);
}

.rd-deckpreview__pop:hover { color: var(--text-link-hover); text-decoration: underline; }
.rd-deckpreview__pop:focus-visible { box-shadow: var(--focus-ring); outline: none; }

/* ─── The preview window (PRD #800) ───────────────────────────────────────
   deck_preview_window.html: a banner over the deck, and nothing else. The
   banner is dark, the colour of no deck theme, so it reads as the tool
   around the page rather than the top of it — the WordPress customizer's
   notice is the model. The frame takes every pixel under it. */
body.rd-previewwin {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
  margin: 0;
  background: var(--bg-sunken);
}

.rd-previewwin__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4) var(--space-7);
  padding: var(--space-5) var(--space-6);
  background: var(--surface-inverse);
  color: var(--text-on-dark);
}

.rd-previewwin__what {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-width: 0;
}

.rd-previewwin__badge {
  padding: var(--space-1) var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--status-warning-bg);
  color: var(--text-heading);
  font: var(--type-meta);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}

.rd-previewwin__name {
  font: var(--type-subhead);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rd-previewwin__state {
  font: var(--type-meta);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  opacity: 0.75;
}

.rd-previewwin__state--live { color: var(--status-success-bg); opacity: 1; }

.rd-previewwin__note,
.rd-previewwin__status {
  margin: 0;
  font: var(--type-body-sm);
}

.rd-previewwin__note { flex: 1 1 22rem; }

.rd-previewwin__link { color: inherit; text-decoration: underline; }

/* The connection, said in colour as well as words: following, or not. */
.rd-previewwin__status { flex: 1 1 100%; order: 10; opacity: 0.8; }
.rd-previewwin__status[data-state="live"] { color: var(--status-success-bg); opacity: 1; }
.rd-previewwin__status[data-state="lost"] { color: var(--status-warning-bg); opacity: 1; }

.rd-previewwin__widths {
  display: inline-flex;
  margin: 0;
  padding: var(--space-1);
  border: 0;
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--text-on-dark) 10%, var(--surface-inverse));
}

.rd-previewwin__legend {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
}

.rd-previewwin__width input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.rd-previewwin__width span {
  display: block;
  padding: var(--space-3) var(--space-5);
  border-radius: var(--radius-sm);
  font: var(--type-button);
  cursor: pointer;
}

.rd-previewwin__width input:checked + span {
  background: var(--surface-card);
  color: var(--text-heading);
}

.rd-previewwin__width input:focus-visible + span { box-shadow: var(--focus-ring); }

.rd-previewwin__edit {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  color: inherit;
  font: var(--type-button);
  text-decoration: none;
}

.rd-previewwin__edit:hover { text-decoration: underline; }

.rd-previewwin__stage {
  display: flex;
  flex: 1;
  justify-content: center;
  min-height: 0;
  padding: var(--space-0);
}

.rd-previewwin__frame {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
  background: var(--bg-page);
  transition: max-width var(--dur-base) var(--ease-out);
}

/* Phone and tablet: the frame at a device's width, on the sunken ground, so
   the page inside lays itself out by its own breakpoints as it would on
   that device. Desktop is the window's whole width. */
.rd-previewwin:has(input[name="preview_width"][value="phone"]:checked) .rd-previewwin__stage,
.rd-previewwin:has(input[name="preview_width"][value="tablet"]:checked) .rd-previewwin__stage {
  padding: var(--space-8) var(--space-6);
}

.rd-previewwin:has(input[name="preview_width"][value="phone"]:checked) .rd-previewwin__frame {
  max-width: 390px;
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg);
}

.rd-previewwin:has(input[name="preview_width"][value="tablet"]:checked) .rd-previewwin__frame {
  max-width: 820px;
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}

/* ─── The picker: add somebody, then the lineup ───────────────────────────
   One column. The panes sat side by side while "Add to this deck" was a
   list of every contact; it is a search now (PRD #774), which needs a line
   rather than a column, and the lineup takes the page's width. */
.rd-picker {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-7);
  align-items: start;
}

.rd-picker__pane {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  min-width: 0;
}

.rd-picker__head {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  padding-bottom: var(--space-4);
  border-bottom: var(--border-width) solid var(--border-subtle);
}

.rd-picker__title {
  font: var(--type-heading);
  color: var(--text-heading);
}

.rd-picker__count {
  margin-left: auto;
  font: var(--type-body-sm);
  color: var(--text-faint);
  white-space: nowrap;
}

/* ─── The lineup: what is on the deck, in the owner's order ───────────── */
.rd-lineup {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  list-style: none;
  padding: 0;
}

.rd-lineuprow {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.rd-lineuprow__head {
  display: flex;
  align-items: center;
  gap: var(--space-5);
}

/* The position, drawn as the mono number it is. This is what makes a reorder
   legible as an order rather than as two buttons that shuffle things. */
.rd-lineuprow__pos {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-8);
  height: var(--space-8);
  border-radius: var(--radius-sm);
  background: var(--accent-quiet);
  color: var(--accent);
  font: var(--type-meta);
}

.rd-lineuprow__identity {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.rd-lineuprow__name {
  font: var(--weight-semibold) var(--text-md)/var(--leading-snug) var(--font-sans);
  color: var(--text-heading);
}

/* The member's own contact: the name is the link to its page, which holds
   what the lineup leaves out. It reads as the name until it is pointed at. */
a.rd-lineuprow__name { text-decoration: none; }
a.rd-lineuprow__name:hover { text-decoration: underline; }

.rd-lineuprow__business {
  font: var(--type-body-sm);
  color: var(--text-body);
}

/* The profession is the design system's own label treatment, not bare
   --type-meta: that token is MONO, and mono alone reads as code. The primitive
   it is ported from (.rd-contact-card__profession) pairs it with uppercase and
   wide tracking, which is what turns it into a category label. */
.rd-lineuprow__profession {
  font: var(--type-meta);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* Attribution is a sentence, so it stays prose. */
.rd-lineuprow__source {
  font: var(--type-body-sm);
  color: var(--text-faint);
}

.rd-lineuprow__controls {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.rd-lineuprow__reorder {
  display: flex;
  gap: var(--space-2);
}

.rd-lineuprow__discard { display: flex; }

.rd-lineuprow__remove:hover { color: var(--status-danger); }

.rd-lineuprow__blurb {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  align-items: flex-start;
  padding-top: var(--space-5);
  border-top: var(--border-width) solid var(--border-subtle);
}

.rd-lineuprow__blurb .rd-field { width: 100%; }

/* The bronze flag, beside the name it belongs to rather than off in the
   controls: it reports a state, and the controls are things you press.
   flex:none so a long business name cannot squeeze it away. */
.rd-lineuprow__flag {
  flex: none;
  display: inline-flex;
}

/* Marking a pick and writing why you picked them are the two curation
   decisions a row carries, so the toggle sits directly above the testimonial
   and shares its left edge. No divider of its own: the blurb below owns the
   row's single rule, and a second one would box the toggle into a band of
   its own. */
.rd-lineuprow__feature {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  flex-wrap: wrap;
}

/* ─── Add to this deck: the search, and whoever it found ──────────────── */
/* The search form is the contact list's own (.rd-rolodex__search); inside a
   pane its head already spaces it, so it gives up its block margin. */
.rd-addsearch { margin-block: 0; }

.rd-addsearch__none {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

/* The dropdown: the names a typed query found, in the pool's own bordered
   list. Each is one link the width of its row, so the whole line picks. */
.rd-addsearch__option { border-top: var(--border-width) solid var(--border-subtle); }
.rd-addsearch__option:first-child { border-top: none; }

.rd-addsearch__pick {
  display: flex;
  align-items: baseline;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  color: inherit;
  text-decoration: none;
}

.rd-addsearch__pick:hover,
.rd-addsearch__pick:focus-visible { background: var(--bg-sunken); }

/* The person picked from the dropdown: who they are, at the size the
   lineup shows people, beside the one button that adds them. */
.rd-addpick {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  flex-wrap: wrap;
}

.rd-addpick .rd-poolrow__add { margin-left: auto; }

.rd-pool {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.rd-pool__group {
  font: var(--type-meta);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}

.rd-pool__items {
  display: flex;
  flex-direction: column;
  list-style: none;
  padding: 0;
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--surface-card);
  overflow: hidden;
}

.rd-poolrow {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  border-top: var(--border-width) solid var(--border-subtle);
}

.rd-poolrow:first-child { border-top: none; }

.rd-poolrow__identity {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.rd-poolrow__name {
  font: var(--weight-medium) var(--text-sm)/var(--leading-snug) var(--font-sans);
  color: var(--text-heading);
}

/* The same business · profession line the rolodex renders, so it takes the
   rolodex's treatment rather than a mono one of its own. */
.rd-poolrow__meta {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

.rd-poolrow__business::before { content: " · "; }

.rd-poolrow__tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding-top: var(--space-2);
}

.rd-poolrow__add { flex: none; }

/* ─── Branding ────────────────────────────────────────────────────────────
   One column: the pictures, then the form. The preview that sat beside
   them was a hand-built miniature of the public page; the page itself is
   framed at the top of the editor now (.rd-deckpreview, PRD #785), on
   every tab, which is what freed the second column. */
.rd-branding {
  display: flex;
  flex-direction: column;
  gap: var(--space-7);
  /* One column, at a width a form reads well at rather than the page's. */
  max-width: 44rem;
}

.rd-branding__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  align-items: flex-start;
}

.rd-branding__form > .rd-field { width: 100%; }

/* ─── The accent palette ──────────────────────────────────────────────────
   Six radios drawn as swatches. THE HUE COMES FROM THE PALETTE BY NAME: each
   option carries data-accent="<name>" and the rule below reads the matching
   --<name>-600 token, so store.DeckAccents' hexes stay single-sourced in
   design-system/tokens/colors.css and this layer states none of them. */
.rd-accentpick { border: none; padding: 0; }

.rd-accentpick__options {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  padding-top: var(--space-4);
}

.rd-accentpick__option {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border: var(--border-width) solid var(--border-default);
  border-radius: var(--radius-pill);
  background: var(--surface-card);
  font: var(--type-body-sm);
  color: var(--text-body);
  cursor: pointer;
  transition: var(--transition-control);
}

.rd-accentpick__option:hover { border-color: var(--border-strong); }

/* The input is the state, kept reachable by keyboard rather than removed:
   :checked and :focus-visible below are what draw the selection. */
.rd-accentpick__input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.rd-accentpick__swatch {
  width: var(--space-6);
  height: var(--space-6);
  border-radius: var(--radius-pill);
  background: var(--accent);
  box-shadow: inset 0 0 0 var(--border-width) var(--border-strong);
}

.rd-accentpick__name { text-transform: capitalize; }

.rd-accentpick__option:has(.rd-accentpick__input:checked) {
  border-color: var(--accent);
  background: var(--bg-sunken);
  color: var(--text-heading);
}

.rd-accentpick__option:has(.rd-accentpick__input:focus-visible) {
  box-shadow: var(--focus-ring);
}

/* ─── The theme picker ───────────────────────────────────────────────────
   The same shape as the accent picker — a radio kept reachable, the label
   drawing the selection — with a bigger tile, because a theme needs more
   than a dot to be told from the next: a miniature (a band and two cards),
   a name, a line, and what it promises about motion and taps. Grouped under
   family headings. WHAT each miniature looks like is in themes.css, keyed on
   the theme's name, next to the theme it stands for. */
/* ─── The hero's gallery (PRD #783) ───────────────────────────────────────
   One tile per gallery picture (store.DeckBackgrounds): its thumbnail at
   the band's own 16:9, its name under it. The radio is the tile, as it is
   on the layout and theme pickers, and the chosen tile wears the accent. */
.rd-bgpick { border: none; padding: 0; }

.rd-bgpick__options {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 11rem), 1fr));
  gap: var(--space-4);
  padding-top: var(--space-4);
}

.rd-bgpick__option {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-3);
  border: var(--border-width) solid var(--border-default);
  border-radius: var(--radius-md);
  background: var(--surface-card);
  cursor: pointer;
  transition: var(--transition-control);
}

.rd-bgpick__option:hover { border-color: var(--border-strong); }

/* The owner's tile while they have no picture: the rule above sets a
   display, which would otherwise undo the hidden attribute. */
.rd-bgpick__option[hidden] { display: none; }

.rd-bgpick__input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.rd-bgpick__thumb {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
}

.rd-bgpick__name {
  font: var(--type-label);
  color: var(--text-heading);
}

.rd-bgpick__option:has(.rd-bgpick__input:checked) {
  border-color: var(--accent);
  background: var(--bg-sunken);
  box-shadow: inset 0 0 0 var(--border-width) var(--accent);
}

.rd-bgpick__option:has(.rd-bgpick__input:focus-visible) {
  box-shadow: var(--focus-ring);
}

/* ─── The layout picker (PRD #771) ──────────────────────────────────────
   One option per registry layout (store.DeckLayouts), the same shape as a
   theme tile: a miniature of the public page's network beside the label
   and tagline. The miniature is four cells drawn from the layout's NAME
   (data-layout) — two tall cards for the card grid, a 2×2 of small tiles
   for the compact directory — so the markup restates nothing about the
   look. */
.rd-layoutpick { border: none; padding: 0; }

.rd-layoutpick__options {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr));
  gap: var(--space-4);
  padding-top: var(--space-4);
}

.rd-layoutpick__option {
  position: relative;
  display: flex;
  gap: var(--space-5);
  align-items: flex-start;
  padding: var(--space-5);
  border: var(--border-width) solid var(--border-default);
  border-radius: var(--radius-md);
  background: var(--surface-card);
  cursor: pointer;
  transition: var(--transition-control);
}

.rd-layoutpick__option:hover { border-color: var(--border-strong); }

.rd-layoutpick__input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.rd-layoutpick__swatch {
  flex: 0 0 auto;
  display: grid;
  gap: var(--space-2);
  width: var(--space-12);
  height: var(--space-13);
  padding: var(--space-3);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-sm);
  background: var(--bg-page);
}

.rd-layoutpick__cell {
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-xs);
  background: var(--surface-card);
}

/* The card grid: one column, each card tall; two of the four cells are
   enough to say so and the others are not drawn. */
.rd-layoutpick__option[data-layout="cards"] .rd-layoutpick__swatch {
  grid-template-columns: 1fr;
}

.rd-layoutpick__option[data-layout="cards"] .rd-layoutpick__cell:nth-child(n+3) { display: none; }

.rd-layoutpick__option[data-layout="cards"] .rd-layoutpick__cell {
  border-top: var(--space-2) solid var(--accent);
}

/* The compact directory: two across, four small tiles, a dot for the logo
   over each. */
.rd-layoutpick__option[data-layout="compact"] .rd-layoutpick__swatch {
  grid-template-columns: 1fr 1fr;
}

.rd-layoutpick__option[data-layout="compact"] .rd-layoutpick__cell {
  display: grid;
  place-items: start center;
  padding-top: var(--space-2);
}

.rd-layoutpick__option[data-layout="compact"] .rd-layoutpick__cell::before {
  content: "";
  width: var(--space-4);
  height: var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--accent);
}

.rd-layoutpick__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

.rd-layoutpick__name {
  font: var(--type-label);
  color: var(--text-heading);
}

.rd-layoutpick__tagline {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

.rd-layoutpick__option:has(.rd-layoutpick__input:checked) {
  border-color: var(--accent);
  background: var(--bg-sunken);
  box-shadow: inset 0 0 0 var(--border-width) var(--accent);
}

.rd-layoutpick__option:has(.rd-layoutpick__input:focus-visible) {
  box-shadow: var(--focus-ring);
}

.rd-themepick { border: none; padding: 0; }

.rd-themepick__family {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-top: var(--space-6);
}

.rd-themepick__familyname {
  font: var(--type-subhead);
  color: var(--text-heading);
}

.rd-themepick__familyblurb {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

.rd-themepick__options {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 17rem), 1fr));
  gap: var(--space-4);
  padding-top: var(--space-4);
}

.rd-themepick__option {
  position: relative;
  display: flex;
  gap: var(--space-5);
  align-items: flex-start;
  padding: var(--space-5);
  border: var(--border-width) solid var(--border-default);
  border-radius: var(--radius-md);
  background: var(--surface-card);
  cursor: pointer;
  transition: var(--transition-control);
}

.rd-themepick__option:hover { border-color: var(--border-strong); }

.rd-themepick__input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* The miniature: a page in a 3:4 box, the band along the top and two cards
   under it, each drawn in the default look until themes.css re-shapes them
   for a name. */
.rd-themepick__swatch {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  width: var(--space-12);
  height: var(--space-13);
  padding: 0 var(--space-3) var(--space-3);
  overflow: hidden;
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-sm);
  background: var(--bg-page);
}

.rd-themepick__swatchband {
  position: relative;
  flex: 0 0 auto;
  height: var(--space-7);
  margin-inline: calc(-1 * var(--space-3));
  margin-bottom: var(--space-2);
  background: var(--accent);
}

.rd-themepick__swatchcard {
  flex: 1 1 0;
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-xs);
  background: var(--surface-card);
}

.rd-themepick__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

.rd-themepick__name {
  font: var(--type-label);
  color: var(--text-heading);
}

.rd-themepick__tagline {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

.rd-themepick__badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding-top: var(--space-1);
}

.rd-themepick__badges .rd-badge { text-transform: capitalize; }

.rd-themepick__option:has(.rd-themepick__input:checked) {
  border-color: var(--accent);
  background: var(--bg-sunken);
  box-shadow: inset 0 0 0 var(--border-width) var(--accent);
}

.rd-themepick__option:has(.rd-themepick__input:focus-visible) {
  box-shadow: var(--focus-ring);
}

/* ─── The one name-to-colour map ─────────────────────────────────────────
   store.DeckAccents stores a palette NAME, never a hex, so the editor maps
   name to palette token here, once, for the swatch that offers a colour.

   Spelled out per accent rather than routed through a local custom property,
   because an app CSS layer may USE design-system tokens but must never DEFINE
   one — the token set has a single home in design-system/tokens/. The rules
   above already read --accent, so an unrecognised name still renders as the
   default rather than as an unpainted box.

   600 and 100 are the two levels every accent in the palette ships; teal and
   bronze carry more, the other four do not, so the map stays on the levels
   that exist for all six.
   ------------------------------------------------------------------------ */

.rd-accentpick__option[data-accent="teal"] .rd-accentpick__swatch { background: var(--teal-600); }

.rd-accentpick__option[data-accent="teal"]:has(.rd-accentpick__input:checked) {
  border-color: var(--teal-600);
}

.rd-accentpick__option[data-accent="bronze"] .rd-accentpick__swatch { background: var(--bronze-600); }

.rd-accentpick__option[data-accent="bronze"]:has(.rd-accentpick__input:checked) {
  border-color: var(--bronze-600);
}

.rd-accentpick__option[data-accent="green"] .rd-accentpick__swatch { background: var(--green-600); }

.rd-accentpick__option[data-accent="green"]:has(.rd-accentpick__input:checked) {
  border-color: var(--green-600);
}

.rd-accentpick__option[data-accent="amber"] .rd-accentpick__swatch { background: var(--amber-600); }

.rd-accentpick__option[data-accent="amber"]:has(.rd-accentpick__input:checked) {
  border-color: var(--amber-600);
}

.rd-accentpick__option[data-accent="blue"] .rd-accentpick__swatch { background: var(--blue-600); }

.rd-accentpick__option[data-accent="blue"]:has(.rd-accentpick__input:checked) {
  border-color: var(--blue-600);
}

.rd-accentpick__option[data-accent="red"] .rd-accentpick__swatch { background: var(--red-600); }

.rd-accentpick__option[data-accent="red"]:has(.rd-accentpick__input:checked) {
  border-color: var(--red-600);
}

/* ─── Share ───────────────────────────────────────────────────────────────
   Four decisions, one card each: who can see it, where it lives, the printed
   code, and the way out. Cards rather than a run of headings because the
   publish toggle is destructive-adjacent and should not sit a line away from
   the delete link. */
.rd-share {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr));
  gap: var(--space-6);
  align-items: start;
}

.rd-share__block {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  align-items: flex-start;
}

.rd-share__head {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  width: 100%;
}

.rd-share__title {
  font: var(--type-heading);
  color: var(--text-heading);
}

.rd-share__head .rd-badge { margin-left: auto; }

.rd-share__note {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

.rd-share__toggle {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  align-items: flex-start;
}

/* The address is meant to be selected and copied, so it is given room and a
   wrap rule rather than being clipped to the card. */
.rd-shareaddress { width: 100%; }

.rd-shareaddress__url {
  display: block;
  padding: var(--space-4) var(--space-5);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-heading);
  overflow-wrap: anywhere;
}

.rd-shareqr__image {
  align-self: center;
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
}

.rd-shareqr__download {
  align-self: center;
  gap: var(--space-3);
}

/* ─── The share sheet (share.go) ─────────────────────────────────────────
   The code, the address and the ways to send it, composed in the public
   page's dialog, on its fallback page, and — the actions alone — on the
   editor's Share tab. One column: it is read on a phone held up to another
   phone, and every action is a full-width thumb target there. */
.rd-sharesheet {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-5);
}

.rd-sharesheet__qr {
  align-self: center;
  width: 200px;
  height: 200px;
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-sm);
  background: var(--surface-card);
}

.rd-sharesheet__hint {
  text-align: center;
  font: var(--type-body-sm);
  color: var(--text-muted);
}

/* Every action wears the icon of the app it opens, and the row wraps to as
   many columns as fit: two on a phone, more on the editor's wide tab. The
   two scripted ones (the phone's share sheet, copy link) are rendered hidden
   and shown by share.js where the browser can honour them. */
.rd-shareactions {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 11rem), 1fr));
  gap: var(--space-4);
  width: 100%;
}

.rd-shareactions .rd-button {
  justify-content: center;
  gap: var(--space-3);
}

/* The card (deckcard.go): the preview at the sheet's width, keeping the
   image's own 3.5:2, on a hairline so its white paper reads as a card
   against the panel rather than as more panel. */
.rd-sharecard {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-4);
  width: 100%;
  padding-top: var(--space-5);
  border-top: var(--border-width) solid var(--border-subtle);
}

.rd-sharecard__image {
  display: block;
  width: 100%;
  height: auto;
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
}

.rd-sharecard__hint {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

.rd-sharecard .rd-button {
  justify-content: center;
  gap: var(--space-3);
}

/* The way out is not a fourth peer decision, so it spans the row rather than
   sitting in a column of its own beside them. Softened against the card too:
   the danger colour belongs to the button inside, not to the box around it. */
.rd-sharedanger {
  grid-column: 1 / -1;
  border-color: color-mix(in srgb, var(--status-danger) 30%, var(--surface-card));
}

/* ─── Mobile ──────────────────────────────────────────────────────────────
   720px is the design system's breakpoint, the same one shell.css flips the
   nav at. Both two-column layouts become one column: a picker squeezed into
   half a phone is worse than a scroll. */
@media (max-width: 720px) {
  .rd-picker {
    grid-template-columns: minmax(0, 1fr);
  }

  /* On a phone the live preview takes at most two fifths of the screen, so
     the tab's own controls start on the first screen under it. */
  .rd-deckpreview__frame { height: clamp(13rem, 38vh, 20rem); }

  .rd-draftnote {
    flex-wrap: wrap;
    row-gap: var(--space-4);
  }

  .rd-lineuprow__head {
    flex-wrap: wrap;
    row-gap: var(--space-4);
  }

  .rd-lineuprow__controls { margin-left: auto; }
}

/* Both panes may hold a long unbroken name or address; neither may push
   the page wider than its column. */
.rd-picker__pane--lineup,
.rd-picker__pane--pool { min-width: 0; }

.rd-shareqr { align-items: stretch; }

/* ─── Groups — /app/groups ───────────────────────────────────────────────
   The last nav item to get a screen. It pointed at an anchor on the dashboard
   for as long as this route did not exist; the dashboard now keeps only a
   summary panel that links here, the same shape the referrals panel has.

   A group is a smaller object than a deck — a name, a role, a member count —
   so the cards are narrower than .rd-decklist's and pack more per row. */
.rd-grouplist {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr));
  gap: var(--space-6);
  padding: 0;
  list-style: none;
}

.rd-groupcard {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.rd-groupcard__head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-5);
}

.rd-groupcard__name {
  font: var(--type-subhead);
  color: var(--text-heading);
  text-decoration: none;
}

.rd-groupcard__name:hover {
  color: var(--text-link-hover);
}

/* The role badge holds the end of the head row whatever the name's length. */
.rd-groupcard__head > .rd-badge {
  flex: none;
  margin-left: auto;
}

.rd-groupcard__desc {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

/* The count and the way in sit on one line, the count leading because it is
   what a member scans for and the action holding the end. */
.rd-groupcard__foot {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  margin-top: auto;
}

.rd-groupcard__meta {
  font: var(--type-meta);
  color: var(--text-muted);
}

.rd-groupcard__action {
  margin-left: auto;
}

/* ═══ Group home ═════════════════════════════════════════════════════════
   Three screens wearing one tab strip: the roster (this URL), the shared
   directory, and — for the owner alone — settings. The strip itself is the
   .rd-tabs primitive, unchanged; what follows is only the content each tab
   holds.

   Built on the vocabulary the ports before it established: .rd-workspace for
   the page, .rd-pagehead for the head, .rd-panel for a titled block. The two
   new list shapes are the roster and the pending-invitation list, and they
   are deliberately the SAME row: a name, a supporting line, a status badge,
   sometimes an action. A member reads them one after the other. */

/* ─── Roster ──────────────────────────────────────────────────────────── */

.rd-roster {
  display: flex;
  flex-direction: column;
  list-style: none;
  padding: 0;
}

.rd-rosterrow {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-5) 0;
  border-top: var(--border-width) solid var(--border-subtle);
}

/* The first row sits directly under the panel title, which already spaces
   itself; a rule there too would read as a stray line. */
.rd-rosterrow:first-child {
  border-top: none;
  padding-top: 0;
}

.rd-rosterrow__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

.rd-rosterrow__name {
  font: var(--type-body);
  color: var(--text-heading);
}

.rd-rosterrow__email {
  font: var(--type-body-sm);
  color: var(--text-muted);
  overflow-wrap: anywhere;
}

/* The rank sits hard right, so the column of badges reads as a column. */
.rd-rosterrow__role {
  flex: none;
  margin-left: auto;
}

/* The owner's rank control sits after the badge it changes, so the row reads
   "who — what rank — change it".

   The slot is a fixed column, and the owner's own row renders it EMPTY rather
   than omitting it. "Make manager" and "Make member" are not the same width,
   and a row with no control at all is 119px narrower still, so without a
   reserved column the badges beside them stop being a column. min-width is
   sized to the wider of the two labels; content is pushed to the right edge
   so a narrower button still lands on the column's line. */
.rd-rosterrow__rank {
  flex: none;
  display: flex;
  justify-content: flex-end;
  min-width: 7rem;
}

/* ─── Pending invitations ─────────────────────────────────────────────── */

.rd-invitelist {
  display: flex;
  flex-direction: column;
  list-style: none;
  padding: 0;
}

.rd-inviterow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-5) 0;
  border-top: var(--border-width) solid var(--border-subtle);
}

.rd-inviterow:first-child {
  border-top: none;
  padding-top: 0;
}

.rd-inviterow__text {
  display: flex;
  flex: 1 1 var(--sidebar-w);
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

.rd-inviterow__email {
  font: var(--type-body);
  color: var(--text-heading);
  overflow-wrap: anywhere;
}

.rd-inviterow__meta {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

/* The short code is read aloud down a phone, so it is set in the mono face
   that tells a zero from an O. */
.rd-inviterow__code {
  padding: 0 var(--space-2);
  border-radius: var(--radius-xs);
  background: var(--bg-sunken);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-body);
}

.rd-inviterow__state {
  flex: none;
  text-transform: capitalize;
}

.rd-inviterow__actions {
  display: flex;
  flex: none;
  gap: var(--space-4);
  margin-left: auto;
}

/* ─── Shared directory ────────────────────────────────────────────────── */

/* The same grid the deck list uses, for the same reason: cards of unknown
   count that should fill the width without ever going narrower than a card
   can be read at. */
.rd-dirgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 22rem), 1fr));
  gap: var(--space-6);
  list-style: none;
  padding: 0;
}

/* The card body is the .rd-contact-card primitive. These are the two things a
   directory card carries that a deck card does not: the contact details it
   shows in full rather than behind a tap, and who put it here. */
.rd-dircard__facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-3) var(--space-5);
  margin: 0;
}

.rd-dircard__term {
  font: var(--type-meta);
  color: var(--text-faint);
}

.rd-dircard__detail {
  margin: 0;
  font: var(--type-body-sm);
  color: var(--text-body);
  overflow-wrap: anywhere;
}

.rd-dircard__by {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font: var(--type-body-sm);
  color: var(--text-muted);
}

.rd-dircard__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4);
}

.rd-dircard__saved {
  font: var(--type-body-sm);
  color: var(--status-success);
}

/* ─── Settings ────────────────────────────────────────────────────────── */

/* The group's address, shown so an owner renaming the group can see that the
   address is not what they are changing. */
.rd-settingsaddr {
  padding: 0 var(--space-2);
  border-radius: var(--radius-xs);
  background: var(--bg-sunken);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-body);
}

@media (max-width: 720px) {
  /* On a phone the roster's badge stops fitting beside a long name, so the
     row wraps and the rank drops under it rather than squeezing the name. */
  .rd-rosterrow {
    flex-wrap: wrap;
  }

  .rd-inviterow__actions {
    margin-left: 0;
  }

  /* Once the row wraps there is no badge column left to keep straight, and
     the button lands on its own line at the left the way the invite row's
     actions do. So the reserved slot stops reserving, and the EMPTY one goes
     away entirely: at zero width it would still contribute the row's gap,
     which pulled the owner's badge 12px off the others' (measured at 360px).
     A column that has stopped being a column must not leave its gap behind. */
  .rd-rosterrow__rank {
    min-width: 0;
  }

  .rd-rosterrow__rank--empty {
    display: none;
  }
}

/* ─── Account settings — /app/settings ─────────────────────────────────
   Three cards of the existing vocabulary (.rd-card + .rd-panel + .rd-field),
   so this screen adds three classes rather than a section of its own:
   the switch and its explanation, and the one field that is displayed but
   not editable. */

/* The switch owns one line; the sentence explaining what it turns off owns
   the next. .rd-switch is inline-flex, so the two need a column around them
   rather than a margin under the label. */
.rd-settings__toggle {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* Indented to the switch's own label, so the sentence reads as belonging to
   the control rather than to the card. The track is 38px wide and .rd-switch
   gaps by --space-5 — the same sum, written as the two tokens it is made of. */
.rd-settings__togglenote {
  max-width: var(--prose-max);
  margin-inline-start: calc(38px + var(--space-5));
  font: var(--type-body-sm);
  color: var(--text-muted);
}

/* The two fields of a row align at the TOP, not the bottom. .rd-panel__row
   bottom-aligns, which is right for a filter row ending in a button and wrong
   here: the right-hand field carries a hint under it, so a bottom-aligned row
   drops the left-hand label by exactly the height of that hint and the two
   labels stop lining up. A grid also keeps the columns equal, which two
   flex-basis fields do not. */
.rd-settings__fields {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: start;
  gap: var(--space-6);
}

/* The sign-in address is shown, never edited here. It has to LOOK unavailable
   — an input styled exactly like the editable one beside it, that silently
   refuses to take a keystroke, reads as a broken field rather than a
   deliberate one. */
.rd-settings__locked {
  background: var(--bg-sunken);
  color: var(--text-muted);
}

/* Paired clients: one row per MCP client, what it is on the left and the
   control that revokes it on the right, ruled apart so a Revoke button can
   never read as belonging to the row above it. */
.rd-settings__clients {
  list-style: none;
  margin: 0;
  padding: 0;
}

.rd-settings__client {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  padding-block: var(--space-4);
  border-top: var(--border-width) solid var(--border-subtle);
}

.rd-settings__clientinfo {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.rd-settings__clientlabel {
  font: var(--type-body);
  font-weight: var(--weight-semibold);
  overflow-wrap: anywhere;
}

.rd-settings__clientmeta {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

/* Connected accounts (PRD #715): the one connected Google account, laid out
   exactly like a paired client's row — what it is on the left, the control
   that ends it on the right. The same shape on purpose: both cards answer
   "what has access to this account, and how do I take it away?", and a
   reader should not have to learn two layouts for one question.

   No list wrapper, because there is at most one of these: a member holds at
   most one Google account, so the markup is a row rather than a <ul> with a
   single <li> pretending more could arrive. */
.rd-settings__account {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-5);
  padding-block: var(--space-4);
  border-top: var(--border-width) solid var(--border-subtle);
}

@media (max-width: 720px) {
  /* One column on a phone, like every other paired-field grid on this
     surface. The breakpoint is the design system's REFDECK_MOBILE_MAX. */
  .rd-settings__fields {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ---- Image slot (layout.html "image_slot") ------------------------------
   The one picture control every screen uses — avatar, personal background,
   group and company mark and background, deck picture and band: the current
   picture beside the form that replaces it, so the thing being changed is
   visible while changing it. The slot's modifier is its shape, and each
   shape says how its frame sits: a square .rd-avatar for an icon, the same
   frame widened for a logo once it holds one, a wide band for a
   background. */
.rd-imageslot {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.rd-imageslot__body {
  display: flex;
  align-items: flex-start;
  gap: var(--space-6);
  flex-wrap: wrap;
}

.rd-imageslot__body .rd-formstack {
  flex: 1 1 16rem;
}

/* The picture is the drop target (assets/imageupload.js). It is a column so
   the cue reads under the frame, and it carries the outline the dragover
   state paints. How wide it sits is its shape's to say, below. */
.rd-imageslot__picture {
  position: relative;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  border-radius: var(--radius-md);
}

/* A mark — an icon or a logo — is a small frame that sits beside the form
   that replaces it. */
.rd-imageslot--icon .rd-imageslot__picture,
.rd-imageslot--logo .rd-imageslot__picture {
  display: inline-flex;
  flex: 0 0 auto;
}

/* A logo is kept whole rather than cropped square (images.Logo), and most
   are wider than they are tall: in the icon's square frame a wordmark is a
   sliver. Once the slot holds one, its frame is twice as wide, so what the
   owner uploaded can be read. The unset frame stays the square the initial
   sits in. */
.rd-imageslot--logo .rd-avatar--image {
  width: calc(var(--space-13) * 2);
}

/* A background frame takes the slot's whole width and pushes the form under
   it, because a strip a third as tall as it is wide has no room beside. */
.rd-imageslot--background .rd-imageslot__picture {
  display: flex;
  flex: 1 1 100%;
}

.rd-imageslot__band {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 1;
  overflow: hidden;
  border: var(--border-width) solid var(--teal-100);
  border-radius: var(--radius-md);
  background: var(--accent-quiet);
  color: var(--accent);
}

.rd-imageslot__band--empty {
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Holding a picture, the band is a frame around it, not a placeholder: the
   accent tint and its border are the empty state's, and behind a photograph
   they show as a coloured edge. The same hairline on white a filled mark's
   frame wears (components.css, .rd-avatar--image). */
.rd-imageslot__band--set {
  border-color: var(--border-subtle);
  background: var(--surface-card);
}

.rd-imageslot__bandimg {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The drop affordance. The cue is server-rendered hidden and the outline is
   drawn only once the script has ARMED the target (data-image-droppable),
   after confirming the browser can hand a dropped file to the file input —
   so a screen without script, or with a browser that cannot honour a drop,
   never advertises a drag it cannot take. While a file is over the target
   (data-image-dragover) the frame lights in the accent. */
.rd-imageslot__picture[data-image-droppable] {
  outline: 2px dashed transparent;
  outline-offset: 4px;
  transition: outline-color 120ms ease, background-color 120ms ease;
}

.rd-imageslot__picture[data-image-dragover] {
  outline-color: var(--accent);
  background-color: var(--accent-quiet);
}

.rd-imageslot__dropcue {
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.rd-imageslot__picture[data-image-dragover] .rd-imageslot__dropcue {
  color: var(--accent);
}

/* A device with no fine pointer cannot drag a file. The file input stays the
   path there, and the cue must not promise otherwise. */
@media (hover: none), (pointer: coarse) {
  .rd-imageslot__dropcue {
    display: none;
  }
}

/* ==== My Cards ============================================================
   The cards a member hands out (/app/cards) and the editor that writes one
   (/app/cards/<id>). The card itself is card.css; these are the screens that
   PLACE it — a section per business on the list, a form beside a live preview
   in the editor. */

/* ---- The list, grouped by business --------------------------------------
   A member thinks "my Paradigm cards", not "card 3 of 5", so the business is
   a section head rather than a column. */
.rd-cardgroup {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

.rd-cardgroup__head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-4);
}

/* The business's mark, in the card's own accent. --accent is written onto
   this element by the template, the same mechanism the cards below it use. */
.rd-cardgroup__mark {
  flex: none;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-sm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-serif);
  font-size: var(--text-sm);
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 10%, var(--surface-card));
}

.rd-cardgroup__name {
  margin: 0;
  font-family: var(--font-serif);
  font-size: var(--text-lg);
  color: var(--text-heading);
}

.rd-cardgroup__slug {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
}

.rd-cardgroup__action { margin-left: auto; }

.rd-cardgroup__empty {
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* auto-fill at 330px: a 7:4 card below about 300px stops being able to show
   a phone number and an email on one line. */
.rd-cardgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
  gap: var(--space-8);
  align-items: start;
}

.rd-cardgrid__item {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  min-width: 0;
}

/* A withdrawn card is a record, not something to hand out. It stays legible
   — the owner may bring it back — and reads as set aside. */
.rd-cardgrid__item--retired { opacity: 0.62; }

.rd-cardgrid__actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-3);
}

/* A form holding one button is still a form, and its default display would
   break the action row onto its own line. */
.rd-cardgrid__form { display: contents; }

.rd-cardgrid__note {
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--text-muted);
}

/* ---- The editor ---------------------------------------------------------
   Form and preview side by side, because the preview is the point: the member
   is writing something that will be printed and handed over. */
.rd-cardeditor {
  display: grid;
  grid-template-columns: minmax(0, 460px) minmax(0, 1fr);
  gap: var(--space-10);
  align-items: start;
}

.rd-cardeditor__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-7);
  min-width: 0;
}

/* The phone and social repeaters: a narrow label beside the value, one row
   per entry, the blank tail row always offered. No add/remove chrome — a row
   emptied out is a row removed on save, so the rows ARE the control. */
.rd-cardeditor__repeat {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  border: 0;
  padding: 0;
  margin: 0;
  min-width: 0;
}

.rd-cardeditor__repeat > legend {
  padding: 0;
  margin-bottom: var(--space-2);
}

.rd-cardeditor__repeatrow {
  display: flex;
  gap: var(--space-4);
  min-width: 0;
}

.rd-cardeditor__repeatrow > .rd-input__control { min-width: 0; }

.rd-cardeditor__repeatlabel {
  flex: none;
  width: 34%;
  max-width: 160px;
}

.rd-cardeditor__save {
  display: flex;
  justify-content: flex-end;
}

.rd-cardeditor__preview {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  min-width: 0;
  /* The preview follows the form down a long page: the fields near the
     bottom — the accent, the service area — are exactly the ones whose
     effect is hardest to picture. */
  position: sticky;
  top: calc(var(--topbar-h) + var(--space-6));
}

.rd-cardeditor__previewhead {
  display: flex;
  align-items: center;
  gap: var(--space-5);
}

.rd-cardeditor__eyebrow {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}

.rd-cardeditor__sides {
  margin-left: auto;
  display: flex;
  gap: var(--space-3);
}

.rd-cardeditor__previewactions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.rd-cardeditor__note {
  max-width: 44ch;
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/* Two fields that belong together on one line — phone and the email select. */
.rd-fieldrow {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-6);
}

.rd-pagehead__meta {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.rd-backlink {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
}

/* ---- The accent picker --------------------------------------------------
   A closed palette, so it is radios rather than a colour input: six named
   hues and "the business's", and nothing a member can type. */
.rd-accents {
  border: 0;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.rd-accents__row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
}

.rd-accents__swatch {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  cursor: pointer;
  font-size: var(--text-2xs);
  color: var(--text-muted);
}

/* The radio itself is visually hidden rather than display:none, so it keeps
   its place in the tab order and its focus ring can be drawn on the dot. */
.rd-accents__swatch input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.rd-accents__dot {
  width: 24px;
  height: 24px;
  border-radius: var(--radius-pill);
  background: var(--accent);
  border: 2px solid transparent;
  box-shadow: 0 0 0 1px var(--border-subtle);
  transition: var(--transition-control);
}

/* "Whatever the business wears" is a real choice, drawn as the absence of a
   colour rather than as a seventh hue. */
.rd-accents__swatch--inherit .rd-accents__dot {
  background: var(--bg-sunken);
  border-color: var(--border-strong);
}

.rd-accents__swatch input:checked + .rd-accents__dot {
  border-color: var(--surface-card);
  box-shadow: 0 0 0 2px var(--border-focus);
}

.rd-accents__swatch input:focus-visible + .rd-accents__dot {
  outline: 2px solid var(--border-focus);
  outline-offset: 2px;
}

.rd-accents__swatch input:checked ~ .rd-accents__name { color: var(--text-heading); }

/* ---- Your email addresses ----------------------------------------------
   Claiming an address and picking one are the same screen: a member who has
   to leave the editor to add an address loses the card they were writing. */
.rd-addresses {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: var(--space-6);
  background: var(--bg-sunken);
  border-radius: var(--radius-md);
}

.rd-addresses__title {
  margin: 0;
  font: var(--type-label);
  color: var(--text-heading);
}

.rd-addresses__list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.rd-addresses__row {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  min-width: 0;
}

.rd-addresses__addr {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-body);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The row's actions sit at its end; the first one claims the gap. */
.rd-addresses__act { display: contents; }
.rd-addresses__row .rd-addresses__act:first-of-type .rd-linkbutton { margin-left: auto; }

.rd-addresses__add {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.rd-addresses__addrow {
  display: flex;
  gap: var(--space-4);
  align-items: center;
}

/* A submit that reads as a link, for the small in-row actions the button
   scale would shout with. */
.rd-linkbutton {
  border: 0;
  background: none;
  padding: 0;
  font-family: var(--font-sans);
  font-size: var(--text-xs);
  color: var(--text-link);
  cursor: pointer;
}

.rd-linkbutton:hover {
  color: var(--text-link-hover);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* ---- Phone -------------------------------------------------------------- */
@media (max-width: 720px) {
  .rd-cardeditor {
    grid-template-columns: 1fr;
    gap: var(--space-8);
  }
  /* Sticky costs a phone a band of the screen it has least of, and the form
     is a single column here anyway. */
  .rd-cardeditor__preview { position: static; }
  .rd-cardgrid { grid-template-columns: 1fr; }
  .rd-fieldrow { grid-template-columns: 1fr; }
  .rd-cardgroup__action { margin-left: 0; }
  .rd-cardgrid__note { margin-left: 0; }
}

/* ==== The deck editor's Card tab =========================================
   The one card that fronts a deck, chosen from the owner's own. Radios rather
   than links: it is one choice among a closed set, and "No card" is one of
   the options rather than the absence of one. */

/* The repo has three copies of this already (shell.css, app.css, admin.css),
   each hanging off the one element that needed it. This is the fourth place
   a legend has to exist for a fieldset and not be seen. */
.rd-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.rd-deckcards {
  display: flex;
  flex-direction: column;
  gap: var(--space-7);
}

.rd-deckcards__set {
  border: 0;
  margin: 0;
  padding: 0;
}

.rd-deckcards__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: var(--space-6);
  align-items: start;
}

/* The tile is the label, so the whole card is the hit area — a radio beside a
   card would be a second thing to aim at on the one screen where the card IS
   the choice. */
.rd-deckcards__choice {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-4);
  border: 2px solid var(--border-subtle);
  border-radius: var(--radius-xl);
  cursor: pointer;
  transition: var(--transition-control);
}

.rd-deckcards__choice:hover { border-color: var(--border-strong); }

/* Chosen: the tile takes the card's own colour, which is also the colour the
   deck is about to wear. */
.rd-deckcards__choice--on {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 5%, var(--surface-card));
}

.rd-deckcards__choice input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.rd-deckcards__choice input:focus-visible ~ .rd-deckcards__label {
  outline: 2px solid var(--border-focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

.rd-deckcards__label {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: 0 var(--space-3) var(--space-1);
}

.rd-deckcards__dot {
  flex: none;
  width: 16px;
  height: 16px;
  border-radius: var(--radius-pill);
  border: 2px solid var(--border-strong);
  background: transparent;
  transition: var(--transition-control);
}

.rd-deckcards__choice--on .rd-deckcards__dot {
  border-color: var(--accent);
  background: var(--accent);
}

/* The "No card" tile takes the ink rather than an accent: it is the absence
   of a colour choice, so colouring it would contradict what it means. */
.rd-deckcards__choice--none.rd-deckcards__choice--on {
  border-color: var(--text-heading);
  background: transparent;
}

.rd-deckcards__choice--none.rd-deckcards__choice--on .rd-deckcards__dot {
  border-color: var(--text-heading);
  background: var(--text-heading);
}

.rd-deckcards__name {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text-heading);
}

/* Dashed, at the height of the cards beside it, so the row reads as one set
   of choices rather than as cards plus an afterthought. */
.rd-deckcards__choice--none {
  border-style: dashed;
  justify-content: center;
  min-height: 200px;
  padding: var(--space-6);
}

.rd-deckcards__nonenote {
  padding: 0 var(--space-3);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.rd-deckcards__save {
  display: flex;
  justify-content: flex-end;
}

@media (max-width: 720px) {
  .rd-deckcards__grid { grid-template-columns: 1fr; }
  .rd-deckcards__save { justify-content: stretch; }
  .rd-deckcards__save .rd-button { width: 100%; }
}

/* ==== A contact's cards, and where they appear ===========================
   The contact page's two new sections (design 2a). The first is read-only —
   they wrote these — and the second is the one thing on the page you write. */
.rd-contactcards,
.rd-deckpicks {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

.rd-contactcards__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
  gap: var(--space-8);
  align-items: start;
}

.rd-contactcards__group {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  min-width: 0;
}

/* The stack is one link over the whole shape, because the thing being
   clicked is "these cards", not a control beside them. */
.rd-contactcards__stacklink {
  display: block;
  text-decoration: none;
  color: inherit;
}

.rd-contactcards__stacklink:hover { text-decoration: none; }

.rd-contactcards__note {
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.rd-contactcards__toggle {
  align-self: flex-start;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
}

.rd-deckpicks__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  max-width: 620px;
}

.rd-deckpicks__row {
  display: flex;
  align-items: center;
  gap: var(--space-5);
}

.rd-deckpicks__deck {
  flex: 0 0 200px;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.rd-deckpicks__name {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text-heading);
}

.rd-deckpicks__slug {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--text-muted);
}

.rd-deckpicks__select {
  flex: 1;
  min-width: 0;
}

@media (max-width: 720px) {
  .rd-contactcards__grid { grid-template-columns: 1fr; }
  /* The row becomes a stack: three things side by side at 390px leaves the
     select too narrow to read a card's role in. */
  .rd-deckpicks__row {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-4);
  }
  .rd-deckpicks__deck { flex: none; }
  .rd-deckpicks__row .rd-button { width: 100%; }
}

/* ==== The rolodex, two ways ==============================================
   List or Cards (design 3a). List stays the dense rolodex it has always been
   and gains one column; Cards shows each person's own card, which is the
   thing they wrote and the thing a visitor to a deck will meet. */
.rd-rolodex__views {
  display: flex;
  gap: var(--space-3);
  justify-content: flex-end;
}

/* The count sits before the chips, right-aligned in a fixed column so the
   numbers line up down the list rather than drifting with the meta beside
   them. */
.rd-rolodex__cardcount {
  flex: none;
  width: 78px;
  text-align: right;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--text-muted);
}

/* A member who publishes cards is the row worth noticing, so the count takes
   the accent only when there is one. */
.rd-rolodex__cardcount--some { color: var(--accent); }

.rd-rolodex__cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: var(--space-8);
  align-items: start;
}

.rd-rolodex__cardcell {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  min-width: 0;
}

.rd-rolodex__cardlink {
  display: block;
  text-decoration: none;
  color: inherit;
}

.rd-rolodex__cardlink:hover { text-decoration: none; }

.rd-rolodex__cardname {
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/* No card yet. Dashed, because it is a slot rather than a thing — and
   person-first like every card beside it: the company is an attribute of
   somebody you know, never the other way round. */
.rd-rolodex__nocard {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  padding: var(--space-6);
  background: var(--surface-card);
  border: var(--border-width) dashed var(--border-strong);
  border-radius: var(--radius-lg);
}

.rd-rolodex__nocardwho {
  display: flex;
  align-items: flex-start;
  gap: var(--space-5);
}

.rd-rolodex__nocardnames {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.rd-rolodex__nocardname {
  font: var(--type-heading);
  color: var(--text-heading);
  text-decoration: none;
}

.rd-rolodex__nocardtrade {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}

.rd-rolodex__nocardco {
  font-size: var(--text-sm);
  color: var(--text-body);
}

.rd-rolodex__nocardnote {
  font-size: var(--text-xs);
  color: var(--text-muted);
}

@media (max-width: 720px) {
  .rd-rolodex__cards { grid-template-columns: 1fr; }
  /* The dense row is already tight at 390px; the count would push the meta
     out before it earned its column. It is a second-glance number, and the
     card view is where cards are the subject. */
  .rd-rolodex__cardcount { display: none; }
}
/* assets/deck.css */
/* Refdeck public deck surface — the screen a stranger reaches by scanning a
   QR code, and the only one they ever see. Ported from the design system's
   own kit for it: design-system/ui_kits/public_refdeck/ (RefdeckHeader,
   RefdeckFilters, RefdeckGrid, RefdeckFooter).

   SCOPE: composition only. The cards, chips, search field and empty state are
   the primitives in components.css (.rd-contact-card, .rd-tag, .rd-search,
   .rd-empty-state) — this file places them, and defines the four regions the
   kit is made of: the band, the sticky filter bar, the grid and the footer.

   WHY THIS LAYER SHIPS LAST: it reaches inside the primitives it composes
   (it sizes .rd-search to fill the filter row, and raises .rd-tag to a
   thumb-sized target on a phone). Equal specificity is broken by document
   order, so composing anything ahead of components.css would silently lose.

   THE ACCENT IS DATA. --accent is redefined per request by public_deck.html
   from the deck's chosen palette colour, so every var(--accent) below is the
   deck owner's branding rather than a fixed hue. All six palette entries are
   the design system's 600-level hues, which is what makes white-on-accent
   text safe for the band on every deck.

   Every value is a token, per the rule the other layers keep. The one
   dimension written by value is 720px, the breakpoint — the token set has no
   breakpoint scale, and the kit's own useIsMobile() hook is the same single
   switch between the phone layout and the wide one. */

/* Mobile-first: everything below is the phone layout, and the one media
   query at the end widens it. */

/* ─── The public page frame ──────────────────────────────────────────────
   The public surface opts out of .app-shell (it runs full-bleed colour to
   the viewport edge), so it gets none of shell.css's frame. base.css already
   resets the box model and sets the body font — all that is left is the
   column that keeps the footer at the bottom of a short deck. */
.public-page {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

/* [hidden] is a UA rule at specificity (0,1,0) and loses to any class that
   sets display — which both the grid and the empty state do. The instant
   filter toggles exactly those, so without this the "no results" state would
   render UNDER a grid that never went away. */
.public-page [hidden] {
  display: none !important;
}

.public-page > main {
  flex: 1;
  min-width: 0;
  width: 100%;
  max-width: var(--deck-max);
  margin-inline: auto;
  padding-inline: var(--space-6);
}

/* ─── The band (RefdeckHeader.jsx) ───────────────────────────────────────
   The owner's branding: their colour behind their photo, name and welcome.
   The kit hard-codes teal-900 because its demo deck is teal; here the same
   surface takes whichever palette colour the owner picked. */
.rd-deckband {
  background: var(--accent);
  color: var(--text-on-dark);
}

/* A band carrying an uploaded background (crit:5327f65ab034). Which picture a
   deck has is data, set as --band-image by the page's own style block; how a
   band with one LOOKS is here.

   The accent stays underneath rather than being replaced. A photograph still
   loading, failing to load, or narrower than the viewport leaves the band its
   own colour instead of a white gap — the unset rendering is the floor, not a
   fallback somebody had to remember to arrange.

   The ::before layer is the scrim (crit:efb3d60be164), and it is not
   decoration: a member may upload any photograph, this band always carries
   white text, and over a pale sky the deck's own name would otherwise be
   unreadable — and it WAS: one layer of the token measured 1.5:1 over a
   blown-out sky. It is built from the design system's --scrim token rather
   than a hand-picked black, it is layered so the floor clears WCAG AA, and
   the gradient over that floor is why the top bar — small text against
   whatever the photo does at its edge — gets more cover than the middle. */
.rd-deckband--photo {
  position: relative;
  background-color: var(--accent);
  background-image: var(--band-image);
  background-size: cover;
  /* Where the picture's people are, when the page knows (a gallery scene,
     PRD #783): on a phone the band is a tall narrow window onto a wide
     picture, and centring it can frame nobody. The token's own value is
     center, which is what every band without a focal point gets. */
  background-position: var(--band-focus);
  isolation: isolate;
}

.rd-deckband--photo::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  /* Three layers of the one --scrim token, never a hand-picked black.

     The token is rgba(…, 0.42), and one layer of it is not enough: measured
     against a blown-out sky (the worst photograph a member can upload, and a
     common one), a single 0.42 wash leaves --text-on-dark at 2.4:1. The
     gradient this replaced was LIGHTEST across the middle — which is exactly
     where the deck's name sits — and measured 1.5:1 there. The band was
     rendering the deck's own name unreadable.

     So the floor is two flat layers of the token, 1-(1-0.42)² = 0.66, which
     puts the worst case at 5.2:1 — clear of WCAG AA for the intro line as
     well as the title. The gradient on top of them is the original design
     intent kept: more cover at the top bar, whose small text meets whatever
     the photograph does at its edge, and at the bottom seam.

     linear-gradient(c, c) is the CSS idiom for a flat colour as a background
     LAYER; the floor cannot be background-color here, because the preview's
     copy of this rule paints the picture as a layer of its own and a
     background-color would sit underneath it.

     TestTheBandScrimKeepsItsTextLegible computes the composite from these
     declarations and the token, so a scrim someone lightens later fails the
     suite instead of shipping. */
  background-image:
    linear-gradient(
      to bottom,
      var(--scrim) 0%,
      transparent 25%,
      transparent 75%,
      var(--scrim) 100%
    ),
    linear-gradient(var(--scrim), var(--scrim)),
    linear-gradient(var(--scrim), var(--scrim));
}

/* Everything the band draws sits above the scrim. Without this the children
   paint underneath it and the scrim dims the text it exists to protect. */
.rd-deckband--photo > * {
  position: relative;
  z-index: 1;
}

.rd-deckband__bar {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  max-width: var(--deck-max);
  height: 48px;
  margin-inline: auto;
  padding-inline: var(--space-6);
  border-bottom: var(--border-width) solid color-mix(in srgb, var(--text-on-dark) 15%, transparent);
}

.rd-deckband__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  font-family: var(--font-serif);
  font-size: var(--text-md);
  color: var(--text-on-dark);
  text-decoration: none;
}

.rd-deckband__brand:hover,
.rd-deckband__brand:focus-visible {
  text-decoration: underline;
}

/* The route serves the 56px rendering for a 28px box: 2x for a retina
   screen without shipping the 512, the same trade the app shell makes. */
.rd-deckband__mark {
  width: 28px;
  height: 28px;
  border-radius: var(--radius-sm);
}

/* The muted-on-dark tone. The kit reads teal-200 off its fixed teal band;
   mixing the on-dark text colour instead keeps it legible on all six
   palette colours, and writes no colour by value. */
.rd-deckband__slug {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: color-mix(in srgb, var(--text-on-dark) 72%, transparent);
}

/* The band's Share control (share.go): a ghost button in the on-dark tone,
   pushed to the bar's far end. The ghost variant's own colours are the light
   surface's, so they are restated here against the band. */
.rd-deckband__share {
  margin-left: auto;
  gap: var(--space-2);
  color: var(--text-on-dark);
  border: var(--border-width) solid color-mix(in srgb, var(--text-on-dark) 35%, transparent);
}

/* Hover and focus brighten the BORDER only. A translucent light fill behind
   white text composites against the scrimmed photograph and falls below
   AA over a bright upload (scrimlegibility_test.go), so the fill stays
   what the band is. */
.rd-deckband__share:hover,
.rd-deckband__share:focus-visible {
  color: var(--text-on-dark);
  background: transparent;
  border-color: color-mix(in srgb, var(--text-on-dark) 70%, transparent);
}

.rd-deckfoot__body a {
  color: var(--text-link);
}

/* What a Refdeck is (public_deck.html). The bar above says the product's
   name and the hero below is the owner's; this strip between them is the one
   place the page explains itself to a visitor who arrived from a QR code.

   It is a rule and a line of small type rather than a card, deliberately: a
   plate here would read as content — a first item above the owner's own name
   — and the band belongs to the owner. So it borrows the bar's own divider,
   sits at the bar's width, and lets the hero keep every bit of the weight. */
.rd-deckband__about {
  max-width: var(--deck-max);
  margin-inline: auto;
  padding: var(--space-5) var(--space-6);
  border-bottom: var(--border-width) solid color-mix(in srgb, var(--text-on-dark) 15%, transparent);
}

/* --prose-max, the design system's own reading measure: two lines of small
   type on a wide screen, three on a phone. The band's identity column (58ch)
   is narrower and was tried first; it broke the sentence across three lines
   and left "no account needed" alone on the last one. */
.rd-deckband__abouttext {
  max-width: var(--prose-max);
  font: var(--type-body-sm);
  color: color-mix(in srgb, var(--text-on-dark) 82%, transparent);
}

/* The question, carrying the weight the sentence after it does not need. */
.rd-deckband__aboutlabel {
  font-weight: var(--weight-semibold);
  color: var(--text-on-dark);
}

.rd-deckband__hero {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  max-width: var(--deck-max);
  margin-inline: auto;
  padding: var(--space-8) var(--space-6) var(--space-9);
}

.rd-deckband__photo {
  flex: 0 0 auto;
  width: 72px;
  height: 72px;
  border: var(--border-width) solid color-mix(in srgb, var(--text-on-dark) 25%, transparent);
  border-radius: var(--radius-lg);
  object-fit: cover;
}

/* A deck with no photo yet keeps the shape rather than collapsing the hero,
   so an owner who has not uploaded one still gets a designed page. */
.rd-deckband__photo--empty {
  display: grid;
  place-items: center;
  background: color-mix(in srgb, var(--text-on-dark) 12%, transparent);
  font: var(--type-meta);
  letter-spacing: var(--tracking-wide);
  color: color-mix(in srgb, var(--text-on-dark) 72%, transparent);
  text-transform: uppercase;
}

.rd-deckband__identity {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  max-width: 58ch;
}

.rd-deckband__title {
  font-family: var(--font-serif-alt);
  font-size: var(--text-2xl);
  font-weight: var(--weight-regular);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
  color: var(--text-on-dark);
}

.rd-deckband__intro {
  font: var(--type-body);
  color: color-mix(in srgb, var(--text-on-dark) 82%, transparent);
}

/* ─── The hero's copy column (PRD #718) ──────────────────────────────────
   The platform's headline, a one-line lede and the way into the network come
   first; the owner's picture, name and note follow under a hairline. The
   column is the whole hero when the deck has no background picture — nothing
   is reserved for one — and takes the left half once there is a picture to
   fill the right half (the wide block below). Every colour is the band's own
   on-dark text or a mix of it, the same mixes the intro and the slug already
   wear, so the band-scrim guards read it like the rest of the band. */
.rd-deckhero__copy {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  min-width: 0;
  max-width: 60ch;
}

.rd-deckhero__headline {
  font-family: var(--font-serif-alt);
  font-size: var(--text-3xl);
  font-weight: var(--weight-regular);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
  color: var(--text-on-dark);
  text-wrap: balance;
}

/* The second line, set apart the way the intro is: the same muted-on-dark
   mix rather than a second hue the six accents would each have to carry. */
.rd-deckhero__accent {
  color: color-mix(in srgb, var(--text-on-dark) 82%, transparent);
}

.rd-deckhero__lede {
  font: var(--type-body);
  font-size: var(--text-md);
  color: var(--text-on-dark);
  max-width: 44ch;
}

.rd-deckhero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
}

/* The owner, under the platform's copy: picture beside name and note, as the
   hero laid them before the headline arrived above them. */
.rd-deckhero__owner {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding-top: var(--space-6);
  border-top: var(--border-width) solid color-mix(in srgb, var(--text-on-dark) 18%, transparent);
}

/* A personal chapter link's referrer is the same trade_head block the cards
   render (PRD #446), set inside the accent band. The band keeps its own tone
   and bleeds nowhere — there is no card edge to meet — the person takes the
   band's title type, and the company the intro's muted-on-dark tone. */
.rd-deckband__hero .rd-tradeband {
  margin: 0;
  border-radius: var(--radius-lg);
}

.rd-deckband__hero .rd-contact-card__person {
  font-family: var(--font-serif-alt);
  font-size: var(--text-2xl);
  font-weight: var(--weight-regular);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
  color: var(--text-on-dark);
}

.rd-deckband__hero .rd-contact-card__company {
  color: color-mix(in srgb, var(--text-on-dark) 82%, transparent);
}

/* A personal chapter link's chapter-member line carries the chapter's own
   mark, small, when its owner has uploaded one (crit:45105c5feafa). */
.rd-deckband__chapter {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

/* Over a photograph, nothing in this band renders translucent
   (crit:efb3d60be164).

   The rules above build their quieter tones by mixing --text-on-dark down
   towards transparent, which is the right way to be quiet on the accent: the
   band's colour is known, so the composite is known. On an uploaded picture
   it is not. What a translucent glyph composites against is the backdrop,
   and there the backdrop is whatever the scrim left of a photograph the
   member chose — so the same declaration that reads as "slightly quieter" on
   teal reads as unreadable on a snow scene. Measured over the worst upload
   there is, a pure white frame, the 82% intro came out at 4.11:1 and the 72%
   slug at 3.57:1, both under WCAG AA, both sitting across the middle of the
   band where the scrim's gradient gives least.

   Thickening the scrim would fix it too, and costs more: another flat layer
   takes coverage from 0.66 to 0.81, which is a fifth of every member's
   photograph traded away to keep a tone nobody asked for. Going opaque costs
   the tone alone, and the band still has type size, weight and family to say
   the same thing with. So on a picture these lines take the full token, and
   the hierarchy is carried by the type.

   Every selector here names the picture's own element and then the target,
   with nothing in between, and that is load-bearing rather than tidy.
   `rd-deckband--photo` is not always on the same element: the public deck
   wears it on the whole <header>, while a chapter page and a personal
   chapter link wear it on `.rd-deckband__hero` itself, because on those two
   the picture is the hero and the bar above it is not. So an override
   written `.rd-deckband--photo .rd-deckband__hero X` matches on the deck and
   matches NOTHING on the two chapter surfaces — a descendant combinator
   never matches the element it is written on. The company line under a
   personal link's trade head shipped that way and kept its 82% tone over the
   member's photograph, at 4.11:1.

   TestEveryTextTokenOverABandScrimClearsAA composites every one of these
   from the declarations. It cannot catch that mistake, because it pairs a
   base rule with its override by their last class and never asks whether the
   override MATCHES; TestEveryTextOverARenderedBandClearsAA resolves the same
   cascade against the DOM each surface actually serves, which does. */
.rd-deckband--photo .rd-deckband__slug,
.rd-deckband--photo .rd-deckband__abouttext,
.rd-deckband--photo .rd-deckband__intro,
.rd-deckband--photo .rd-deckhero__accent,
.rd-deckband--photo .rd-deckband__identity .rd-contact-card__company {
  color: var(--text-on-dark);
}

/* The initials plate is the one that was worst — 2.92:1 — because its 12%
   white plate LIGHTENS the backdrop under 72% text: the scrim darkens the
   picture and the plate undoes it. On a photograph the plate becomes another
   layer of the same scrim, so it darkens with the band instead of against
   it, and the initials take the full token like everything else. */
.rd-deckband--photo .rd-deckband__photo--empty {
  background: var(--scrim);
  color: var(--text-on-dark);
}

/* ─── The filter bar (RefdeckFilters.jsx) ────────────────────────────────
   Sticky, because scanning a long deck and then reaching for the search is
   the whole interaction. Translucent over the page it pins above. */
.rd-deckfilters {
  position: sticky;
  top: 0;
  z-index: 10;
  background: color-mix(in srgb, var(--bg-page) 94%, transparent);
  backdrop-filter: saturate(1.3) blur(8px);
  border-bottom: var(--border-width) solid var(--border-subtle);
}

.rd-deckfilters__inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  max-width: var(--deck-max);
  margin-inline: auto;
  padding-block: var(--space-5) var(--space-4);
}

.rd-deckfilters__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-4) var(--space-5);
  padding-inline: var(--space-6);
}

/* Composing the primitive: the search field takes the row. */
.rd-deckfilters__row .rd-search {
  flex: 1 1 16rem;
  min-width: 0;
}

.rd-deckfilters__count {
  margin-left: auto;
  font: var(--type-meta);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* On a phone the chips are one horizontally scrollable row rather than four
   wrapped lines — the kit's note, and the reason for the negative-free
   inline padding: the row scrolls past the page gutter. */
.rd-deckfilters__chips {
  display: flex;
  gap: var(--space-4);
  overflow-x: auto;
  padding: var(--space-1) var(--space-6);
  scrollbar-width: none;
}

.rd-deckfilters__chips::-webkit-scrollbar {
  display: none;
}

/* Composing the primitive: a chip is a thumb target here, not a label. */
.rd-deckfilters__chips .rd-tag {
  height: var(--control-h-md);
  flex: 0 0 auto;
}

/* ─── The grid (RefdeckGrid.jsx) ─────────────────────────────────────────
   One column on a phone, and as many 304px columns as fit above that. */
.rd-deckgrid {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  padding-block: var(--space-8) var(--space-10);
}

.rd-deckgrid__label {
  font: var(--type-meta);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* The directory's heading (PRD #718): the section the hero's call to action
   lands on, so it reads as a title rather than a muted caps label. */
.rd-deckgrid__title {
  margin: 0;
  font: var(--type-title);
  color: var(--text-heading);
}

.rd-deckgrid__items {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
  margin: var(--space-0);
  padding: var(--space-0);
  list-style: none;
}

/* The card is the whole tap target, so the anchor IS the card. a.rd-card in
   components.css already undoes base.css's link colour and underline. */
.rd-deckgrid__items > li > .rd-card {
  height: 100%;
}

/* The kit passes fullWidth to the stacked card's button, which is the phone
   layout. .rd-contact-card__action carries margin-left:auto for the ROW
   layout, and that auto margin is what stops it stretching in a column —
   so the phone layout has to put it back. */
.rd-deckgrid .rd-contact-card__action {
  margin-left: 0;
}

/* ─── The compact directory (PRD #771) ──────────────────────────────────
   The network as small tiles rather than full cards: the company's mark
   centred above the business name, the person's name and their category.
   Which layout a deck wears is the owner's data, carried on <body> as
   data-layout (internal/store/layouts.go), so the grid keys its columns on
   it here: two across on a phone, and past 720px as many as fit at a tile's
   measure (the wide block below). The tile keeps rd-contact-card, so a
   theme's card rules (themes.css) and the tap gesture reach it; only the
   column gap and the alignment are its own. */
.public-page[data-layout="compact"] .rd-deckgrid__items {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-4);
}

.rd-decktile {
  position: relative;
  align-items: center;
  gap: var(--space-3);
  overflow: hidden;
  text-align: center;
}

/* The trade's tone along the top edge: the colour the card's band would
   wear, so one category is one colour on both layouts. The tone classes
   (components.css, rd-tradeband--tone-N) set only a background, which is
   all a stripe needs. */
.rd-decktile__stripe {
  position: absolute;
  inset: 0 0 auto 0;
  height: var(--space-2);
}

/* The mark, centred in a slot of fixed height so a grid of tiles keeps its
   baselines whether a company has a wide wordmark, a square mark, or none —
   in which case the person's portrait (the shared trade_portrait block)
   stands in the slot. */
.rd-decktile__logo {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: var(--space-13);
  padding-top: var(--space-2);
}

.rd-decktile__mark {
  display: block;
  width: auto;
  height: auto;
  max-width: 80%;
  max-height: var(--space-12);
  object-fit: contain;
}

.rd-decktile__business {
  font: var(--type-label);
  color: var(--text-heading);
  overflow-wrap: anywhere;
}

.rd-decktile__person {
  font: var(--type-body-sm);
  color: var(--text-body);
  overflow-wrap: anywhere;
}

.rd-decktile__trade {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font: var(--type-meta);
  color: var(--text-muted);
}

/* The category's icon, the one its chip and the card's band wear, at the
   meta line's size rather than the band's. */
.rd-decktile__trade .rd-tradeband__icon {
  width: var(--space-5);
  height: var(--space-5);
  flex: none;
}

/* ─── The footer (RefdeckFooter.jsx) ─────────────────────────────────────
   What to do with the deck now that you have it: keep the link. */
.rd-deckfoot {
  margin-top: auto;
  background: var(--bg-sunken);
  border-top: var(--border-width) solid var(--border-subtle);
}

.rd-deckfoot__inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  max-width: var(--deck-max);
  margin-inline: auto;
  padding: var(--space-9) var(--space-6);
}

.rd-deckfoot__title {
  font: var(--type-subhead);
  color: var(--text-heading);
}

.rd-deckfoot__body {
  max-width: 44ch;
  font: var(--type-body-sm);
  color: var(--text-muted);
}

.rd-deckfoot__slug {
  font-family: var(--font-mono);
}

.rd-deckfoot__meta {
  font: var(--type-meta);
  color: var(--text-faint);
}

/* ─── How it works, and the values band (PRD #718) ───────────────────────
   Two strips of platform copy between the band and the network. The steps
   sit on the sunken paper like the footer; the values band is the deck's
   accent, like the band, so a blue deck reads blue here too. Every colour
   is a token the themes already re-ink or the on-dark text the accent is
   safe under (the six accents are the 600-level hues chosen for exactly
   that, components.css). */
.rd-deckhow {
  background: var(--bg-sunken);
  border-block: var(--border-width) solid var(--border-subtle);
}

.rd-deckhow__inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-7);
  max-width: var(--deck-max);
  margin-inline: auto;
  padding: var(--space-8) var(--space-6);
}

.rd-deckhow__head {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.rd-deckhow__eyebrow {
  font: var(--type-meta);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--highlight);
}

.rd-deckhow__title {
  font-family: var(--font-serif-alt);
  font-size: var(--text-xl);
  font-weight: var(--weight-regular);
  line-height: var(--leading-snug);
  color: var(--text-heading);
}

.rd-deckhow__steps {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  margin: 0;
  padding: 0;
  list-style: none;
}

.rd-deckhow__step {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  font: var(--type-body);
  color: var(--text-body);
}

/* The step number: a plate in the featured tint, the serif face, so it reads
   as a numeral rather than a badge. */
.rd-deckhow__n {
  display: grid;
  place-items: center;
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-pill);
  background: var(--highlight-soft);
  font: var(--weight-semibold) var(--text-md)/1 var(--font-serif);
  color: var(--highlight);
}

.rd-deckhow__icon {
  flex: none;
  width: 22px;
  height: 22px;
  color: var(--highlight);
}

.rd-deckhow__text {
  min-width: 0;
}

.rd-deckvalues {
  background: var(--accent);
  color: var(--text-on-dark);
}

.rd-deckvalues__inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  max-width: var(--deck-max);
  margin-inline: auto;
  padding: var(--space-9) var(--space-6);
}

.rd-deckvalues__title {
  font-family: var(--font-serif-alt);
  font-size: var(--text-2xl);
  font-weight: var(--weight-regular);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
  color: var(--text-on-dark);
  max-width: 18ch;
  text-wrap: balance;
}

.rd-deckvalues__items {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  margin: 0;
  padding: 0;
  list-style: none;
}

.rd-deckvalue {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-2) var(--space-5);
  align-items: start;
}

.rd-deckvalue__icon {
  grid-row: span 2;
  width: 26px;
  height: 26px;
  margin-top: var(--space-1);
  color: var(--text-on-dark);
}

.rd-deckvalue__title {
  font: var(--type-subhead);
  color: var(--text-on-dark);
}

.rd-deckvalue__body {
  font: var(--type-body-sm);
  color: var(--text-on-dark);
}

/* ─── The share page (share.go) ──────────────────────────────────────────
   The public page's share dialog as a page of its own, for a visitor with
   script off: the same centred panel the friendly 404 uses, carrying the
   sheet. */
.rd-decksharepage {
  display: grid;
  place-items: center;
  min-height: 100vh;
  padding: var(--space-7);
}

.rd-decksharepage__panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  width: 100%;
  max-width: 28rem;
}

.rd-decksharepage__back {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font: var(--type-body-sm);
  color: var(--text-link);
  text-decoration: none;
}

.rd-decksharepage__title {
  font: var(--type-title);
  color: var(--text-heading);
}

.rd-decksharepage__lede {
  font: var(--type-body);
  color: var(--text-body);
}

/* ─── The friendly 404 ───────────────────────────────────────────────────
   An unpublished or unknown deck. One body for every cause, centred on the
   viewport — a stranger who mistyped a link gets a designed page too. */
.rd-deck404 {
  display: grid;
  place-items: center;
  min-height: 100vh;
  padding: var(--space-7);
}

.rd-deck404__panel {
  max-width: 26rem;
  text-align: center;
}

.rd-deck404__mark {
  display: block;
  margin-inline: auto;
  color: var(--text-faint);
}

.rd-deck404__title {
  margin-top: var(--space-5);
  font-family: var(--font-serif-alt);
  font-size: var(--text-xl);
  font-weight: var(--weight-regular);
  line-height: var(--leading-snug);
}

.rd-deck404__body {
  margin-top: var(--space-4);
  font: var(--type-body-sm);
  color: var(--text-muted);
}

/* ─── The expanded card (/<deck-slug>/c/<id>, /<deck-slug>/b/<id>, /b/<slug>) ─
   The screen a visitor reaches by tapping a card on the deck. It is the same
   contact the grid showed, opened out: the grid card trades detail for
   scannability, and this is where the detail it dropped goes — the owner's
   testimonial in full, the business description, the ways to reach them, and
   the one action the page exists for.

   THREE PAGES, ONE SHEET. A business gets the same sheet twice over: inside a
   deck at /<deck-slug>/b/<id>, and on its own canonical page at /b/<slug>.
   The canonical one is the sheet with the deck taken away — no accent
   override, no testimonial (that belongs to a curation slot, not to the
   business) and no introduction CTA (that flow records a referral against a
   deck, and there is no deck behind it). What is left is the profile, which
   is why it is this composition rather than a fourth one.

   Composition only, like the rest of this file: the sheet is .rd-card, the
   avatar .rd-avatar, the tags .rd-badge and the action .rd-button. What is
   defined here is the arrangement.

   THE ACCENT IS STILL DATA. The quote rule and the action both take
   var(--accent), so they carry the deck owner's colour; the reassurance panel
   mixes that same colour into the card surface rather than naming a fixed
   teal, so it follows a bronze deck as readily as a teal one. */
/* THE FEATURED COLOUR IS DATA TOO. public_deck.html redefines the three
   --highlight tokens per request from the deck's featured-colour choice, so
   the featured card's tint and its badge's fill already follow it through
   components.css. Their borders and the badge's text named bronze-200 and
   bronze-700 by token, which would leave a red-featured deck with a bronze
   outline; mixed from --highlight here instead, so every part of the
   featured look answers to the one choice. */
.public-page .rd-contact-card--featured {
  border-color: color-mix(in srgb, var(--highlight) 32%, var(--surface-card));
}

/* The Featured flag itself, wherever it renders. Its fill is already
   --highlight-soft by token (components.css), so only the text and the edge
   needed lifting off the fixed bronze pair; scoped to the flag rather than
   to the featured CARD so the same flag reads in the same colour on the
   entry's own detail page, whose sheet is not a featured card. */
.public-page .rd-contact-card--featured .rd-badge--bronze,
.public-page .rd-contact-card__flag .rd-badge--bronze {
  color: var(--highlight);
  border-color: color-mix(in srgb, var(--highlight) 32%, var(--surface-card));
}

/* AND NOT THE BAND (crit:846af1244ba3). A rule here painting the trade band
   from --highlight is the regression this comment stands in the way of: it
   was tried, and it broke the criterion twice over — the starred card's
   colour stopped being a function of the folded category key, and one
   category rendered two colours on the same deck, because a three-selector
   rule beat the one-selector tone class that was still in the markup. The
   band's fill is .rd-tradeband--tone-* (components.css) and nothing else,
   which is also what keeps the AA contrast proof over those twelve tones a
   proof about every band a visitor can be served. The featured choice has
   the card's surface, the card's edge and the flag above; the band belongs
   to the trade. TestAStarredCardsBandKeepsItsCategoryTone reads this sheet
   as served and fails on any rule whose subject is a band and which sets a
   background. */

.rd-deckband__back {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  font: var(--type-label);
  color: var(--text-on-dark);
  text-decoration: none;
}

.rd-deckband__back:hover {
  color: var(--text-on-dark);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.rd-deckdetail {
  display: flex;
  flex-direction: column;
  gap: var(--space-7);
  margin-block: var(--space-8) var(--space-11);
}

/* A flex item's min-width is auto, which resolves to its MIN-CONTENT, so a row
   whose content cannot shrink pushes the sheet wider than the page. Everything
   on this sheet is user data — a business name, a service area, a website — so
   the guard is standing rather than a reaction to one long string. The deck is
   reached by scanning a QR code, which makes the phone the case that matters. */
.rd-deckdetail > * {
  min-width: 0;
}

.rd-deckdetail__head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-6);
  min-width: 0;
}

/* The deck detail page stacks the trade band over the person row
   (PRD #446): a column, so the band, when there is one, spans the sheet's
   full width and the row below it reads trade → person → company. A
   modifier rather than a descendant rule because the canonical /b/ page
   sits inside the same .rd-deckdetail and keeps its head as one row. */
.rd-deckdetail__head--stacked {
  flex-direction: column;
  align-items: stretch;
}

/* The trade band: the type of business, in full, as the first thing on the
   sheet. It wraps rather than truncates — "Residential Mortgage Broker" is
   the whole point of the page, not a caption. The colour is the deck's
   accent for now; the per-category colour (crit:846af1244ba3) lands on
   this same rule. It bleeds to the card's edges so it reads as a band
   across the top rather than a pill floating inside it. */
.rd-deckdetail__who {
  display: flex;
  align-items: flex-start;
  gap: var(--space-6);
  min-width: 0;
}

.rd-deckdetail__identity {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

.rd-deckdetail__title {
  font-family: var(--font-serif);
  font-size: var(--text-2xl);
  line-height: var(--leading-tight);
  color: var(--text-heading);
  /* A business name is user data and may be one long unbroken string. */
  overflow-wrap: anywhere;
}

.rd-deckdetail__person {
  font: var(--type-body-sm);
  color: var(--text-body);
}

/* ─── The shared trade head on the sheet (PRD #446, crit:cc61e4318f0d) ──
   The contact detail page renders the SAME trade_head block the deck cards
   and the chapter pages do; nothing here is a second head, only the sheet's
   scale for the block's own classes. Three-class selectors on purpose: the
   phone-type block below sizes .public-page .rd-contact-card__person and
   __company for the card grid, and a page's heading is not a card line —
   these outrank it rather than racing it on order. */
.public-page .rd-deckdetail .rd-contact-card__head {
  gap: var(--space-6);
}

.public-page .rd-deckdetail .rd-contact-card__identity {
  gap: var(--space-2);
}

/* The person is the page's one heading, so it takes the sheet's title type
   (.rd-deckdetail__title, which the canonical /b/ page keeps). */
.public-page .rd-deckdetail .rd-contact-card__person {
  font: var(--weight-regular) var(--text-2xl)/var(--leading-tight) var(--font-serif);
  color: var(--text-heading);
  /* A person's name is user data and may be one long unbroken string. */
  overflow-wrap: anywhere;
}

/* The company beneath the person: present, subordinate, body-small type
   with the logo as a small mark beside the name — the third fact on the
   sheet, not the first — and no meta tracking or casing, which read as a
   caption on a card and as a mistake on a page. */
.public-page .rd-deckdetail .rd-contact-card__company {
  margin-top: 0;
  font: var(--type-body-sm);
  letter-spacing: normal;
  color: var(--text-body);
  overflow-wrap: anywhere;
}

.public-page .rd-deckdetail .rd-contact-card__logoblock {
  height: 80px;
}

.public-page .rd-deckdetail .rd-contact-card__logo {
  max-height: 64px;
}

/* ─── The trade band on the sheet (PRD #446) ────────────────────────────
   The coloured strip the shared trade_head block opens with: the type of
   business in full, and the person's picture. It sits ACROSS the top of the
   sheet, so inside the padded card it pulls itself out to the card's edges
   with the card's own padding (.rd-card--pad-lg is --space-9) and takes the
   card's top radius, less the hairline so the corners meet.

   THE COLOUR comes from the band's tone class (crit:846af1244ba3), set in
   components.css from the folded category key, with the component's accent
   background as the fallback — so this layer declares no background at all:
   deck.css ships after components.css and an unscoped declaration here
   would win the tie and erase the tone. Scoped to the detail sheet so the
   deck cards' own band geometry (components.css, small-card padding) is
   untouched. */
.rd-deckdetail > .rd-tradeband {
  gap: var(--space-5);
  margin: calc(-1 * var(--space-9)) calc(-1 * var(--space-9)) 0;
  padding: var(--space-6) var(--space-9);
  border-radius: calc(var(--radius-lg) - var(--border-width)) calc(var(--radius-lg) - var(--border-width)) 0 0;
}

/* The band is the one place the person's picture sits on colour rather than
   on the card, so it borrows the deck band's photo-slot treatment: on-dark
   text mixed for the frame and the initials' fill, and no colour written by
   value. A photo or a mark keeps the card surface behind it — a face on a
   translucent tint reads as a mistake. */
.rd-tradeband .rd-avatar {
  flex: 0 0 auto;
  border-color: color-mix(in srgb, var(--text-on-dark) 25%, transparent);
  background: color-mix(in srgb, var(--text-on-dark) 12%, transparent);
  color: var(--text-on-dark);
}

.rd-tradeband .rd-avatar--photo,
.rd-tradeband .rd-avatar--image {
  background: var(--surface-card);
}

.rd-deckdetail__profession {
  margin-top: var(--space-1);
  font: var(--type-meta);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-muted);
}

.rd-deckdetail__tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

/* The owner's own words about this contact, which is why they lead the sheet
   rather than sitting under the description. The 3px rule is geometry the
   token set has no scale for — --border-width is the hairline used for
   boxes, and a quote bar at 1px reads as a rendering artefact. */
.rd-deckdetail__quote {
  padding-left: var(--space-6);
  border-left: 3px solid var(--accent);
  font: var(--type-body);
  font-style: italic;
  color: var(--text-heading);
}

.rd-deckdetail__body {
  font: var(--type-body);
  color: var(--text-body);
}

.rd-deckdetail__facts {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-4) var(--space-6);
  padding-top: var(--space-6);
  border-top: var(--border-width) solid var(--border-subtle);
  font: var(--type-body-sm);
}

.rd-deckdetail__facts dt {
  color: var(--text-muted);
}

.rd-deckdetail__facts dd {
  margin: 0;
  color: var(--text-body);
  overflow-wrap: anywhere;
}

.rd-deckdetail__cta {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  min-width: 0;
}

/* .rd-button is nowrap, which is right for a button sized by its label and
   wrong for a full-width action: on the narrowest phone the label plus its
   icon and padding has nowhere to go. Wrapping it costs nothing, since the
   button is already full width and its height is free to grow. */
.rd-deckdetail__cta .rd-button {
  white-space: normal;
  height: auto;
  min-height: var(--control-h-lg);
  padding-block: var(--space-4);
  text-align: center;
}

/* The business's own canonical page (/b/<slug>), offered from the deck-scoped
   sheet as a quiet second action beneath the introduction.

   Deliberately not a .rd-button: the introduction is what the deck-scoped page
   exists for, and a second control of equal weight would make a visitor choose
   between them rather than act. */
.rd-deckdetail__profile {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: baseline;
  align-self: flex-start;
  gap: var(--space-3);
  min-width: 0;
  font: var(--type-body-sm);
  color: var(--text-muted);
  text-decoration: none;
}

.rd-deckdetail__profile:hover {
  color: var(--accent);
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* The address itself, in the same mono the band and the footer set a slug in,
   so /b/<slug> reads as an address wherever it appears. */
.rd-deckdetail__profileslug {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-faint);
  overflow-wrap: anywhere;
}

/* The kit's reassurance panel (ui_kits/public_refdeck/RequestIntroDialog.jsx):
   the promise that answers the visitor's actual hesitation — that asking will
   cost them an account. Mixed from --accent so it is the owner's colour. */
.rd-deckpromise {
  display: flex;
  align-items: flex-start;
  gap: var(--space-5);
  padding: var(--space-5);
  border: var(--border-width) solid color-mix(in srgb, var(--accent) 26%, var(--surface-card));
  border-radius: var(--radius-md);
  background: color-mix(in srgb, var(--accent) 7%, var(--surface-card));
  font: var(--type-body-sm);
  color: var(--text-body);
}

.rd-deckpromise .rd-icon {
  color: var(--accent);
  margin-top: var(--space-1);
}

.rd-deckpromise > span {
  min-width: 0;
}

/* ─── The request form (/<deck-slug>/c/<id>/request) ─────────────────────
   The only thing a Refdeck ever asks a stranger to DO, ported from the kit's
   RequestIntroDialog.jsx. The prototype shows it as a Dialog; production
   serves it as a page, and stays a page — PRD 377's dialog phase (P4) names
   four OTHER forms and deliberately not this one, because this is the one
   form filled in by someone with no account, on a phone, from a QR code, and
   a page survives a refresh and a back button in a way a dialog does not.

   So what is ported is the dialog's COMPOSITION, not its chrome: the promise
   panel above the fields, name and email paired, phone and message optional
   with hints that say why, and one full-width action.

   Composition only, like the rest of this file — the fields are .rd-field /
   .rd-input / .rd-textarea and the action is .rd-button. */
.rd-deckrequest {
  display: flex;
  flex-direction: column;
  gap: var(--space-7);
  margin-block: var(--space-8) var(--space-11);
}

.rd-deckrequest__head {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.rd-deckrequest__title {
  /* Not --type-title: this heading ENDS in a business name, so at 30px a
     four-word name takes four lines on a phone and pushes the form itself
     below the fold — on the one screen whose entire job is to get filled in.
     It steps up to the title size once there is room. */
  font-family: var(--font-sans);
  font-size: var(--text-lg);
  font-weight: var(--weight-semibold);
  line-height: var(--leading-snug);
  color: var(--text-heading);
  /* A business name is user data and may be one long unbroken string —
     the same reason .rd-deckdetail__title carries this. */
  overflow-wrap: anywhere;
}

.rd-deckrequest__subtitle {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

.rd-deckrequest__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

/* One column on a phone, which is where a scanned QR code lands; the media
   query at the end pairs name and email once there is room. */
.rd-deckrequest__pair {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-6);
}

/* The honeypot. Positioned off-screen rather than display:none or
   visibility:hidden, both of which a bot reading computed styles skips —
   which would defeat the trap. Real visitors never see it; tabindex="-1" and
   aria-hidden keep it off the keyboard and screen-reader paths. */
.rd-deckrequest__trap {
  position: absolute;
  left: -10000px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* ─── The deck-level ask (PRD #718, deckrequest.go) ───────────────────────
   "Who can we introduce you to?" under the hero: the one form on the deck
   page itself. A sunken panel at the deck measure, the fields in one
   column on a phone and two on a wide screen, the needs and the consent
   spanning both. Composition only: the fields are the design system's
   .rd-field / .rd-input / .rd-select / .rd-checkbox primitives. */
.rd-deckask {
  background: var(--bg-sunken);
  border-bottom: var(--border-width) solid var(--border-subtle);
}

.rd-deckask__inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-7);
  max-width: var(--deck-max);
  margin-inline: auto;
  padding: var(--space-9) var(--space-6);
}

.rd-deckask__head {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.rd-deckask__title {
  margin: 0;
  font: var(--type-title);
  color: var(--text-heading);
}

.rd-deckask__lede {
  font: var(--type-body);
  color: var(--text-muted);
}

.rd-deckask__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

.rd-deckask__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-6);
}

.rd-deckask__actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.rd-deckask__note {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

/* ─── The confirmation (POST result) ─────────────────────────────────────
   A visitor who just handed over their email gets no account and no
   dashboard, so this page is the only receipt they will ever hold. It is
   centred and short on purpose: one mark, one sentence naming the person now
   holding the request, and the way back to the deck. */
.rd-deckdone {
  margin-block: var(--space-8) var(--space-11);
  text-align: center;
}

.rd-deckdone__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-12);
  height: var(--space-12);
  border-radius: var(--radius-pill);
  background: var(--status-success-bg);
  color: var(--status-success);
}

.rd-deckdone__title {
  margin-top: var(--space-6);
  font: var(--type-title);
  color: var(--text-heading);
}

.rd-deckdone__body {
  margin-top: var(--space-4);
  font: var(--type-body);
  color: var(--text-body);
}

.rd-deckdone__meta {
  margin-top: var(--space-3);
  font: var(--type-body-sm);
  color: var(--text-muted);
}

.rd-deckdone .rd-button {
  margin-top: var(--space-8);
}

/* ─── Phone type ─────────────────────────────────────────────────────────
   The kit's type scale was cut for a desktop card: body-sm (13px) for the
   description, xs (12px) for the facts line, meta (11px) for the company and
   the tags. Held at arm's length on a phone — which is where a deck is read,
   off a QR code — that is too small, and the owner said so (2026-08-25). The
   headings were fine and stay put; every line of REGULAR text on the public
   pages steps up one notch below the wide switch, so the desktop card keeps
   its density. Tokens only, and the phone-first rules above still hold the
   weight, colour and casing — this block changes sizes and nothing else. */
@media (max-width: 719px) {
  .public-page .rd-contact-card__person {
    font: var(--weight-medium) var(--text-base)/var(--leading-snug) var(--font-sans);
  }

  .public-page .rd-contact-card__company {
    font-size: var(--text-sm);
  }

  .public-page .rd-contact-card__description {
    font: var(--type-body);
  }

  .public-page .rd-contact-card__area {
    font-size: var(--text-base);
  }

  .public-page .rd-contact-card .rd-badge {
    font-size: var(--text-sm);
  }

  .public-page .rd-deckband__intro {
    font-size: var(--text-md);
  }

  .public-page .rd-deckband__abouttext,
  .public-page .rd-deckfoot__body,
  .public-page .rd-deck404__body,
  .public-page .rd-deckdetail__person,
  .public-page .rd-deckdetail .rd-contact-card__company,
  .public-page .rd-deckdetail__facts,
  .public-page .rd-deckdetail__profile,
  .public-page .rd-deckpromise,
  .public-page .rd-deckrequest__subtitle {
    font: var(--type-body);
  }

  .public-page .rd-deckdetail__profession {
    font-size: var(--text-sm);
  }
}

/* ─── Wide ───────────────────────────────────────────────────────────────
   The kit's useIsMobile() switch, as one media query. */
@media (min-width: 720px) {
  .public-page > main {
    padding-inline: var(--space-8);
  }

  .rd-deckband__bar {
    height: 52px;
    padding-inline: var(--space-8);
  }

  .rd-deckband__about {
    padding: var(--space-6) var(--space-8);
  }

  .rd-deckband__hero {
    flex-direction: row;
    align-items: flex-start;
    gap: var(--space-9);
    padding: var(--space-11) var(--space-8) var(--space-12);
  }

  /* The headline at the display size the band has room for. */
  .rd-deckhero__headline {
    font-size: var(--text-5xl);
  }

  /* With a background picture the band splits: the copy column keeps the
     left half and the picture fills the right, height-fitted and pinned to
     the right edge rather than stretched over the copy. The scrim the
     photo band always carries stays over the whole band — the picture is
     still drawn by the one rd-deckband--photo rule and still sits under
     rd-deckband--photo::before — so nothing about legibility changes, only
     where the picture is. :has() scopes it to the deck's own hero: the
     chapter-link hero wears the same modifier and keeps its full-bleed
     picture. */
  .rd-deckband--photo:has(.rd-deckhero__copy) {
    background-size: auto 100%;
    background-position: right center;
    background-repeat: no-repeat;
  }

  .rd-deckband--photo .rd-deckhero__copy {
    flex: 0 1 50%;
    max-width: 50%;
  }

  .rd-deckband__photo {
    width: 108px;
    height: 108px;
  }

  .rd-deckband__title,
  .rd-deckband__hero .rd-contact-card__person {
    font-size: var(--text-4xl);
  }

  /* The strips go to one row each: the head beside the three steps, the
     title beside the three values. */
  .rd-deckhow__inner {
    display: grid;
    grid-template-columns: 1fr 2fr;
    align-items: center;
    gap: var(--space-9);
    padding: var(--space-9) var(--space-8);
  }

  .rd-deckhow__head {
    padding-right: var(--space-8);
    border-right: var(--border-width) solid var(--border-default);
  }

  .rd-deckhow__steps {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-8);
  }

  .rd-deckvalues__inner {
    display: grid;
    grid-template-columns: 1fr 2fr;
    align-items: center;
    gap: var(--space-10);
    padding: var(--space-11) var(--space-8);
  }

  .rd-deckvalues__title {
    font-size: var(--text-3xl);
  }

  .rd-deckvalues__items {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-8);
  }

  .rd-deckfilters__inner {
    gap: var(--space-5);
    padding-block: var(--space-6);
  }

  .rd-deckfilters__row,
  .rd-deckfilters__chips {
    padding-inline: var(--space-8);
  }

  .rd-deckfilters__row .rd-search {
    flex: 0 1 460px;
  }

  /* The deck grid wants the full --deck-max to fit three columns; one
     contact does not. Holding the sheet to the prose measure keeps a long
     description readable instead of running it to 930px. */
  .rd-deckdetail {
    max-width: var(--prose-max);
    margin-block: var(--space-9) var(--space-12);
    margin-inline: auto;
  }

  .rd-deckdetail__title,
  .public-page .rd-deckdetail .rd-contact-card__person {
    font-size: var(--text-3xl);
  }

  /* On a phone the action is the full width of the sheet — it is the one
     thing the page is for. Given room, it stops shouting and sits left. */
  .rd-deckdetail__cta .rd-button {
    align-self: flex-start;
    width: auto;
  }

  /* Above the phone the chips wrap instead of scrolling: nothing is hidden
     off the edge of a screen this wide. */
  .rd-deckfilters__chips {
    flex-wrap: wrap;
    overflow-x: visible;
  }

  .rd-deckfilters__chips .rd-tag {
    height: var(--control-h-sm);
  }

  .rd-deckgrid {
    gap: var(--space-7);
    padding-block: var(--space-9) var(--space-12);
  }

  .rd-deckgrid__title {
    font-size: var(--text-3xl);
  }

  .rd-deckgrid__items {
    grid-template-columns: repeat(auto-fill, minmax(304px, 1fr));
    gap: var(--space-6);
  }

  /* The compact directory past the phone: as many tiles as fit at a tile's
     measure, so a wide screen reads four or five across. */
  .public-page[data-layout="compact"] .rd-deckgrid__items {
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: var(--space-5);
  }

  /* Above the phone the action is a control at the end of the card, not a
     full-width bar: .rd-button--full is the phone's shape, so undo it here
     rather than branching the markup on a viewport the server cannot see. */
  .rd-deckgrid .rd-contact-card__action {
    width: auto;
    margin-left: auto;
  }

  .rd-deckfoot__inner {
    flex-direction: row;
    align-items: center;
    gap: var(--space-9);
    padding: var(--space-10) var(--space-8);
  }

  .rd-deckfoot__meta {
    margin-left: auto;
  }

  .rd-deckrequest__pair {
    grid-template-columns: 1fr 1fr;
  }

  .rd-deckask__inner {
    padding-inline: var(--space-8);
  }

  .rd-deckask__grid {
    grid-template-columns: 1fr 1fr;
  }

  .rd-deckask__wide {
    grid-column: 1 / -1;
  }

  .rd-deckask__actions {
    flex-direction: row;
    align-items: center;
    gap: var(--space-6);
  }

  .rd-deckrequest__title {
    font-size: var(--text-2xl);
  }
}

/* ─── The wide measure (PRD #718) ────────────────────────────────────────
   Past 1200px every wrapper that sits at --deck-max (1040px) — the band's
   bar, its about line and its hero, the how-it-works and values strips,
   the filter bar, the grid's <main> and the footer — widens together to
   one 1400px measure, so the hero's split, the strips' columns and a
   fourth grid column have room without any one of them going out of
   register with the rest. One value, one block: a wrapper widened here and
   nowhere else is what TestPublicDeckWideMeasure holds. The strips are
   named here ahead of the template that renders them (the same PRD) so the
   measure is theirs the moment they land. The contact detail page's sheet
   keeps --prose-max above: a column of reading is not a grid. */
@media (min-width: 1200px) {
  .public-page > main,
  .rd-deckband__bar,
  .rd-deckband__about,
  .rd-deckband__hero,
  .rd-deckask__inner,
  .rd-deckhow__inner,
  .rd-deckvalues__inner,
  .rd-deckfilters__inner,
  .rd-deckfoot__inner {
    max-width: 1400px;
  }
}

/* ==== A card's own page (/c/<slug>) =======================================
   What a QR code scanned off a piece of paper lands on. The reader is a
   stranger, on a phone, possibly standing up — so it is one narrow column,
   the card at the top of it, and full-width actions under that. No filters,
   no navigation, nothing to read past. */
.rd-cardpage {
  min-height: 100vh;
  padding: var(--space-9) var(--space-6) var(--space-11);
  background: var(--bg-sunken);
}

/* 420px is the card's own comfortable width: wider and a 7:4 card starts to
   look like a banner, which is not what was handed over. */
.rd-cardpage__inner {
  max-width: 420px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-7);
}

.rd-cardpage__actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

/* The card's primary address: a card surface of its own, plain text the
   reader copies into whatever maps app they carry. */
.rd-cardpage__address {
  display: flex;
  gap: var(--space-4);
  align-items: flex-start;
  padding: var(--space-6);
  background: var(--surface-card);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-lg);
  font-style: normal;
  font-size: var(--text-sm);
  color: var(--text-body);
}

.rd-cardpage__address > .rd-icon {
  flex: none;
  margin-top: 2px;
  color: var(--accent);
}

.rd-cardpage__addresslines {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-width: 0;
}

.rd-cardpage__addresslabel {
  font: var(--type-meta);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* The social row wraps rather than scrolls: a card can carry a dozen and a
   phone screen is narrow. */
.rd-cardpage__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  justify-content: center;
}

.rd-cardpage__qr {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-7);
  background: var(--surface-card);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-lg);
}

.rd-cardpage__qrimg {
  width: 180px;
  height: 180px;
  display: block;
}

.rd-cardpage__qrnote {
  max-width: 34ch;
  text-align: center;
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.rd-cardpage__business {
  text-align: center;
  font-size: var(--text-sm);
}

.rd-cardpage__foot {
  padding-top: var(--space-6);
  border-top: var(--border-width) solid var(--border-subtle);
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--text-muted);
}

/* ---- The owner's card in the band --------------------------------------
   A deck can front one of its owner's cards (design 1c → 1d): a visitor meets
   the person before they meet the list. It sits beside the deck's identity on
   a desktop and under it on a phone, where the header is already a stack.

   The band is the accent colour and the card is white, which is the contrast
   this needs — the card should read as a real card laid on the band, not as a
   panel cut out of it. */
.rd-deckband__card {
  flex: none;
  width: 390px;
  max-width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.rd-deckband__cardnote {
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  /* Full strength, not a faded one. The band can carry an uploaded photo
     behind it, and what a TRANSLUCENT glyph composites against is the
     scrimmed picture rather than the accent — which over a bright upload puts
     this line under AA. The guard is TestEveryTextTokenOverABandScrimClearsAA;
     the note is small mono on a dark band, and it does not need to be quiet
     as well as small. */
  color: var(--text-on-dark);
}

/* The hero is a row on a desktop once a card joins it; without one it keeps
   the layout it has always had. */
.rd-deckband__hero:has(.rd-deckband__card) {
  display: flex;
  align-items: center;
  gap: var(--space-9);
  flex-wrap: wrap;
}

.rd-deckband__hero:has(.rd-deckband__card) .rd-deckband__identity {
  flex: 1 1 320px;
  min-width: 0;
}

@media (max-width: 720px) {
  /* The phone is the primary case: a deck is reached by scanning a code, so
     the card goes under the title at full width rather than beside it. */
  .rd-deckband__card { width: 100%; }
}

/* ---- Their own card on the entry's detail page --------------------------
   Under the introduction, not above it (design 2b): this page is the person
   as THIS owner recommends them, and asking for the introduction is what it
   is for. The card is how a visitor keeps them afterwards. */
.rd-deckdetail__card {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin-top: var(--space-7);
}

.rd-deckdetail__cardnote {
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}

/* ─── The page as a preview (PRD #785) ────────────────────────────────────
   The deck editor frames this page for its owner (deckpreview.go), and the
   render marks <body> data-preview. There it is something to look at: its
   links, buttons and fields take no clicks, so the owner cannot wander off
   inside the frame, open a sheet over half the preview, or send themselves
   an introduction request. Scrolling is untouched — a wheel over an inert
   control scrolls the page under it. Nothing about how the page LOOKS
   changes, which is the one thing a preview must not do.

   The preview WINDOW (PRD #800) is the exception, and says so with
   data-preview="live": there the page is the thing being tried, so it takes
   clicks, and assets/deckpreviewwindow.js keeps them inside the preview. */
body[data-preview]:not([data-preview="live"]) a,
body[data-preview]:not([data-preview="live"]) button,
body[data-preview]:not([data-preview="live"]) input,
body[data-preview]:not([data-preview="live"]) textarea,
body[data-preview]:not([data-preview="live"]) select,
body[data-preview]:not([data-preview="live"]) summary,
body[data-preview]:not([data-preview="live"]) label {
  pointer-events: none;
}

/* What the preview window's page says when it holds a click
   (assets/deckpreviewwindow.js): a link to another page of the site, or an
   introduction request, which is never sent from a preview. Pinned to the
   foot of the frame, above the contact sheets' dialog (components.css, 40),
   in the window banner's colours so it reads as the tool speaking rather
   than the deck. */
.rd-previewnote {
  position: fixed;
  left: 50%;
  bottom: var(--space-7);
  z-index: 50;
  max-width: min(28rem, calc(100% - var(--space-9)));
  padding: var(--space-5) var(--space-6);
  transform: translateX(-50%);
  border-radius: var(--radius-md);
  background: var(--surface-inverse);
  color: var(--text-on-dark);
  font: var(--type-body-sm);
  box-shadow: var(--shadow-lg);
}

.rd-previewnote[hidden] { display: none; }
/* assets/themes.css */
/* Refdeck public deck THEMES — the looks a deck owner can choose for the
   page a stranger reaches by scanning their code (internal/store/themes.go).

   WHAT A THEME IS. deck.css composes the public surface once: the band, the
   sticky filter bar, the grid of contact cards, the footer. A theme is that
   same surface with a different SHAPE — its paper, its edges, how it moves,
   what a tap on a card does — and this file is ten of those, one block per
   registry name, keyed on the data-theme attribute public_deck.html puts on
   the body element. Nothing here is a colour choice: the accent and the featured tint
   the owner already picked keep working inside every theme, because every
   rule below reads var(--accent) and var(--highlight) rather than a hue.

   HOW A THEME IS WIRED. Four things, all keyed on the same name:
     1. a DeckThemes entry (themes.go) — label, family, motion, tap, popup;
     2. a [data-theme="name"] block here — the look;
     3. a .rd-themepick__option[data-theme="name"] block at the foot — the
        picker's miniature;
     4. optionally a data-tap kind (below) if the theme wants a gesture the
        existing kinds do not play.
   TestEveryDeckThemeIsStyled holds 1 and 2 together; the rest is composed.

   SCOPED TO THE PAGE. Every rule below is written against
   .public-page[data-theme="…"], never the bare attribute, because the
   attribute also rides on the picker's tiles
   (deck_editor.html) — a bare [data-theme="garage"] { background: ink }
   painted the picker's Garage tile black and hid its own label.

   ORDER AND ORDERING. Ships right after deck.css so every rule here wins a
   tie against the surface it re-dresses, by document order and never by
   !important. Within the file, the shared machinery comes first (the sheet,
   the tap kinds, the entrance), then the ten themes in the registry's order
   — conservative to outrageous — then the picker's miniatures.

   TOKENS ONLY, CONSUMED AND NEVER RESTATED. Every value is a token or a mix
   of tokens, and this layer defines no custom property of its own — the
   rule every app layer keeps (TestNoAppCSSLayerDefinesTokensOfItsOwn), which
   means a DARK theme cannot simply re-point the page and text tokens on
   the body element and let the primitives follow. So the two dark themes
   share one block below that re-inks each primitive the deck page draws —
   the paper, the cards, the chips, the search, the buttons, the footer, the
   sheet — from the ink end of the palette, and each dark theme then adds
   its own shape on top. The featured card's tint is re-mixed there too,
   because public_deck.html mixes the highlight tokens against a white card
   surface on :root and a dark card needs them mixed against the dark one.

   MOTION. Every animation below is cancelled to 0.01ms by the design
   system's prefers-reduced-motion rule (tokens/base.css), so a visitor who
   asked their phone for stillness gets a still page on every theme, wild
   ones included. The tap script (themes.js) waits on animationend, which
   still fires, so a tap under reduced motion opens the card at once.

   The one dimension written by value is 720px, the breakpoint deck.css uses. */

/* ─── The contact sheet ────────────────────────────────────────────────────
   The dialog a card opens in place on a theme with Popup set: the detail
   page's blocks (deck.css owns their type) stacked in a dialog panel, with
   the two ways on at the foot. */
.rd-decksheet__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

.rd-decksheet__actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding-top: var(--space-4);
}

/* ─── Tap kinds ────────────────────────────────────────────────────────────
   themes.js adds .rd-tap and .rd-tap--<kind> to the card for the length of
   one animation, then replays the tap. Keyed on data-tap rather than on the
   theme so two themes can share a gesture, and so a theme picks a gesture
   by name in the registry rather than by a rule here. */
.rd-deckgrid .rd-contact-card.rd-tap {
  animation-duration: var(--dur-slow);
  animation-timing-function: var(--ease-out);
  animation-fill-mode: both;
}

@keyframes rd-tap-press {
  0%   { transform: scale(1); }
  40%  { transform: scale(0.965); }
  100% { transform: scale(1); }
}

@keyframes rd-tap-lift {
  0%   { transform: translateY(0); box-shadow: var(--shadow-xs); }
  50%  { transform: translateY(-8px); box-shadow: var(--shadow-lg); }
  100% { transform: translateY(0); box-shadow: var(--shadow-xs); }
}

@keyframes rd-tap-glow {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 45%, transparent); }
  60%  { box-shadow: 0 0 0 14px color-mix(in srgb, var(--accent) 0%, transparent); }
  100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 0%, transparent); }
}

@keyframes rd-tap-rev {
  0%   { transform: scale(1); }
  25%  { transform: scale(1.04); }
  50%  { transform: scale(0.99); }
  75%  { transform: scale(1.05); }
  100% { transform: scale(1); }
}

@keyframes rd-tap-flip {
  0%   { transform: perspective(900px) rotateY(0deg); }
  100% { transform: perspective(900px) rotateY(90deg); opacity: 0.4; }
}

@keyframes rd-tap-flash {
  0%   { filter: brightness(1); box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 80%, transparent); }
  30%  { filter: brightness(1.6); box-shadow: 0 0 28px 6px color-mix(in srgb, var(--accent) 80%, transparent); }
  100% { filter: brightness(1); box-shadow: 0 0 0 0 color-mix(in srgb, var(--accent) 0%, transparent); }
}

@keyframes rd-tap-spin {
  0%   { transform: rotate(0deg) scale(1); }
  50%  { transform: rotate(180deg) scale(0.9); }
  100% { transform: rotate(360deg) scale(1); }
}

.rd-deckgrid .rd-contact-card.rd-tap--press { animation-name: rd-tap-press; }
.rd-deckgrid .rd-contact-card.rd-tap--lift  { animation-name: rd-tap-lift; }
.rd-deckgrid .rd-contact-card.rd-tap--glow  { animation-name: rd-tap-glow; }
.rd-deckgrid .rd-contact-card.rd-tap--rev   { animation-name: rd-tap-rev; }
.rd-deckgrid .rd-contact-card.rd-tap--flip  { animation-name: rd-tap-flip; }
.rd-deckgrid .rd-contact-card.rd-tap--flash { animation-name: rd-tap-flash; }
.rd-deckgrid .rd-contact-card.rd-tap--spin  { animation-name: rd-tap-spin; }

/* ─── Entrance ─────────────────────────────────────────────────────────────
   Cards arrive rather than appear on every theme but the classic one, each
   a beat after the last so the eye is led down the grid. The stagger is
   eight steps long: past that a long deck would keep a visitor waiting on
   cards they have already scrolled to. */
@keyframes rd-theme-enter {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

.public-page[data-theme]:not([data-theme="classic"]) .rd-deckgrid__items > li {
  animation: rd-theme-enter var(--dur-slow) var(--ease-entrance) both;
}

.public-page[data-theme]:not([data-theme="classic"]) .rd-deckgrid__items > li:nth-child(2) { animation-delay: 60ms; }
.public-page[data-theme]:not([data-theme="classic"]) .rd-deckgrid__items > li:nth-child(3) { animation-delay: 120ms; }
.public-page[data-theme]:not([data-theme="classic"]) .rd-deckgrid__items > li:nth-child(4) { animation-delay: 180ms; }
.public-page[data-theme]:not([data-theme="classic"]) .rd-deckgrid__items > li:nth-child(5) { animation-delay: 240ms; }
.public-page[data-theme]:not([data-theme="classic"]) .rd-deckgrid__items > li:nth-child(6) { animation-delay: 300ms; }
.public-page[data-theme]:not([data-theme="classic"]) .rd-deckgrid__items > li:nth-child(7) { animation-delay: 360ms; }
.public-page[data-theme]:not([data-theme="classic"]) .rd-deckgrid__items > li:nth-child(n+8) { animation-delay: 420ms; }

/* ─── 1. classic — Ledger ──────────────────────────────────────────────────
   The page as deck.css draws it, and the default every deck wears until its
   owner chooses. The one rule it needs is the one that says so: no entrance,
   no motion, nothing the surface did not do before themes existed. */
.public-page[data-theme="classic"] .rd-deckgrid__items > li {
  animation: none;
}

/* ─── 2. boardroom — Boardroom ─────────────────────────────────────────────
   A firm's letterhead: the band goes near-black with the accent as a rule
   beneath it, the title takes the serif display face, and the cards lose
   their radius for a hairline and rise on hover. */
.public-page[data-theme="boardroom"] .rd-deckband {
  background: var(--surface-inverse);
  border-bottom: var(--space-2) solid var(--accent);
}

.public-page[data-theme="boardroom"] .rd-deckband--photo {
  background-color: var(--surface-inverse);
}

.public-page[data-theme="boardroom"] .rd-deckband__title {
  font-family: var(--font-serif);
  font-size: var(--text-3xl);
  letter-spacing: var(--tracking-normal);
}

.public-page[data-theme="boardroom"] .rd-deckband__about {
  background: color-mix(in srgb, var(--text-on-dark) 6%, transparent);
}

.public-page[data-theme="boardroom"] .rd-deckgrid__label {
  font-family: var(--font-serif);
  font-size: var(--text-md);
  font-weight: var(--weight-regular);
  letter-spacing: var(--tracking-normal);
  text-transform: none;
  color: var(--text-heading);
}

.public-page[data-theme="boardroom"] .rd-deckgrid .rd-card {
  border-radius: var(--radius-xs);
  border-color: var(--border-default);
  box-shadow: var(--shadow-none);
}

.public-page[data-theme="boardroom"] .rd-deckgrid .rd-tradeband {
  border-radius: var(--radius-xs) var(--radius-xs) 0 0;
}

.public-page[data-theme="boardroom"] .rd-deckgrid .rd-card--interactive:hover {
  transform: translateY(-3px);
  box-shadow: var(--shadow-md);
  border-color: var(--accent);
}

.public-page[data-theme="boardroom"] .rd-deckgrid .rd-contact-card__person {
  font-family: var(--font-serif);
  font-size: var(--text-lg);
}

.public-page[data-theme="boardroom"] .rd-deckfilters {
  background: color-mix(in srgb, var(--bg-sunken) 94%, transparent);
}

.public-page[data-theme="boardroom"] .rd-deckfilters__chips .rd-tag {
  border-radius: var(--radius-xs);
}

.public-page[data-theme="boardroom"] .rd-deckfoot {
  background: var(--surface-inverse);
  border-top: var(--space-1) solid var(--accent);
}

.public-page[data-theme="boardroom"] .rd-deckfoot__title,
.public-page[data-theme="boardroom"] .rd-deckfoot__body,
.public-page[data-theme="boardroom"] .rd-deckfoot__meta,
.public-page[data-theme="boardroom"] .rd-deckfoot a {
  color: var(--text-on-dark);
}

.public-page[data-theme="boardroom"] .rd-deckhow {
  border-top: var(--space-1) solid var(--accent);
}

/* ─── 3. portfolio — Portfolio ─────────────────────────────────────────────
   Ledger rules: the page on the sunken paper, cards square-cornered and
   ruled top and bottom rather than boxed, meta in the mono face. The first
   theme whose cards open in place. */
.public-page[data-theme="portfolio"] {
  background: var(--bg-sunken);
}

.public-page[data-theme="portfolio"] .rd-deckband {
  border-bottom: var(--border-width) solid color-mix(in srgb, var(--text-on-dark) 30%, transparent);
}

.public-page[data-theme="portfolio"] .rd-deckband__title {
  font-family: var(--font-serif);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  font-size: var(--text-xl);
}

.public-page[data-theme="portfolio"] .rd-deckgrid .rd-card {
  border-radius: 0;
  border-width: var(--border-width) 0;
  border-color: var(--border-strong);
  box-shadow: var(--shadow-none);
  background: var(--surface-card);
}

.public-page[data-theme="portfolio"] .rd-deckgrid .rd-tradeband {
  border-radius: 0;
}

.public-page[data-theme="portfolio"] .rd-deckgrid .rd-card--interactive:hover {
  transform: none;
  border-color: var(--accent);
  box-shadow: inset var(--space-2) 0 0 var(--accent);
}

.public-page[data-theme="portfolio"] .rd-deckgrid .rd-contact-card__company,
.public-page[data-theme="portfolio"] .rd-deckgrid .rd-contact-card__area {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
}

.public-page[data-theme="portfolio"] .rd-deckgrid .rd-contact-card__action {
  border-radius: 0;
}

.public-page[data-theme="portfolio"] .rd-deckfilters__chips .rd-tag,
.public-page[data-theme="portfolio"] .rd-deckfilters__row .rd-search {
  border-radius: 0;
}

.public-page[data-theme="portfolio"] .rd-decksheet .rd-dialog__panel {
  border-radius: 0;
  border-top: var(--space-2) solid var(--accent);
}

/* ─── 4. blueprint — Blueprint ─────────────────────────────────────────────
   Drafting paper: a fine grid drawn from the border token over the page,
   dashed card edges, drafting labels in the mono face, and a work order for
   a sheet. Cards lift off the paper when tapped. */
.public-page[data-theme="blueprint"] {
  background-image:
    linear-gradient(to right, color-mix(in srgb, var(--border-default) 55%, transparent) var(--border-width), transparent var(--border-width)),
    linear-gradient(to bottom, color-mix(in srgb, var(--border-default) 55%, transparent) var(--border-width), transparent var(--border-width));
  background-size: var(--space-8) var(--space-8);
}

.public-page[data-theme="blueprint"] .rd-deckband {
  background-image:
    linear-gradient(to right, color-mix(in srgb, var(--text-on-dark) 10%, transparent) var(--border-width), transparent var(--border-width)),
    linear-gradient(to bottom, color-mix(in srgb, var(--text-on-dark) 10%, transparent) var(--border-width), transparent var(--border-width));
  background-size: var(--space-8) var(--space-8);
}

.public-page[data-theme="blueprint"] .rd-deckband__title {
  font-family: var(--font-mono);
  font-size: var(--text-xl);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
}

.public-page[data-theme="blueprint"] .rd-deckband__intro,
.public-page[data-theme="blueprint"] .rd-deckband__slug {
  font-family: var(--font-mono);
}

.public-page[data-theme="blueprint"] .rd-deckband__photo {
  border: var(--space-1) dashed color-mix(in srgb, var(--text-on-dark) 60%, transparent);
  border-radius: var(--radius-xs);
}

.public-page[data-theme="blueprint"] .rd-deckgrid__label {
  font-family: var(--font-mono);
  color: var(--accent);
}

.public-page[data-theme="blueprint"] .rd-deckgrid .rd-card {
  border: var(--space-1) dashed var(--accent);
  border-radius: var(--radius-xs);
  background: color-mix(in srgb, var(--surface-card) 92%, transparent);
  box-shadow: var(--shadow-none);
}

.public-page[data-theme="blueprint"] .rd-deckgrid .rd-tradeband {
  margin-top: calc(-1 * var(--space-6) - var(--space-1));
  margin-inline: calc(-1 * var(--space-6) - var(--space-1));
  border-radius: 0;
}

.public-page[data-theme="blueprint"] .rd-deckgrid .rd-card--interactive:hover {
  transform: translateY(-4px) rotate(-0.4deg);
  box-shadow: var(--shadow-lg);
  border-style: solid;
}

.public-page[data-theme="blueprint"] .rd-deckgrid .rd-contact-card__company,
.public-page[data-theme="blueprint"] .rd-deckgrid .rd-contact-card__area,
.public-page[data-theme="blueprint"] .rd-deckgrid .rd-badge {
  font-family: var(--font-mono);
}

.public-page[data-theme="blueprint"] .rd-deckfilters {
  border-bottom: var(--space-1) dashed var(--border-strong);
}

.public-page[data-theme="blueprint"] .rd-deckfilters__chips .rd-tag {
  border-radius: var(--radius-xs);
  border-style: dashed;
  font-family: var(--font-mono);
}

.public-page[data-theme="blueprint"] .rd-decksheet .rd-dialog__panel {
  border: var(--space-1) dashed var(--accent);
  border-radius: var(--radius-xs);
}

.public-page[data-theme="blueprint"] .rd-decksheet .rd-dialog__title {
  font-family: var(--font-mono);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  font-size: var(--text-lg);
}

.public-page[data-theme="blueprint"] .rd-decksheet .rd-dialog__title::before {
  content: "WORK ORDER — ";
  color: var(--accent);
}

/* ─── 5. porchlight — Porchlight ───────────────────────────────────────────
   Warm paper and soft corners, a light left on: the band's accent is mixed
   towards bronze, every corner is the largest radius, and a touched card
   glows in the accent. */
.public-page[data-theme="porchlight"] {
  background: var(--bronze-50);
}

.public-page[data-theme="porchlight"] .rd-deckfilters {
  background: color-mix(in srgb, var(--bronze-50) 94%, transparent);
  border-bottom-color: var(--bronze-200);
}

.public-page[data-theme="porchlight"] .rd-deckband {
  background: linear-gradient(160deg, var(--accent), color-mix(in srgb, var(--accent) 70%, var(--bronze-600)));
}

.public-page[data-theme="porchlight"] .rd-deckband__title {
  font-family: var(--font-serif-alt);
  font-size: var(--text-3xl);
}

.public-page[data-theme="porchlight"] .rd-deckband__photo {
  border-radius: var(--radius-pill);
  border-width: var(--space-1);
  box-shadow: 0 0 0 var(--space-3) color-mix(in srgb, var(--text-on-dark) 14%, transparent);
}

.public-page[data-theme="porchlight"] .rd-deckgrid .rd-card {
  border-radius: var(--radius-xl);
  border-color: var(--bronze-200);
  box-shadow: var(--shadow-sm);
}

.public-page[data-theme="porchlight"] .rd-deckgrid .rd-tradeband {
  border-radius: var(--radius-xl) var(--radius-xl) 0 0;
}

.public-page[data-theme="porchlight"] .rd-deckgrid .rd-card--interactive:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md), 0 0 0 var(--space-2) color-mix(in srgb, var(--accent) 22%, transparent);
  border-color: color-mix(in srgb, var(--accent) 50%, var(--bronze-200));
}

.public-page[data-theme="porchlight"] .rd-deckgrid .rd-contact-card__action {
  border-radius: var(--radius-pill);
}

.public-page[data-theme="porchlight"] .rd-deckfilters__row .rd-search {
  border-radius: var(--radius-pill);
}

.public-page[data-theme="porchlight"] .rd-deckfoot {
  background: var(--bronze-100);
}

.public-page[data-theme="porchlight"] .rd-decksheet .rd-dialog__panel {
  border-radius: var(--radius-xl);
  box-shadow: var(--shadow-lg), 0 0 0 var(--space-3) color-mix(in srgb, var(--accent) 22%, transparent);
}

.public-page[data-theme="porchlight"] .rd-decksheet .rd-button {
  border-radius: var(--radius-pill);
}

/* ─── 6. toolbox — Toolbox ─────────────────────────────────────────────────
   Safety stripes across the band in the accent and the featured tint, a
   thick handle bar on every card, chunky corners and chunky type. A tap
   presses the card in like the button on a drill. */
.public-page[data-theme="toolbox"] .rd-deckband__bar {
  background-image: repeating-linear-gradient(
    -45deg,
    var(--highlight) 0,
    var(--highlight) var(--space-5),
    transparent var(--space-5),
    transparent var(--space-9)
  );
  border-bottom: var(--space-2) solid var(--highlight);
}

.public-page[data-theme="toolbox"] .rd-deckband__brand,
.public-page[data-theme="toolbox"] .rd-deckband__slug,
.public-page[data-theme="toolbox"] .rd-deckband__share {
  background: color-mix(in srgb, var(--accent) 88%, var(--ink-1));
  padding-inline: var(--space-4);
  border-radius: var(--radius-sm);
}

.public-page[data-theme="toolbox"] .rd-deckband__title {
  font-family: var(--font-sans);
  font-weight: var(--weight-semibold);
  font-size: var(--text-3xl);
  text-transform: uppercase;
  letter-spacing: var(--tracking-snug);
}

.public-page[data-theme="toolbox"] .rd-deckband__photo {
  border-radius: var(--radius-sm);
  border: var(--space-2) solid var(--highlight);
}

.public-page[data-theme="toolbox"] .rd-deckgrid .rd-card {
  border-radius: var(--radius-md);
  border: var(--space-1) solid var(--border-strong);
  border-top: var(--space-4) solid var(--accent);
  box-shadow: 0 var(--space-2) 0 0 var(--border-strong);
}

.public-page[data-theme="toolbox"] .rd-deckgrid .rd-tradeband {
  margin-top: calc(-1 * var(--space-6));
  border-radius: 0;
}

.public-page[data-theme="toolbox"] .rd-deckgrid .rd-card--interactive:hover {
  transform: translateY(-2px);
  box-shadow: 0 var(--space-3) 0 0 var(--border-strong);
}

.public-page[data-theme="toolbox"] .rd-deckgrid .rd-contact-card__person {
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

.public-page[data-theme="toolbox"] .rd-deckgrid .rd-contact-card__action {
  border-radius: var(--radius-sm);
  border-width: var(--space-1);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

.public-page[data-theme="toolbox"] .rd-deckfilters__chips .rd-tag {
  border-radius: var(--radius-sm);
  border-width: var(--space-1);
  font-weight: var(--weight-semibold);
}

.public-page[data-theme="toolbox"] .rd-deckfilters__chips .rd-tag--selected {
  background: var(--highlight);
  border-color: var(--highlight);
  color: var(--text-on-dark);
}

.public-page[data-theme="toolbox"] .rd-deckfoot {
  border-top: var(--space-2) solid var(--highlight);
}

.public-page[data-theme="toolbox"] .rd-decksheet .rd-dialog__panel {
  border: var(--space-1) solid var(--border-strong);
  border-top: var(--space-5) solid var(--accent);
  border-radius: var(--radius-md);
}

/* ─── Dark surfaces, shared by garage and neon ─────────────────────────────
   Everything the deck page draws on the paper, re-inked for a dark page:
   ink-1 for the paper, ink-2 for a card, the on-dark text colour and mixes
   of it for the three text roles and the three border weights, and the
   accent mixed towards white for a link. Each rule names both themes so
   there is one copy of the inking; neon pushes a few surfaces darker after. */
.public-page[data-theme="garage"],
.public-page[data-theme="neon"] {
  background: var(--ink-1);
  color: color-mix(in srgb, var(--text-on-dark) 86%, var(--ink-2));
}

.public-page[data-theme="garage"] .rd-deckgrid__label,
.public-page[data-theme="neon"] .rd-deckgrid__label,
.public-page[data-theme="garage"] .rd-deckgrid__title,
.public-page[data-theme="neon"] .rd-deckgrid__title,
.public-page[data-theme="garage"] .rd-deckfilters__count,
.public-page[data-theme="neon"] .rd-deckfilters__count,
.public-page[data-theme="garage"] .rd-deckgrid .rd-contact-card__company,
.public-page[data-theme="neon"] .rd-deckgrid .rd-contact-card__company,
.public-page[data-theme="garage"] .rd-deckgrid .rd-contact-card__area,
.public-page[data-theme="neon"] .rd-deckgrid .rd-contact-card__area,
.public-page[data-theme="garage"] .rd-deckfoot__body,
.public-page[data-theme="neon"] .rd-deckfoot__body,
.public-page[data-theme="garage"] .rd-empty-state__body,
.public-page[data-theme="neon"] .rd-empty-state__body,
.public-page[data-theme="garage"] .rd-decksheet .rd-dialog__subtitle,
.public-page[data-theme="neon"] .rd-decksheet .rd-dialog__subtitle,
.public-page[data-theme="garage"] .rd-decksheet .rd-deckdetail__facts dt,
.public-page[data-theme="neon"] .rd-decksheet .rd-deckdetail__facts dt {
  color: color-mix(in srgb, var(--text-on-dark) 66%, var(--ink-2));
}

.public-page[data-theme="garage"] .rd-deckfoot__meta,
.public-page[data-theme="neon"] .rd-deckfoot__meta,
.public-page[data-theme="garage"] .rd-deckgrid .rd-contact-card__area .rd-icon,
.public-page[data-theme="neon"] .rd-deckgrid .rd-contact-card__area .rd-icon {
  color: color-mix(in srgb, var(--text-on-dark) 46%, var(--ink-2));
}

.public-page[data-theme="garage"] .rd-deckgrid .rd-contact-card__person,
.public-page[data-theme="neon"] .rd-deckgrid .rd-contact-card__person,
.public-page[data-theme="garage"] .rd-deckfoot__title,
.public-page[data-theme="neon"] .rd-deckfoot__title,
.public-page[data-theme="garage"] .rd-empty-state__title,
.public-page[data-theme="neon"] .rd-empty-state__title,
.public-page[data-theme="garage"] .rd-decksheet .rd-dialog__title,
.public-page[data-theme="neon"] .rd-decksheet .rd-dialog__title,
.public-page[data-theme="garage"] .rd-decksheet .rd-deckdetail__quote,
.public-page[data-theme="neon"] .rd-decksheet .rd-deckdetail__quote,
.public-page[data-theme="garage"] .rd-decksheet .rd-deckdetail__facts dd,
.public-page[data-theme="neon"] .rd-decksheet .rd-deckdetail__facts dd,
.public-page[data-theme="garage"] .rd-deckfilters__row .rd-search__input,
.public-page[data-theme="neon"] .rd-deckfilters__row .rd-search__input,
.public-page[data-theme="garage"] .rd-decksheet .rd-icon-button,
.public-page[data-theme="neon"] .rd-decksheet .rd-icon-button {
  color: var(--text-on-dark);
}

.public-page[data-theme="garage"] .rd-deckgrid .rd-contact-card__description,
.public-page[data-theme="neon"] .rd-deckgrid .rd-contact-card__description,
.public-page[data-theme="garage"] .rd-decksheet .rd-deckdetail__body,
.public-page[data-theme="neon"] .rd-decksheet .rd-deckdetail__body {
  color: color-mix(in srgb, var(--text-on-dark) 86%, var(--ink-2));
}

.public-page[data-theme="garage"] .rd-deckfoot a,
.public-page[data-theme="neon"] .rd-deckfoot a,
.public-page[data-theme="garage"] .rd-decksheet .rd-deckdetail__facts a,
.public-page[data-theme="neon"] .rd-decksheet .rd-deckdetail__facts a {
  color: color-mix(in srgb, var(--accent) 45%, var(--text-on-dark));
}

/* The surfaces: a card, a chip, the search field, the secondary button and
   the sheet's panel are all ink-2 on ink-1 with a translucent on-dark edge. */
.public-page[data-theme="garage"] .rd-deckgrid .rd-card,
.public-page[data-theme="neon"] .rd-deckgrid .rd-card,
.public-page[data-theme="garage"] .rd-deckfilters__chips .rd-tag,
.public-page[data-theme="neon"] .rd-deckfilters__chips .rd-tag,
.public-page[data-theme="garage"] .rd-deckfilters__row .rd-search,
.public-page[data-theme="neon"] .rd-deckfilters__row .rd-search,
.public-page[data-theme="garage"] .rd-deckgrid .rd-contact-card__action,
.public-page[data-theme="neon"] .rd-deckgrid .rd-contact-card__action,
.public-page[data-theme="garage"] .rd-empty-state .rd-button--secondary,
.public-page[data-theme="neon"] .rd-empty-state .rd-button--secondary,
.public-page[data-theme="garage"] .rd-decksheet .rd-dialog__panel,
.public-page[data-theme="neon"] .rd-decksheet .rd-dialog__panel,
.public-page[data-theme="garage"] .rd-decksheet .rd-button--secondary,
.public-page[data-theme="neon"] .rd-decksheet .rd-button--secondary {
  background: var(--ink-2);
  border-color: color-mix(in srgb, var(--text-on-dark) 22%, transparent);
  color: var(--text-on-dark);
}

.public-page[data-theme="garage"] .rd-deckgrid .rd-badge--neutral,
.public-page[data-theme="neon"] .rd-deckgrid .rd-badge--neutral,
.public-page[data-theme="garage"] .rd-decksheet .rd-badge--neutral,
.public-page[data-theme="neon"] .rd-decksheet .rd-badge--neutral,
.public-page[data-theme="garage"] .rd-deckfilters__chips .rd-tag__count,
.public-page[data-theme="neon"] .rd-deckfilters__chips .rd-tag__count {
  background: color-mix(in srgb, var(--text-on-dark) 12%, transparent);
  color: color-mix(in srgb, var(--text-on-dark) 86%, var(--ink-2));
}

.public-page[data-theme="garage"] .rd-deckfilters__chips .rd-tag--selected,
.public-page[data-theme="neon"] .rd-deckfilters__chips .rd-tag--selected {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--text-on-dark);
}

.public-page[data-theme="garage"] .rd-deckfilters,
.public-page[data-theme="neon"] .rd-deckfilters {
  background: color-mix(in srgb, var(--ink-1) 92%, transparent);
  border-bottom-color: color-mix(in srgb, var(--text-on-dark) 12%, transparent);
}

.public-page[data-theme="garage"] .rd-deckfoot,
.public-page[data-theme="neon"] .rd-deckfoot {
  background: color-mix(in srgb, var(--ink-1) 60%, var(--ink-2));
  border-top-color: color-mix(in srgb, var(--text-on-dark) 12%, transparent);
}

/* The how-it-works strip is the footer's paper, so it goes dark the same
   way; its heading and steps take the on-dark text, its numeral plate a
   translucent on-dark plate. The values band is the accent on every theme
   already and needs nothing. */
.public-page[data-theme="garage"] .rd-deckhow,
.public-page[data-theme="neon"] .rd-deckhow {
  background: color-mix(in srgb, var(--ink-1) 60%, var(--ink-2));
  border-color: color-mix(in srgb, var(--text-on-dark) 12%, transparent);
}

.public-page[data-theme="garage"] .rd-deckhow__title,
.public-page[data-theme="neon"] .rd-deckhow__title,
.public-page[data-theme="garage"] .rd-deckhow__step,
.public-page[data-theme="neon"] .rd-deckhow__step {
  color: var(--text-on-dark);
}

.public-page[data-theme="garage"] .rd-deckhow__n,
.public-page[data-theme="neon"] .rd-deckhow__n {
  background: color-mix(in srgb, var(--text-on-dark) 14%, transparent);
  color: var(--text-on-dark);
}

.public-page[data-theme="garage"] .rd-deckhow__head,
.public-page[data-theme="neon"] .rd-deckhow__head {
  border-color: color-mix(in srgb, var(--text-on-dark) 12%, transparent);
}

/* The featured tint, re-mixed against the dark card because :root mixed it
   against white. */
.public-page[data-theme="garage"] .rd-deckgrid .rd-contact-card--featured,
.public-page[data-theme="neon"] .rd-deckgrid .rd-contact-card--featured,
.public-page[data-theme="garage"] .rd-deckgrid .rd-card--highlight,
.public-page[data-theme="neon"] .rd-deckgrid .rd-card--highlight {
  background: color-mix(in srgb, var(--highlight) 18%, var(--ink-2));
  border-color: color-mix(in srgb, var(--highlight) 55%, transparent);
}

/* ─── 7. garage — Garage ───────────────────────────────────────────────────
   The first DARK theme, on the shared dark inking above. The accent
   survives as a gauge stripe under the band, a stripe down each card and
   the lit switch of a selected chip. */

.public-page[data-theme="garage"] .rd-deckband {
  background: linear-gradient(to bottom, var(--ink-2), var(--ink-1));
  border-bottom: var(--space-3) solid var(--accent);
}

.public-page[data-theme="garage"] .rd-deckband--photo {
  background-color: var(--ink-1);
}

.public-page[data-theme="garage"] .rd-deckband__bar {
  background-image: repeating-linear-gradient(
    to right,
    color-mix(in srgb, var(--text-on-dark) 18%, transparent) 0,
    color-mix(in srgb, var(--text-on-dark) 18%, transparent) var(--border-width),
    transparent var(--border-width),
    transparent var(--space-5)
  );
  background-repeat: repeat-x;
  background-size: 100% var(--space-3);
  background-position: bottom;
}

/* The title stays the on-dark text colour every band title wears — the
   scrim guard (scrimlegibility_test.go) holds every colour in the band to
   AA over a member's photograph, and only a colour built from it can be
   read that way. The accent shows in the gauge stripe under the band. */
.public-page[data-theme="garage"] .rd-deckband__title {
  font-family: var(--font-mono);
  font-size: var(--text-2xl);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

.public-page[data-theme="garage"] .rd-deckband__photo {
  border-radius: var(--radius-sm);
  border: var(--space-1) solid var(--accent);
}

.public-page[data-theme="garage"] .rd-deckgrid .rd-card {
  border-radius: var(--radius-sm);
  border-left: var(--space-3) solid var(--accent);
  box-shadow: var(--shadow-md);
}

.public-page[data-theme="garage"] .rd-deckgrid .rd-tradeband {
  border-radius: 0 var(--radius-sm) 0 0;
}

.public-page[data-theme="garage"] .rd-deckgrid .rd-card--interactive:hover {
  border-left-width: var(--space-4);
  box-shadow: var(--shadow-lg), 0 0 0 var(--border-width) var(--accent);
}

.public-page[data-theme="garage"] .rd-deckfilters__chips .rd-tag {
  border-radius: var(--radius-xs);
  padding-left: var(--space-8);
  position: relative;
}

/* The switch: a dot that lights up when the chip is on. The selected chip
   is the accent itself (shared block above), so its dot is the on-dark
   colour, lit. */
.public-page[data-theme="garage"] .rd-deckfilters__chips .rd-tag::before {
  content: "";
  position: absolute;
  left: var(--space-4);
  top: 50%;
  width: var(--space-4);
  height: var(--space-4);
  margin-top: calc(-1 * var(--space-2));
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--text-on-dark) 36%, transparent);
  transition: var(--transition-control);
}

.public-page[data-theme="garage"] .rd-deckfilters__chips .rd-tag--selected::before {
  background: var(--text-on-dark);
  box-shadow: 0 0 var(--space-4) var(--text-on-dark);
}

.public-page[data-theme="garage"] .rd-deckgrid .rd-contact-card__action {
  border-radius: var(--radius-xs);
}

.public-page[data-theme="garage"] .rd-deckfoot {
  border-top: var(--space-2) solid var(--accent);
}

.public-page[data-theme="garage"] .rd-decksheet .rd-dialog__panel {
  border-radius: var(--radius-sm);
  border-left: var(--space-4) solid var(--accent);
}

.public-page[data-theme="garage"] .rd-decksheet .rd-dialog__title {
  font-family: var(--font-mono);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  font-size: var(--text-lg);
}

/* ─── 8. gearworks — Gearworks ─────────────────────────────────────────────
   Turning gears behind the band — two dashed rings, one each way, drawn from
   the on-dark text colour so they sit on any accent — and riveted corners on
   the cards. A tap flips the card over to its sheet. */
@keyframes rd-gear-turn {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

.public-page[data-theme="gearworks"] .rd-deckband {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

.public-page[data-theme="gearworks"] .rd-deckband::before,
.public-page[data-theme="gearworks"] .rd-deckband::after {
  content: "";
  position: absolute;
  z-index: 0;
  border-radius: var(--radius-pill);
  border: var(--space-4) dashed color-mix(in srgb, var(--text-on-dark) 18%, transparent);
  box-shadow: inset 0 0 0 var(--space-4) color-mix(in srgb, var(--text-on-dark) 8%, transparent);
  animation: rd-gear-turn 24s linear infinite;
  pointer-events: none;
}

.public-page[data-theme="gearworks"] .rd-deckband::before {
  width: 260px;
  height: 260px;
  right: -80px;
  top: -60px;
}

.public-page[data-theme="gearworks"] .rd-deckband::after {
  width: 160px;
  height: 160px;
  right: 120px;
  bottom: -70px;
  animation-direction: reverse;
  animation-duration: 16s;
}

.public-page[data-theme="gearworks"] .rd-deckband > * {
  position: relative;
  z-index: 1;
}

.public-page[data-theme="gearworks"] .rd-deckband__title {
  font-family: var(--font-sans);
  font-weight: var(--weight-semibold);
  font-size: var(--text-3xl);
}

.public-page[data-theme="gearworks"] .rd-deckband__photo {
  border-radius: var(--radius-pill);
  border: var(--space-2) dashed color-mix(in srgb, var(--text-on-dark) 70%, transparent);
}

.public-page[data-theme="gearworks"] .rd-deckgrid .rd-card {
  border-radius: var(--radius-sm);
  border-color: var(--border-strong);
  /* Four rivets: a dot in each corner, drawn as inset rings of the strong
     border colour. */
  background-image:
    radial-gradient(circle at var(--space-4) var(--space-4), var(--border-strong) var(--space-1), transparent calc(var(--space-1) + var(--border-width))),
    radial-gradient(circle at calc(100% - var(--space-4)) var(--space-4), var(--border-strong) var(--space-1), transparent calc(var(--space-1) + var(--border-width))),
    radial-gradient(circle at var(--space-4) calc(100% - var(--space-4)), var(--border-strong) var(--space-1), transparent calc(var(--space-1) + var(--border-width))),
    radial-gradient(circle at calc(100% - var(--space-4)) calc(100% - var(--space-4)), var(--border-strong) var(--space-1), transparent calc(var(--space-1) + var(--border-width)));
  transform-style: preserve-3d;
}

/* The band sits inside the rivets rather than bleeding to the card's edge,
   so the four corners stay visible above it. */
.public-page[data-theme="gearworks"] .rd-deckgrid .rd-tradeband {
  margin-top: calc(-1 * var(--space-6) + var(--space-5));
  margin-inline: 0;
  border-radius: var(--radius-sm);
}

.public-page[data-theme="gearworks"] .rd-deckgrid .rd-card--interactive:hover {
  transform: perspective(900px) rotateY(-6deg);
  box-shadow: var(--shadow-lg);
}

.public-page[data-theme="gearworks"] .rd-deckgrid .rd-contact-card__action {
  border-radius: var(--radius-pill);
}

.public-page[data-theme="gearworks"] .rd-deckfilters__chips .rd-tag {
  border-style: dashed;
}

.public-page[data-theme="gearworks"] .rd-decksheet .rd-dialog__panel {
  border: var(--space-2) dashed var(--border-strong);
  border-radius: var(--radius-sm);
}

/* ─── 9. neon — Neon ───────────────────────────────────────────────────────
   Lights off. The page goes to the darkest ink, every edge is the accent as
   a glow, and the title breathes. The second dark theme, on the shared
   inking above with its surfaces pushed darker and tinted by the accent. */
@keyframes rd-neon-breathe {
  0%, 100% { text-shadow: 0 0 var(--space-3) var(--accent), 0 0 var(--space-7) color-mix(in srgb, var(--accent) 60%, transparent); }
  50%      { text-shadow: 0 0 var(--space-5) var(--accent), 0 0 var(--space-10) var(--accent); }
}

.public-page[data-theme="neon"] .rd-deckgrid .rd-card,
.public-page[data-theme="neon"] .rd-deckfilters__chips .rd-tag,
.public-page[data-theme="neon"] .rd-deckfilters__row .rd-search,
.public-page[data-theme="neon"] .rd-deckgrid .rd-contact-card__action,
.public-page[data-theme="neon"] .rd-decksheet .rd-dialog__panel,
.public-page[data-theme="neon"] .rd-decksheet .rd-button--secondary {
  background: color-mix(in srgb, var(--ink-1) 88%, var(--accent));
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
}

.public-page[data-theme="neon"] .rd-deckband {
  background: var(--ink-1);
  border-bottom: var(--space-1) solid var(--accent);
  box-shadow: 0 0 var(--space-9) color-mix(in srgb, var(--accent) 55%, transparent);
}

.public-page[data-theme="neon"] .rd-deckband--photo {
  background-color: var(--ink-1);
}

.public-page[data-theme="neon"] .rd-deckband__title {
  font-family: var(--font-sans);
  font-weight: var(--weight-semibold);
  font-size: var(--text-3xl);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  animation: rd-neon-breathe 2.8s var(--ease-in-out) infinite;
}

.public-page[data-theme="neon"] .rd-deckband__photo {
  border: var(--space-1) solid var(--accent);
  box-shadow: 0 0 var(--space-6) var(--accent);
}

.public-page[data-theme="neon"] .rd-deckband__share {
  border-color: var(--accent);
  box-shadow: 0 0 var(--space-4) color-mix(in srgb, var(--accent) 60%, transparent);
}

.public-page[data-theme="neon"] .rd-deckgrid__label {
  color: var(--accent);
  filter: brightness(1.8);
}

.public-page[data-theme="neon"] .rd-deckgrid .rd-card {
  border: var(--border-width) solid var(--accent);
  border-radius: var(--radius-md);
  box-shadow: 0 0 var(--space-4) color-mix(in srgb, var(--accent) 50%, transparent), inset 0 0 var(--space-6) color-mix(in srgb, var(--accent) 12%, transparent);
}

.public-page[data-theme="neon"] .rd-deckgrid .rd-tradeband {
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  margin-top: calc(-1 * var(--space-6) + var(--border-width));
  margin-inline: calc(-1 * var(--space-6) + var(--border-width));
}

.public-page[data-theme="neon"] .rd-deckgrid .rd-card--interactive:hover {
  transform: translateY(-3px);
  box-shadow: 0 0 var(--space-8) var(--accent), inset 0 0 var(--space-6) color-mix(in srgb, var(--accent) 20%, transparent);
}

.public-page[data-theme="neon"] .rd-deckgrid .rd-contact-card--featured,
.public-page[data-theme="neon"] .rd-deckgrid .rd-card--highlight {
  background: color-mix(in srgb, var(--highlight) 16%, var(--ink-1));
  border-color: var(--highlight);
  box-shadow: 0 0 var(--space-4) color-mix(in srgb, var(--highlight) 60%, transparent);
}

.public-page[data-theme="neon"] .rd-deckgrid .rd-contact-card__action {
  border-color: var(--accent);
  box-shadow: 0 0 var(--space-3) color-mix(in srgb, var(--accent) 50%, transparent);
}

.public-page[data-theme="neon"] .rd-deckfilters {
  border-bottom-color: var(--accent);
}

.public-page[data-theme="neon"] .rd-deckfilters__chips .rd-tag--selected {
  box-shadow: 0 0 var(--space-4) var(--accent);
}

.public-page[data-theme="neon"] .rd-deckfoot {
  background: var(--ink-1);
  border-top-color: var(--accent);
  box-shadow: 0 calc(-1 * var(--space-1)) var(--space-8) color-mix(in srgb, var(--accent) 35%, transparent);
}

.public-page[data-theme="neon"] .rd-decksheet .rd-dialog__panel {
  border: var(--border-width) solid var(--accent);
  box-shadow: 0 0 var(--space-9) var(--accent);
}

/* ─── 10. carnival — Carnival ──────────────────────────────────────────────
   A striped tent for a band, in the accent, the featured tint and two more
   of the palette's hues; cards hung at alternating tilts that straighten and
   wobble when touched; a spin for a tap. The outrageous end of the list, on
   purpose, and still made of tokens. */
@keyframes rd-carnival-wobble {
  0%, 100% { transform: rotate(0deg); }
  25%      { transform: rotate(1.5deg); }
  75%      { transform: rotate(-1.5deg); }
}

@keyframes rd-carnival-bounce {
  0%   { transform: translateY(-40px) scale(0.9); opacity: 0; }
  60%  { transform: translateY(6px) scale(1.02); opacity: 1; }
  100% { transform: translateY(0) scale(1); }
}

@keyframes rd-carnival-march {
  from { background-position: 0 0; }
  to   { background-position: var(--space-13) 0; }
}

.public-page[data-theme="carnival"] {
  background: color-mix(in srgb, var(--paper) 90%, var(--highlight));
}

.public-page[data-theme="carnival"] .rd-deckfilters {
  background: color-mix(in srgb, var(--paper) 86%, var(--highlight));
}

.public-page[data-theme="carnival"] .rd-deckband {
  background-image: repeating-linear-gradient(
    90deg,
    var(--accent) 0,
    var(--accent) var(--space-10),
    var(--highlight) var(--space-10),
    var(--highlight) var(--space-13),
    var(--blue-600) var(--space-13),
    var(--blue-600) calc(var(--space-13) + var(--space-10)),
    var(--red-600) calc(var(--space-13) + var(--space-10)),
    var(--red-600) calc(var(--space-13) * 2)
  );
  animation: rd-carnival-march 12s linear infinite;
}

.public-page[data-theme="carnival"] .rd-deckband--photo {
  background-color: var(--accent);
}

.public-page[data-theme="carnival"] .rd-deckband__bar,
.public-page[data-theme="carnival"] .rd-deckband__about {
  background: color-mix(in srgb, var(--ink-1) 55%, transparent);
}

.public-page[data-theme="carnival"] .rd-deckband__hero {
  background: color-mix(in srgb, var(--ink-1) 45%, transparent);
}

.public-page[data-theme="carnival"] .rd-deckband__title {
  font-family: var(--font-serif);
  font-size: var(--text-4xl);
  font-style: italic;
  text-shadow: var(--space-1) var(--space-1) 0 var(--ink-1);
}

.public-page[data-theme="carnival"] .rd-deckband__photo {
  border-radius: var(--radius-pill);
  border: var(--space-2) solid var(--text-on-dark);
  transform: rotate(-4deg);
}

.public-page[data-theme="carnival"] .rd-deckgrid__items > li {
  animation-name: rd-carnival-bounce;
  animation-duration: 0.8s;
}

.public-page[data-theme="carnival"] .rd-deckgrid .rd-card {
  border-radius: var(--radius-lg);
  border: var(--space-1) solid var(--ink-1);
  box-shadow: var(--space-2) var(--space-2) 0 0 var(--ink-1);
  transform: rotate(-1.2deg);
}

.public-page[data-theme="carnival"] .rd-deckgrid__items > li:nth-child(even) .rd-card {
  transform: rotate(1.2deg);
}

.public-page[data-theme="carnival"] .rd-deckgrid .rd-tradeband {
  margin-top: calc(-1 * var(--space-6) - var(--space-1));
  margin-inline: calc(-1 * var(--space-6) - var(--space-1));
  border-radius: var(--radius-md) var(--radius-md) 0 0;
}

.public-page[data-theme="carnival"] .rd-deckgrid .rd-card--interactive:hover {
  animation: rd-carnival-wobble 0.5s var(--ease-in-out) infinite;
  box-shadow: var(--space-3) var(--space-3) 0 0 var(--ink-1);
}

.public-page[data-theme="carnival"] .rd-deckgrid .rd-contact-card--featured,
.public-page[data-theme="carnival"] .rd-deckgrid .rd-card--highlight {
  border-color: var(--highlight);
  box-shadow: var(--space-2) var(--space-2) 0 0 var(--highlight);
}

.public-page[data-theme="carnival"] .rd-deckgrid .rd-contact-card__action {
  border-radius: var(--radius-pill);
  border: var(--space-1) solid var(--ink-1);
  font-weight: var(--weight-semibold);
}

.public-page[data-theme="carnival"] .rd-deckfilters__chips .rd-tag {
  border: var(--space-1) solid var(--ink-1);
  font-weight: var(--weight-semibold);
}

.public-page[data-theme="carnival"] .rd-deckfilters__chips .rd-tag--selected {
  background: var(--highlight);
  border-color: var(--ink-1);
  color: var(--text-on-dark);
}

.public-page[data-theme="carnival"] .rd-deckfoot {
  background: var(--ink-1);
  border-top: var(--space-2) solid var(--highlight);
}

.public-page[data-theme="carnival"] .rd-deckfoot__title,
.public-page[data-theme="carnival"] .rd-deckfoot__body,
.public-page[data-theme="carnival"] .rd-deckfoot__meta,
.public-page[data-theme="carnival"] .rd-deckfoot a {
  color: var(--text-on-dark);
}

.public-page[data-theme="carnival"] .rd-decksheet .rd-dialog__panel {
  border: var(--space-1) solid var(--ink-1);
  box-shadow: var(--space-3) var(--space-3) 0 0 var(--highlight), var(--shadow-lg);
  border-radius: var(--radius-lg);
}

.public-page[data-theme="carnival"] .rd-decksheet .rd-dialog__title {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: var(--text-2xl);
}

/* ─── The picker's miniatures ──────────────────────────────────────────────
   One small tile per theme in the branding tab (deck_editor.html), drawn
   from the theme's NAME on the label — a band and two cards, re-shaped the
   way the theme re-shapes the page. The picker's layout lives in app.css;
   what each name looks like is here, next to the theme it stands for. The
   editor's live preview needs nothing here: it is the page itself in a
   frame (PRD #785), dressed by the rules above. */
.rd-themepick__option[data-theme="boardroom"] .rd-themepick__swatchband { background: var(--surface-inverse); border-bottom: var(--space-1) solid var(--accent); }
.rd-themepick__option[data-theme="boardroom"] .rd-themepick__swatchcard { border-radius: 0; }

.rd-themepick__option[data-theme="portfolio"] .rd-themepick__swatch { background: var(--bg-sunken); }
.rd-themepick__option[data-theme="portfolio"] .rd-themepick__swatchcard { border-radius: 0; border-inline: none; }

.rd-themepick__option[data-theme="blueprint"] .rd-themepick__swatch {
  background-image:
    linear-gradient(to right, var(--border-default) var(--border-width), transparent var(--border-width)),
    linear-gradient(to bottom, var(--border-default) var(--border-width), transparent var(--border-width));
  background-size: var(--space-4) var(--space-4);
}
.rd-themepick__option[data-theme="blueprint"] .rd-themepick__swatchcard { border: var(--border-width) dashed var(--accent); border-radius: 0; }

.rd-themepick__option[data-theme="porchlight"] .rd-themepick__swatch { background: var(--bronze-50); }
.rd-themepick__option[data-theme="porchlight"] .rd-themepick__swatchcard { border-radius: var(--radius-md); border-color: var(--bronze-200); }

.rd-themepick__option[data-theme="toolbox"] .rd-themepick__swatchband {
  background-image: repeating-linear-gradient(-45deg, var(--highlight) 0, var(--highlight) var(--space-2), transparent var(--space-2), transparent var(--space-4));
}
.rd-themepick__option[data-theme="toolbox"] .rd-themepick__swatchcard { border-top: var(--space-1) solid var(--accent); border-radius: var(--radius-xs); }

.rd-themepick__option[data-theme="garage"] .rd-themepick__swatch { background: var(--ink-1); }
.rd-themepick__option[data-theme="garage"] .rd-themepick__swatchband { background: var(--ink-2); border-bottom: var(--space-1) solid var(--accent); }
.rd-themepick__option[data-theme="garage"] .rd-themepick__swatchcard { background: var(--ink-2); border-color: var(--ink-2); border-left: var(--space-1) solid var(--accent); border-radius: var(--radius-xs); }

.rd-themepick__option[data-theme="gearworks"] .rd-themepick__swatchband::after {
  content: "";
  position: absolute;
  right: var(--space-2);
  top: calc(-1 * var(--space-3));
  width: var(--space-8);
  height: var(--space-8);
  border-radius: var(--radius-pill);
  border: var(--space-1) dashed color-mix(in srgb, var(--text-on-dark) 60%, transparent);
}
.rd-themepick__option[data-theme="gearworks"] .rd-themepick__swatchcard { border-style: dashed; border-radius: var(--radius-xs); }

.rd-themepick__option[data-theme="neon"] .rd-themepick__swatch { background: var(--ink-1); }
.rd-themepick__option[data-theme="neon"] .rd-themepick__swatchband { background: var(--ink-1); border-bottom: var(--border-width) solid var(--accent); box-shadow: 0 0 var(--space-3) var(--accent); }
.rd-themepick__option[data-theme="neon"] .rd-themepick__swatchcard { background: var(--ink-1); border-color: var(--accent); box-shadow: 0 0 var(--space-2) var(--accent); }

.rd-themepick__option[data-theme="carnival"] .rd-themepick__swatchband {
  background-image: repeating-linear-gradient(90deg, var(--accent) 0, var(--accent) var(--space-3), var(--highlight) var(--space-3), var(--highlight) var(--space-5), var(--blue-600) var(--space-5), var(--blue-600) var(--space-7), var(--red-600) var(--space-7), var(--red-600) var(--space-8));
}
.rd-themepick__option[data-theme="carnival"] .rd-themepick__swatchcard { border: var(--border-width) solid var(--ink-1); box-shadow: var(--space-1) var(--space-1) 0 0 var(--ink-1); transform: rotate(-3deg); }
.rd-themepick__option[data-theme="carnival"] .rd-themepick__swatchcard + .rd-themepick__swatchcard { transform: rotate(3deg); }
/* assets/marketing.css */
/* Refdeck marketing surface — the front page at /.

   Ported from design-system/ui_kits/marketing/: SiteHeader, Hero, HowItWorks,
   InviteBand and SiteFooter.

   WHAT DID NOT PORT, AND WHY. The kit USED to sell a product you could sign up
   for — its header, hero and closing band all said "Create your Refdeck", and
   its footer linked Pricing, About, Privacy, Terms and six other pages.
   Refdeck has no public registration at all (AGENTS.md hard constraint 12:
   accounts come from first-run /setup or an invitation, and never from a third
   path), and none of those pages exist. So the kit's LAYOUT and TYPE are what
   ported; its calls to action are the shipped ones. In particular the closing
   dark band is where the invitation-code form lives here, because "how do I
   get in" is the only thing this page has to close on.

   The kit has since come the other way to meet this: its closing band is
   InviteBand.jsx now, and closes on the invitation code for the same reason
   the shipped page always did. Its old Audiences section — two columns of
   marketing claims with no counterpart here, deliberately not invented — is
   gone from the kit entirely. What the kit grew instead, ExampleBand and
   Scenes, the shipped page already has: the example-deck row and the
   illustration set are both below.

   SCOPE: composition. The buttons, inputs and cards are the primitives in
   components.css; this file places them and defines the five regions the page
   is made of.

   Every value is a token. The kit writes its translucent header as
   rgba(251,250,248,.9) and its band copy as rgba(242,245,244,.8); both are
   color-mix over a token here, because a literal in this file is a palette
   this project has to remember to update twice. The one dimension written by
   value is 720px, the breakpoint — the token set has no breakpoint scale, and
   it is the same switch the kit's own useIsMobile() makes. */

/* Mobile-first: everything below is the phone layout, and the one media query
   at the end widens it. */

/* ─── The sticky header (SiteHeader.jsx) ─────────────────────────────────
   The kit's nav links point at pages that do not exist here, so the bar
   carries the wordmark and the one real destination a stranger has. */
.rd-siteheader {
  position: sticky;
  top: 0;
  z-index: 20;
  background: color-mix(in srgb, var(--paper) 90%, transparent);
  backdrop-filter: var(--blur-veil);
  border-bottom: var(--border-width) solid var(--border-subtle);
}

.rd-siteheader__inner {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  max-width: var(--page-max);
  min-height: var(--topbar-h);
  margin-inline: auto;
  padding-inline: var(--space-6);
}

.rd-wordmark {
  display: inline-flex;
  align-items: center;
  gap: var(--space-4);
  font-family: var(--font-serif);
  font-size: var(--text-lg);
  color: var(--text-heading);
  text-decoration: none;
}

/* Sized by the tag's width/height, like the member topbar's mark, so the
   reserved box and the painted box cannot disagree. */
.rd-wordmark__mark {
  flex: 0 0 auto;
}

.rd-siteheader__actions {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  margin-left: auto;
}

/* ─── The hero (Hero.jsx) ────────────────────────────────────────────────
   One column on a phone, and the kit's 1.05fr/0.95fr split once there is
   room. The right-hand card is the kit's product mock: a picture of what a
   Refdeck is, made of type rather than an illustration. */
.rd-hero {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-9);
  align-items: start;
  max-width: var(--page-max);
  margin-inline: auto;
  padding: var(--space-10) var(--space-6) var(--space-11);
}

.rd-hero__copy {
  display: flex;
  flex-direction: column;
  gap: var(--space-7);
}

.rd-eyebrow {
  font: var(--type-meta);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}

.rd-hero__title {
  font-family: var(--font-serif);
  font-weight: var(--weight-regular);
  font-size: var(--text-3xl);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
  color: var(--text-heading);
}

.rd-hero__lede {
  font: var(--type-body);
  font-size: var(--text-md);
  color: var(--text-body);
  max-width: 46ch;
}

.rd-hero__actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

/* The trust row: three plain statements, each already true of the shipped
   product rather than a claim written for this page. */
.rd-trustrow {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  padding-top: var(--space-5);
  border-top: var(--border-width) solid var(--border-subtle);
}

.rd-trustrow__item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  font: var(--type-body-sm);
  color: var(--text-muted);
}

.rd-trustrow__item .rd-icon {
  color: var(--accent);
}

/* The product mock. Illustrative, not data: aria-hidden, because the copy
   beside it already says what it shows. */
.rd-mock {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  box-shadow: var(--shadow-lg);
}

.rd-mock__deck {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-5);
  border-radius: var(--radius-md);
  background: var(--surface-inverse);
}

.rd-mock__identity {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.rd-mock__name {
  font-family: var(--font-serif);
  font-size: var(--text-md);
  color: var(--text-on-dark);
}

.rd-mock__slug {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--teal-200);
}

.rd-mock__deck .rd-icon {
  margin-left: auto;
  color: var(--teal-200);
}

.rd-mock__tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
}

.rd-mock__row {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  padding: var(--space-5);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
}

.rd-mock__rowtext {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.rd-mock__business {
  font: var(--type-label);
  color: var(--text-heading);
}

.rd-mock__who {
  font: var(--type-body-sm);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/* ─── How it works (HowItWorks.jsx) ──────────────────────────────────────
   Three numbered steps on the sunken tint, between two hairlines. */
.rd-steps {
  background: var(--bg-sunken);
  border-block: var(--border-width) solid var(--border-subtle);
}

.rd-steps__inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-9);
  max-width: var(--page-max);
  margin-inline: auto;
  padding: var(--space-10) var(--space-6);
}

.rd-steps__head {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  max-width: 52ch;
}

.rd-steps__title {
  font-family: var(--font-serif);
  font-weight: var(--weight-regular);
  font-size: var(--text-2xl);
  line-height: var(--leading-snug);
  color: var(--text-heading);
}

.rd-steps__items {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-6);
  margin: 0;
  padding: 0;
  list-style: none;
}

.rd-step {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.rd-step__mark {
  display: flex;
  align-items: center;
  gap: var(--space-5);
}

.rd-step__mark .rd-icon {
  color: var(--accent);
}

.rd-step__n {
  font: var(--type-meta);
  color: var(--text-faint);
}

.rd-step__title {
  font: var(--type-heading);
  color: var(--text-heading);
}

/* A qualifier inside a step's heading — "(if you have one)". Lighter and
   fainter than the title it sits in, so it reads as a caveat rather than as
   part of the step's name. */
.rd-step__aside {
  font: var(--type-body-sm);
  color: var(--text-faint);
}

.rd-step__body {
  font: var(--type-body-sm);
  color: var(--text-body);
}

/* ─── The example deck ───────────────────────────────────────────────────
   Shipped section, kept: the seeded deck is the one live example of what the
   product produces, and the band only renders when it is really published. */
.rd-example {
  border-bottom: var(--border-width) solid var(--border-subtle);
}

.rd-example__inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  align-items: flex-start;
  max-width: var(--page-max);
  margin-inline: auto;
  padding: var(--space-10) var(--space-6);
}

.rd-example__title {
  font: var(--type-subhead);
  color: var(--text-heading);
}

.rd-example__body {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

/* ─── The way in (InviteBand.jsx) ─────────────────────────────────────────
   The kit used to close on "Create your Refdeck". This product cannot be
   signed up for, so the band closes on the only thing that does let someone
   in: the invitation code — which is where the kit's own band landed too. The form sits on a light card so its Input and Button read
   as the same controls they are everywhere else. */
.rd-invite {
  background: var(--surface-inverse);
  color: var(--text-on-dark);
}

.rd-invite__inner {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-8);
  align-items: center;
  max-width: var(--page-max);
  margin-inline: auto;
  padding: var(--space-10) var(--space-6);
}

.rd-invite__copy {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  max-width: 44ch;
}

.rd-invite__title {
  font-family: var(--font-serif);
  font-weight: var(--weight-regular);
  font-size: var(--text-2xl);
  line-height: var(--leading-snug);
  color: var(--text-on-dark);
}

.rd-invite__body {
  font: var(--type-body);
  color: color-mix(in srgb, var(--text-on-dark) 80%, transparent);
}

/* The sign-in door, which on the dark band is a sentence rather than a second
   card: there is only one primary action here, and it is the code. */
.rd-invite__alt {
  font: var(--type-body-sm);
  color: color-mix(in srgb, var(--text-on-dark) 80%, transparent);
}

.rd-invite__alt a {
  color: var(--text-on-dark);
}

.rd-invite__card {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

/* The code itself is read aloud down a phone and typed back, so it is set in
   the mono face at a size that survives that — and upper-cased on the way in,
   while the placeholder stays in the sans face so it does not read as input
   the visitor already typed. */
.rd-invite__code {
  font-family: var(--font-mono);
  font-size: var(--text-md);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

.rd-invite__code::placeholder {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  letter-spacing: var(--tracking-normal);
  text-transform: none;
  color: var(--text-faint);
}

.rd-invite__hint {
  font: var(--type-body-sm);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

/* ─── The footer (SiteFooter.jsx) ────────────────────────────────────────
   The kit's four link columns are nine pages this site does not have, so the
   footer keeps the kit's wordmark-and-hairline treatment and links only what
   really answers. */
.rd-sitefoot {
  border-top: var(--border-width) solid var(--border-subtle);
  background: var(--bg-page);
}

.rd-sitefoot__inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  max-width: var(--page-max);
  margin-inline: auto;
  padding: var(--space-9) var(--space-6);
}

.rd-sitefoot__blurb {
  font: var(--type-body-sm);
  color: var(--text-muted);
  max-width: 30ch;
}

.rd-sitefoot__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
}

.rd-sitefoot__links a {
  font: var(--type-body-sm);
  color: var(--text-link);
}

.rd-sitefoot__meta {
  font: var(--type-meta);
  color: var(--text-faint);
}

/* ─── The illustrations (PRD #392) ───────────────────────────────────────
   Scenes from the supplied editorial set, placed by aspect: the 16:9 scene
   runs as a full-width band between sections, the near-4:3 scenes sit in a
   two-up of columns, and the square concept sits beside the steps head.
   Nothing is object-fit-cropped into a slot it does not match — a crop
   through a face is how a recurring cast stops reading as one story. Every
   image keeps its own aspect: width from the slot, height auto. */
.rd-scene {
  margin: 0;
}

.rd-scene img,
.rd-steps__art {
  display: block;
  width: 100%;
  height: auto;
}

/* The band: edge to edge on a phone, capped at the page measure on a wide
   screen so the 1600px derivative is never upscaled past legibility. */
.rd-scene--band img {
  max-width: var(--page-max);
  margin-inline: auto;
}

/* The 4:3 pair: one column on a phone, the two-up once there is room. */
.rd-scenes {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-6);
  max-width: var(--page-max);
  margin-inline: auto;
  padding: var(--space-10) var(--space-6);
}

.rd-scene--column img {
  border-radius: var(--radius-lg);
}

/* ─── Wide ───────────────────────────────────────────────────────────────
   The kit's useIsMobile() switch, as one media query. */
@media (min-width: 720px) {
  .rd-siteheader__inner,
  .rd-steps__inner,
  .rd-example__inner,
  .rd-invite__inner,
  .rd-sitefoot__inner {
    padding-inline: var(--space-8);
  }

  .rd-hero {
    grid-template-columns: 1.05fr 0.95fr;
    gap: var(--space-12);
    padding: var(--space-13) var(--space-8) var(--space-12);
  }

  .rd-hero__title {
    font-size: var(--text-5xl);
  }

  .rd-hero__actions {
    flex-direction: row;
    align-items: center;
  }

  .rd-trustrow {
    gap: var(--space-7);
  }

  .rd-steps__inner {
    padding-block: var(--space-12);
  }

  .rd-steps__title {
    font-size: var(--text-3xl);
  }

  /* The steps head and the concept square share a row, the same split the
     hero uses; the three step cards run under both. */
  .rd-steps__inner {
    display: grid;
    grid-template-columns: 1.05fr 0.95fr;
    align-items: center;
  }

  .rd-steps__items {
    grid-template-columns: repeat(3, 1fr);
    grid-column: 1 / -1;
  }

  .rd-scenes {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-8);
    padding-inline: var(--space-8);
  }

  .rd-example__inner {
    flex-direction: row;
    align-items: center;
    gap: var(--space-7);
    padding-block: var(--space-11);
  }

  .rd-invite__inner {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-10);
    padding-block: var(--space-12);
  }

  .rd-invite__title {
    font-size: var(--text-3xl);
  }

  .rd-sitefoot__inner {
    flex-direction: row;
    align-items: flex-start;
    gap: var(--space-10);
    padding-block: var(--space-11);
  }

  .rd-sitefoot__links {
    margin-left: auto;
  }
}

/* ─── The connector page, /mcp ───────────────────────────────────────────
   GET /mcp in a browser (mcplanding.go): what the MCP server at that address
   is, the URL to paste, how each kind of client connects, and the tools it
   serves. It is composed from this file's own regions — the header, the hero,
   the numbered-card band and the footer — so the front page and this one are
   one surface; what follows is only what the front page has no equivalent
   for: an address on a card, a command line, a tool's name, a second band of
   cards on the page ground, and the dark close. */

/* The hero, as one centred column: the two-column grid the front page has
   no second child here, and a headline over an address reads better centred
   than ragged against an empty column. */
.rd-hero--single {
  grid-template-columns: 1fr;
  justify-items: center;
  text-align: center;
}

.rd-hero__copy--centered {
  align-items: center;
  max-width: 60ch;
}

.rd-hero__copy--centered .rd-hero__actions,
.rd-hero__copy--centered .rd-trustrow {
  justify-content: center;
}

/* The two words the headline is about, underlined in the accent. */
.rd-hero__mark {
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 0.08em;
  text-underline-offset: 0.12em;
}

/* The status pill, a badge with a live dot. */
.rd-status {
  gap: var(--space-3);
}

.rd-status__dot {
  width: 8px;
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--accent);
}

/* The address in a field beside its Copy button. The URL is one line that
   ellipsises rather than wrapping, because the button is the way it is
   meant to leave the page and the full text is one tap away in the closing
   band; the whole run is selectable for the browser without a clipboard. */
.rd-copyfield {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  width: 100%;
  padding: var(--space-4) var(--space-4) var(--space-4) var(--space-6);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
}

.rd-copyfield__url {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--text-heading);
  text-align: left;
  text-overflow: ellipsis;
  white-space: nowrap;
  user-select: all;
}

.rd-copyfield__button {
  flex: none;
}

/* The address. In the mono face because it is copied character for character
   rather than read, wrapped anywhere because a phone is narrower than a URL,
   and selectable as one run so a long-press takes the whole thing. */
.rd-endpoint {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.rd-endpoint__label {
  font: var(--type-meta);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}

.rd-endpoint__url {
  display: block;
  padding: var(--space-5) var(--space-6);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  font-family: var(--font-mono);
  font-size: var(--text-md);
  color: var(--text-heading);
  overflow-wrap: anywhere;
  user-select: all;
}

.rd-endpoint__meta {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

/* A command line, the same block as the address at the smaller size a
   terminal reads at. pre keeps the line's spaces; the wrap keeps a phone from
   scrolling sideways. */
.rd-code {
  margin: 0;
  padding: var(--space-4) var(--space-5);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-sm);
  background: var(--bg-sunken);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-heading);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  user-select: all;
}

/* A tool or command named inside a sentence. */
.rd-inlinecode {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--text-heading);
}

/* Client by client: the numbered band's cards on the page ground rather than
   the sunken tint, so the two bands alternate down the page. */
.rd-clients {
  border-bottom: var(--border-width) solid var(--border-subtle);
}

.rd-clients__inner {
  display: flex;
  flex-direction: column;
  gap: var(--space-9);
  max-width: var(--page-max);
  margin-inline: auto;
  padding: var(--space-10) var(--space-6);
}

.rd-clients__items {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-6);
}

.rd-client {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

.rd-client__title {
  font: var(--type-heading);
  color: var(--text-heading);
}

.rd-client__body {
  font: var(--type-body-sm);
  color: var(--text-body);
}

/* The tools: the numbered band's grid, with the protocol name where a step
   has its number and icon. */
.rd-tools {
  gap: var(--space-5);
}

.rd-tools__lede {
  font: var(--type-body);
  color: var(--text-body);
}

.rd-tool {
  gap: var(--space-4);
}

.rd-tool__name {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--accent);
  overflow-wrap: anywhere;
}

/* The close: the front page's dark band, closing on the address and the
   sign-in rather than on an invitation code. */
.rd-closing {
  background: var(--surface-inverse);
  color: var(--text-on-dark);
}

.rd-closing__inner {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-8);
  align-items: center;
  max-width: var(--page-max);
  margin-inline: auto;
  padding: var(--space-10) var(--space-6);
}

.rd-closing__copy {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
  max-width: 44ch;
}

.rd-closing__title {
  font-family: var(--font-serif);
  font-weight: var(--weight-regular);
  font-size: var(--text-2xl);
  line-height: var(--leading-snug);
  color: var(--text-on-dark);
}

.rd-closing__body {
  font: var(--type-body);
  color: color-mix(in srgb, var(--text-on-dark) 80%, transparent);
}

.rd-closing__alt {
  font: var(--type-body-sm);
  color: color-mix(in srgb, var(--text-on-dark) 80%, transparent);
}

.rd-closing__alt a {
  color: var(--text-on-dark);
}

@media (min-width: 720px) {
  .rd-clients__inner {
    padding-inline: var(--space-8);
  }

  .rd-clients__items {
    grid-template-columns: repeat(2, 1fr);
  }

  .rd-client--wide {
    grid-column: 1 / -1;
  }

  .rd-copyfield__url {
    font-size: var(--text-md);
  }

  .rd-closing__inner {
    grid-template-columns: 1fr 1fr;
    gap: var(--space-10);
    padding: var(--space-12) var(--space-8);
  }

  .rd-closing__title {
    font-size: var(--text-3xl);
  }
}
/* assets/auth.css */
/* Refdeck auth surface — the two doors into an invitation-only product, plus
   the single-purpose pages that behave like them.

   SCOPE: composition only, like deck.css and marketing.css. The card is
   .rd-card, the fields are .rd-field / .rd-input, the action is .rd-button and
   the failed sign-in is .rd-inline-note; what is defined here is only the
   frame that centres them.

   The inline note USED to be defined here, as .rd-authnote, because the
   primitives had no equivalent. They do now — the design system absorbed it
   as components/feedback/InlineNote.jsx — so it moved to components.css and
   this layer composes it like every other primitive it does not own.

   WHY ITS OWN FRAME. These pages opt out of .app-shell: a sign-in form has no
   navigation to wear, and the app chrome behind it would imply the visitor is
   already inside. The surface is deliberately narrow and quiet — one card, one
   decision, nothing else to click.

   NO SIGNUP LIVES HERE. Accounts are created only by first-run /setup or by
   accepting an invitation (AGENTS.md hard constraint 12), so this layer styles
   no "create an account" affordance and the sign-in template offers none. The
   links it does carry — redeem an invitation code, reach a human, reset a
   password — are what make an invitation-only front door usable, and a test
   holds them there.

   Every value is a token. */

/* ─── The frame ──────────────────────────────────────────────────────────
   One card, centred on the viewport. Grid rather than flex so the card
   centres on both axes and still scrolls when a small phone in landscape
   cannot fit it. */
.auth-page {
  display: grid;
  place-items: center;
  min-height: 100vh;
  padding: var(--space-8) var(--space-6);
  background: var(--bg-page);
}

/* The column the frame centres: wordmark, card, and the note beneath it move
   together, so the card is optically centred rather than the card alone. */
.rd-authpage {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  width: 100%;
  max-width: 420px;
  min-width: 0;
}

.rd-authpage__brand {
  align-self: center;
  font-family: var(--font-serif);
  font-size: var(--text-xl);
  color: var(--text-heading);
  text-decoration: none;
}

.rd-authpage__brand:hover {
  color: var(--text-heading);
  text-decoration: none;
}

/* The note under the card: how an invited person gets in, and how anyone
   reaches a human. Outside the card because it is not part of the decision
   the card asks for. */
.rd-authpage__note {
  font: var(--type-body-sm);
  color: var(--text-muted);
  text-align: center;
  text-wrap: pretty;
}

.rd-authpage__note + .rd-authpage__note {
  margin-top: calc(var(--space-5) * -1);
}

/* ─── The card ───────────────────────────────────────────────────────────
   Composed on .rd-card in the markup; this places its contents. */
.rd-authcard {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  min-width: 0;
}

.rd-authcard__title {
  font-family: var(--font-serif);
  font-size: var(--text-2xl);
  line-height: var(--leading-tight);
  color: var(--text-heading);
}

.rd-authcard__lede {
  margin-top: calc(var(--space-5) * -1);
  font: var(--type-body-sm);
  color: var(--text-muted);
}

.rd-authcard__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  min-width: 0;
}

/* The sign-in action is the only thing on the card worth pressing, so it is
   full width and cannot be missed. */
.rd-authcard__form .rd-button {
  white-space: normal;
  height: auto;
  min-height: var(--control-h-lg);
  padding-block: var(--space-4);
}

/* The password field's label row carries the reset link on its right, which
   is where someone looks the moment the password is the problem. */
.rd-authcard__labelrow {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-5);
}

.rd-authcard__labelaside {
  font: var(--type-body-sm);
  font-size: var(--text-xs);
}

/* [hidden] is a UA rule at (0,1,0) and loses to any class that sets display,
   which the logo preview's frame does. Without this the placeholder and the
   preview would render on top of each other the moment a file is chosen —
   the same trap .public-page hit. */
.auth-page [hidden] {
  display: none !important;
}

/* ─── The wide variant ───────────────────────────────────────────────────
   Most of this surface asks for one decision and fits in 420px. The partner
   profile editor is the exception: it is a real form of seven fields plus an
   upload, filled in once by someone who will never see it again, and cramming
   that into the sign-in column makes it read as longer than it is. */
.rd-authpage--wide {
  max-width: 560px;
}

/* ─── The logo field ─────────────────────────────────────────────────────
   An upload whose result is the whole point: this image becomes the partner's
   face on every public card that carries them. So the field SHOWS it — the
   stored logo on arrival, and the newly chosen file the moment it is picked,
   before any save. Without JS the frame still shows whatever is stored, and
   the upload still works; the preview is the enhancement, not the feature. */
.rd-logofield {
  display: flex;
  align-items: flex-start;
  gap: var(--space-6);
  min-width: 0;
}

.rd-logofield__frame {
  display: grid;
  place-items: center;
  flex: 0 0 auto;
  width: var(--space-13);
  height: var(--space-13);
  overflow: hidden;
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
}

.rd-logofield__img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* The empty frame says what it is rather than sitting blank, so a partner who
   has not uploaded anything sees a field rather than a bug. */
.rd-logofield__empty {
  padding: var(--space-2);
  font: var(--type-meta);
  color: var(--text-faint);
  text-align: center;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

.rd-logofield__controls {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
}

.rd-logofield__input {
  font: var(--type-body-sm);
  color: var(--text-body);
}

/* ─── The invitation card ────────────────────────────────────────────────
   The accept screen and the three ways a link arrives too late. It is the
   auth card with three extra rows: who sent it, what they wrote, and which
   action this visitor is being offered.

   WHY THE SENDER IS SET APART. An emailed link is only as trustworthy as its
   sender, and this page previously named the group and the recipient but not
   the person. The from-line is the credibility of the whole screen, so it sits
   on the sunken tint rather than running on as another paragraph. */
.rd-invitecard__from {
  padding: var(--space-5) var(--space-6);
  border-radius: var(--radius-md);
  background: var(--bg-sunken);
  font: var(--type-body-sm);
  color: var(--text-body);
}

.rd-invitecard__from strong {
  color: var(--text-heading);
  font-weight: var(--weight-semibold);
}

/* Whatever the inviter typed. It is the one human sentence on the page, so it
   is set apart in the accent rather than folded into the chrome. */
.rd-invitecard__note {
  margin: 0;
  padding-left: var(--space-6);
  border-left: var(--space-1) solid var(--accent);
  font: var(--type-body);
  font-style: italic;
  color: var(--text-heading);
}

.rd-invitecard__as {
  font: var(--type-body-sm);
  color: var(--text-muted);
}

/* ─── The step meter ─────────────────────────────────────────────────────
   "Step n of 3 · label" over three bars, filled up to the current step —
   the welcome's sense of place (design-system/ui_kits/onboarding
   WizardSteps.jsx StepMeter). Bars are 3px hairlines, not a progress bar:
   three steps is short enough to count, and the label already says which. */
.rd-stepmeter {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.rd-stepmeter__label {
  font: var(--type-meta);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--text-muted);
}

.rd-stepmeter__bars {
  display: flex;
  gap: var(--space-3);
}

.rd-stepmeter__bar {
  flex: 1;
  height: 3px;
  border-radius: var(--radius-pill);
  background: var(--border-subtle);
}

.rd-stepmeter__bar--done {
  background: var(--accent);
}

/* The dead-end mark: expired, revoked or already spent. Muted rather than
   alarming — none of these is the visitor's fault, and a red warning on a
   page that is only bad news reads as an accusation.

   It lives on the CARD, not on the invitation, because nothing about it was
   ever invitation-specific: the reset flow's four dead-link states and its
   check-your-email confirmation wear the same mark. */
.rd-authcard__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-12);
  height: var(--space-12);
  border-radius: var(--radius-pill);
  background: var(--bg-sunken);
  color: var(--text-muted);
}

/* ─── The done state ─────────────────────────────────────────────────────
   A form that has been sent is not the same page with different words: the
   fields are gone and there is nothing left to decide, so the card becomes a
   centred acknowledgement with one way onward. Used by the contact form's
   confirmation, and shaped for the other single-purpose forms that end the
   same way.

   The mark is the semantic success colour on its own tint — the confirmation
   is the one place on this surface where green is the honest signal. */
.rd-authdone {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-5);
  text-align: center;
}

.rd-authdone__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-12);
  height: var(--space-12);
  border-radius: var(--radius-pill);
  background: var(--status-success-bg);
  color: var(--status-success);
}

.rd-authdone__title {
  font-family: var(--font-serif);
  font-size: var(--text-xl);
  line-height: var(--leading-tight);
  color: var(--text-heading);
}

.rd-authdone__body {
  font: var(--type-body-sm);
  color: var(--text-muted);
  text-wrap: pretty;
}

/* The honeypot's wrapper carries [hidden], which any class that sets display
   would beat. Nothing here does, but the field must never become visible on
   a public form, so the rule is stated rather than assumed. */
.rd-authcard__form [hidden] {
  display: none !important;
}

@media (min-width: 720px) {
  .auth-page {
    padding: var(--space-11) var(--space-8);
  }

  .rd-authcard__title {
    font-size: var(--text-3xl);
  }
}

/* ─── The other door ─────────────────────────────────────────────────────
   "or — Sign in with Google" (PRD #715), under the password form on the
   sign-in card. Composition only, like everything else in this layer: the
   control itself is .rd-button.

   It renders only where the deployment has the Google OAuth client, so these
   rules are frequently unused — which is the point. A deployment with no
   Google keys gets the card it has always had.

   STILL NOT A SIGNUP. The note at the top of this file holds: a Google
   sign-in matches an account somebody already connected and creates nothing,
   so this is a second door into an existing account rather than a third way
   to make one. */
.rd-authcard__alt {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-block: calc(var(--space-5) * -1);
}

/* Two rules with the word between them, rather than one rule with a label
   floated over it: a flex row needs no background to punch a hole in, so the
   divider survives any card surface the theme gives it. */
.rd-authcard__altrule {
  flex: 1 1 0;
  height: 1px;
  background: var(--border-subtle);
}

.rd-authcard__altlabel {
  font: var(--type-body-sm);
  font-size: var(--text-xs);
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
}

/* Matches .rd-authcard__form's treatment of its own action: the two doors
   are the same size, because neither is the lesser one. */
.rd-authcard__altform .rd-button {
  white-space: normal;
  height: auto;
  min-height: var(--control-h-lg);
  padding-block: var(--space-4);
}
