/* ============================================================================
   NFC.cool business console — one style system.

   Every value here comes from the approved canvas mockup (design-v2: Main /
   Tablet / Phone / Sidebar / Setup / Buttons artboards), which lifted them from
   nfc.cool itself. The rule is: a page composes these classes and adds nothing.
   No template carries a <style> block and no element carries a style attribute
   except for data (a brand colour, a progress width) — if a screen needs
   something this file lacks, the class is added HERE, once, for every screen.

   THE WORDS ARE PART OF IT. Two rules hold across every console screen: no em
   dash in anything a person reads, because a period, a colon or a comma says
   the same thing without the typographic throat-clearing; and no dash standing
   in for a value that is missing, which is "Not set", "None", or whatever word
   the field would use. A comment like this one is not copy and keeps its
   dashes. Both rules are pinned in ConsoleCopyTests.
   ========================================================================== */

/* --- Tokens --------------------------------------------------------------- */
:root {
  --bg: #F5F6F8; --card: #FFFFFF; --text: #212529; --text-2: #495057; --text-3: #666D74;
  --border: #E6E8EB; --border-light: #F0F1F3;
  /* Blue as an AREA is the gradient's light stop; blue as TEXT is its dark
     stop, which is the only one that clears 4.5:1 on this page. One control
     never carries both. @nfccool/design calls these --color-link and
     --color-link-text; the console's names stay. */
  --brand: #128CF0; --brand-text: #1A60CE; --brand-text-hover: #1754B5; --brand-2: var(--brand);
  --brand-light: rgba(26,96,206,0.12); --accent: #FFC700; --success: #0A6E4A;
  --success-light: rgba(5,150,105,0.12); --danger: #B3202A; --shadow: 0 1px 2px rgba(33,37,41,0.06);
  /* A field's boundary is not a decorative hairline: --border is 1.23:1 on a
     card, under the 3:1 a control's edge needs. */
  --field-border: #767E86;
  --radius: 10px; --radius-lg: 14px; --radius-sm: 8px;
  /* The console's gutter: the floating sidebar and the page share it. */
  --gutter: 16px;
  /* The vertical rhythm between a page's blocks. A wrapper that groups
     two of them (the members panel) reuses it, or it swallows the gap. */
  --stack-gap: 22px;
  /* Card padding is a token so anything that bleeds to a card's edge (the
     checklist's highlighted row) stays aligned when the padding changes. */
  --card-pad-x: 24px; --card-pad-y: 24px;
  /* The sidebar follows the theme: a white panel in light mode, #1C2128 in dark
     — the same surface as a card, so it can never stay dark on a light page. */
  /* The active item is painted with the AREA blue, the same stop as the filled
     button, so the sidebar's blue and the page's blue are one colour. This is
     the one place a label carries that stop: it measures 2.93:1 on its own
     tinted pill, under the 4.5:1 a label owes, and it is a deliberate call
     taken on 2026-09-07 with both options rendered side by side. The item is
     also marked by its filled pill and, on the rail, by weight, so the colour
     is not the only thing saying "you are here". */
  --nav-fg: var(--text-2); --nav-active-bg: var(--brand-light); --nav-active-fg: var(--brand);
  /* nfc.cool's one button: blue family in light, yellow family in dark. The
     filled hover DARKENS, which lifts its white label from 3.48:1 to 4.70:1,
     so the state a person is pointing at is the compliant one. */
  --btn: var(--brand); --btn-hover: #0F76CA; --btn-tint: var(--brand-light);
  --btn-on: #FFFFFF; --danger-btn: #B3202A; --danger-on: #FFFFFF;
  /* One red wash. Three near-identical rgba() reds were hard-coded across
     the danger button, the failed pill and the error banner. */
  --danger-tint: rgba(179,32,42,0.10);
  /* A switch's knob: light on both the off and the on track, in both themes. */
  --switch-knob: #FFFFFF;
  /* The veil behind a modal. Dark in both themes: it darkens whatever is
     behind it, so it does not flip with the mode any more than a shadow does. */
  --scrim: rgb(0 0 0 / .6);
  /* The label a filled ACCENT surface carries. The accent is the same
     yellow in both themes, so this does not flip with the mode. */
  --on-accent: #212529;
  /* The drawer and the App Clip preview lift off the page, not out of it. */
  --shadow-pop: 0 14px 30px rgba(33,37,41,0.18); --shadow-card: 0 1px 3px rgba(33,37,41,0.18);
  /* The faintest brand wash: the checklist's "do this next" band. */
  --tint-soft: rgba(26,96,206,0.05);
  /* A lead's status wears the apps' colours (refocus spec §9): the colour as
     the pill's label on a 20% tint of the same colour. The five are the same
     in both themes, which is why they are declared once here and the dark
     block overrides none of them. */
  --status-new: #007AFF; --status-new-tint: rgba(0,122,255,0.20);
  --status-contacted: #FF9500; --status-contacted-tint: rgba(255,149,0,0.20);
  --status-qualified: #34C759; --status-qualified-tint: rgba(52,199,89,0.20);
  --status-converted: #AF52DE; --status-converted-tint: rgba(175,82,222,0.20);
  --status-archived: #8E8E93; --status-archived-tint: rgba(142,142,147,0.20);
}
:root[data-theme="dark"] {
  --bg: #161B22; --card: #1C2128; --text: #F0F2F5; --text-2: #C2C8D0; --text-3: #8B949E;
  --border: #30363D; --border-light: #21262D; --brand-light: rgba(255,199,0,0.18);
  --shadow: 0 1px 2px rgba(0,0,0,0.3);
  /* The mode flip: interactive is brand blue by day, brand yellow by night,
     so both roles of blue collapse onto the one yellow here. */
  --brand: #FFC700; --brand-text: #FFC700; --brand-text-hover: #FFD93D;
  --field-border: #8B949E; --danger-on: #161B22;
  --success: #3FB950; --success-light: rgba(63,185,80,0.15); --danger: #FF7B72;
  --nav-active-bg: rgba(255,199,0,0.18); --nav-active-fg: #FFC700;
  --btn: #FFC700; --btn-hover: #FFD93D; --btn-tint: rgba(255,199,0,0.18);
  --btn-on: #161B22; --danger-btn: #FF7B72; --tint-soft: rgba(255,199,0,0.07);
  --danger-tint: rgba(255,123,114,0.14);
  --switch-knob: #FFFFFF;
  --shadow-pop: 0 14px 30px rgba(0,0,0,0.45); --shadow-card: 0 1px 3px rgba(0,0,0,0.30);
}
/* The ONLY prefers-color-scheme block left, and it is not a theme mechanism:
   the head script stamps data-theme before first paint, so this can only
   apply when JavaScript has not run. Components ask [data-theme] and nothing
   else -- two component blocks used to repeat every swap for this query. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) {
    --bg: #161B22; --card: #1C2128; --text: #F0F2F5; --text-2: #C2C8D0; --text-3: #8B949E;
    --border: #30363D; --border-light: #21262D; --brand-light: rgba(255,199,0,0.18);
    --shadow: 0 1px 2px rgba(0,0,0,0.3);
    --brand: #FFC700; --brand-text: #FFC700; --brand-text-hover: #FFD93D;
    --field-border: #8B949E; --danger-on: #161B22;
    --success: #3FB950; --success-light: rgba(63,185,80,0.15); --danger: #FF7B72;
    --nav-active-bg: rgba(255,199,0,0.18); --nav-active-fg: #FFC700;
    --btn: #FFC700; --btn-hover: #FFD93D; --btn-tint: rgba(255,199,0,0.18);
    --btn-on: #161B22; --danger-btn: #FF7B72; --tint-soft: rgba(255,199,0,0.07);
    --danger-tint: rgba(255,123,114,0.14);
    --switch-knob: #FFFFFF;
  --switch-knob: #FFFFFF;
  --shadow-pop: 0 14px 30px rgba(0,0,0,0.45); --shadow-card: 0 1px 3px rgba(0,0,0,0.30);
  }
}

/* --- Base ----------------------------------------------------------------- */
/* Titillium Web loads with font-display: swap (titillium.css). Until it arrives the page renders in a
   local face; without these overrides that face is 6-10% wider and its line box 1.15em instead of
   Titillium's 1.52em, so every line re-wraps and the whole page shifts when the swap happens ("the
   view jumps on load"). size-adjust is Titillium's width over Helvetica Neue's, measured per weight;
   ascent/descent are Titillium's own metrics. Same rule for widths that would otherwise wrap. */
@font-face { font-family: 'Titillium Fallback'; font-weight: 400; src: local('Helvetica Neue'), local('Arial'); size-adjust: 93.8%; ascent-override: 113%; descent-override: 39%; line-gap-override: 0%; }
@font-face { font-family: 'Titillium Fallback'; font-weight: 600; src: local('Helvetica Neue Bold'), local('HelveticaNeue-Bold'), local('Arial Bold'); size-adjust: 89.7%; ascent-override: 113%; descent-override: 39%; line-gap-override: 0%; }
@font-face { font-family: 'Titillium Fallback'; font-weight: 700; src: local('Helvetica Neue Bold'), local('HelveticaNeue-Bold'), local('Arial Bold'); size-adjust: 90.8%; ascent-override: 113%; descent-override: 39%; line-gap-override: 0%; }
* { box-sizing: border-box; }
/* A class rule (.fs-row is a flex box) would otherwise beat the UA's [hidden],
   so scripts can set el.hidden instead of writing a style attribute. */
[hidden] { display: none !important; }
/* An explicit line-height keeps every line box the same height before and after the
   font swap. common.css (loaded first, for the public card pages) pads the body 20px;
   the console owns its gutter, so reset it or every screen sits 36px from the edge. */
body { margin: 0; padding: 0; background: var(--bg); color: var(--text); font-family: 'Titillium Web', 'Titillium Fallback', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; line-height: 1.5; }
/* base.html wraps every page in .section and opens it with two <br>; the console
   owns its own gutter, so those spacers must not push it off the top. */
.section > br { display: none; }
a { color: var(--brand-text); text-decoration: none; }
a:hover { color: var(--brand-text-hover); }
a:focus-visible, .chip:focus-visible, summary:focus-visible { outline: 2px solid var(--btn); outline-offset: 2px; border-radius: 4px; }
h1 { margin: 0; font-size: 26px; font-weight: 700; line-height: 1.2; }
h2, h3 { margin: 0; font-size: 16px; font-weight: 700; }
p { margin: 0 0 10px; }
p:last-child { margin-bottom: 0; }
code { font-family: 'SF Mono', Menlo, monospace; font-size: 13px; }
input, textarea, button, select { font-family: inherit; }
button { cursor: pointer; }

/* --- Accessibility utilities ------------------------------------------------ */
/* Visually hidden but present for assistive tech: the invite form's field
   labels (email/role/card) shouldn't duplicate the visible placeholder/layout. */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: 0; overflow: hidden; clip: rect(0,0,0,0); clip-path: inset(50%); white-space: nowrap; border: 0; }

/* --- Text roles ----------------------------------------------------------- */
.section-title { margin: 0; font-size: 16px; font-weight: 700; color: var(--text); }
.muted { font-size: 14px; color: var(--text-2); }
.hint { font-size: 12px; color: var(--text-3); }
.label { font-size: 13px; font-weight: 600; color: var(--text-2); }

/* An explanation that only some people need, folded behind a mark rather than
   spent as three permanent lines under a control. Use it for a rule someone
   reads once (accepted file types) or a consequence they check when deciding
   (what a toggle does to every card) -- never for something needed to answer
   the question in front of them, which stays a visible .hint.

   Markup, so every use is the same shape:

     <button type="button" class="info-tip" aria-label="About X"
             aria-describedby="tip-x">
       <svg viewBox="0 0 24 24" ...>...</svg>
       <span class="info-tip-bubble" id="tip-x" role="tooltip">The text.</span>
     </button>

   type="button" because the console's settings forms autosave and a bare
   <button> in a form submits it. aria-label names the mark, so the bubble's
   own words are not read as the button's name; aria-describedby is what
   speaks them. It opens on hover AND on keyboard focus, so it is not
   pointer-only. The bubble escapes .card, which sets no overflow. */
.info-tip { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 18px; height: 18px; padding: 0; margin-left: 6px; border: 0; border-radius: 50%; background: transparent; color: var(--text-3); vertical-align: -3px; cursor: help; }
.info-tip:hover, .info-tip:focus-visible { color: var(--brand-text); }
.info-tip svg { width: 16px; height: 16px; display: block; }
/* Anchored to the mark's left edge, opening rightwards, NOT centred on it:
   centred, a 260px bubble on a mark near the start of a line reaches back
   past the content column and disappears under the floating sidebar, which
   paints over it. Every mark in the console sits in the left half of a wide
   card, so rightwards always has room. */
.info-tip-bubble { position: absolute; bottom: calc(100% + 8px); left: -8px; z-index: 60; width: max-content; max-width: 260px; padding: 10px 12px; border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--card); box-shadow: var(--shadow-pop); color: var(--text-2); font-size: 12px; font-weight: 400; line-height: 1.45; text-align: left; white-space: normal; opacity: 0; visibility: hidden; transition: opacity .15s ease, visibility .15s ease; }
/* :focus as well as :focus-visible. The trigger is a real button, so a tap
   focuses it, but :focus-visible is a keyboard heuristic and does not reliably
   match a touch tap: without this the mark was unopenable on a phone, which has
   no hover either. */
.info-tip:hover .info-tip-bubble, .info-tip:focus .info-tip-bubble, .info-tip:focus-visible .info-tip-bubble { opacity: 1; visibility: visible; }
@media (prefers-reduced-motion: reduce) { .info-tip-bubble { transition: none; } }
.eyebrow, .stat-label { font-size: 12px; font-weight: 700; color: var(--text-2); text-transform: uppercase; letter-spacing: .05em; }
/* A value's unit/word, spelled out only where there's room for it: hidden on
   desktop's tight table columns, shown on the phone list layout below. */
.unit { display: none; }

/* --- Shell: the console, its floating sidebar and the page column ---------- */
.console { display: flex; align-items: flex-start; gap: var(--gutter); padding: var(--gutter); min-height: 100vh; }
/* The sidebar is a card like every other surface: same border, radius, shadow and
   theme. It floats at the gutter and sticks, so the menu, the account line and the
   theme toggle stay in view while the page scrolls. */
.sidebar {
  position: sticky; top: var(--gutter); z-index: 50;
  width: 236px; flex-shrink: 0; align-self: flex-start;
  height: calc(100vh - var(--gutter) * 2);
  display: flex; flex-direction: column; padding: 20px 14px;
  background: var(--card); color: var(--nav-fg);
  border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow);
}
.sidebar .logo-link { display: block; margin: 0 8px 22px; }
.sidebar .logo { height: 22px; width: auto; display: block; }
/* One logo per theme, switched by the same mechanism as the tokens. The two
   files are ONE design in two colours, not two drawings of the same words:
   they carry the same aspect ratio, so the wordmark keeps its size when the
   theme flips. They did not always. The dark slot once held outlined 650x147
   artwork while the light slot held solid 2892x713, which at a shared height
   rendered 7px narrower and read lighter as well as wider. Pinned in
   ConsoleBrandMarkTests. */
.sidebar .logo-dark { display: none; }
:root[data-theme="dark"] .sidebar .logo-light { display: none; }
:root[data-theme="dark"] .sidebar .logo-dark { display: block; }
.sidebar-menu { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow-y: auto; }
.org-switch { display: flex; align-items: center; gap: 10px; padding: 10px 12px; margin-bottom: 16px; background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); color: var(--text); }
.org-switch .mark { width: 28px; height: 28px; flex-shrink: 0; border-radius: var(--radius-sm); display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 12px; color: #fff; }
.org-switch > span { min-width: 0; line-height: 1.15; font-size: 14px; }
.org-switch strong { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.org-switch small { display: block; font-size: 12px; color: var(--text-3); font-weight: 400; }
.nav { display: flex; flex-direction: column; gap: 2px; font-size: 14px; font-weight: 600; }
.nav a { display: flex; align-items: center; gap: 12px; padding: 10px 12px; border-radius: var(--radius-sm); color: var(--nav-fg); }
.nav a:hover { background: var(--btn-tint); color: var(--nav-active-fg); }
.nav a.is-active { background: var(--nav-active-bg); color: var(--nav-active-fg); }
.nav a:focus-visible { outline: 2px solid var(--btn); outline-offset: -2px; }
.nav svg { width: 18px; height: 18px; flex-shrink: 0; }
/* The account block: who is signed in, then the way out. The avatar is always
   drawn -- it anchors the row, and it makes a truncated address read as a
   deliberate second line rather than a broken one. */
.sidebar .me { margin-top: auto; display: flex; align-items: center; gap: 10px; padding: 12px 12px 0; border-top: 1px solid var(--border-light); font-size: 13px; }
/* The identity is a link to the Account section now, so avatar and name sit
   inside it and it carries the row's flex sizing. Sign out stays a sibling: one
   target for "my account", a separate one for "leave", never a single block
   where a mis-click does the other thing. */
.sidebar .me .me-link { flex: 1; min-width: 0; display: flex; align-items: center; gap: 10px; padding: 4px 6px; margin: -4px -6px; border-radius: var(--radius-sm); color: inherit; }
.sidebar .me .me-link:hover { background: var(--btn-tint); }
.sidebar .me .me-link:focus-visible { outline: 2px solid var(--btn); outline-offset: 2px; }
.sidebar .me .me-who { flex: 1; min-width: 0; display: flex; flex-direction: column; line-height: 1.25; }
.sidebar .me .me-name { font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sidebar .me .me-email { font-size: 11.5px; color: var(--text-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Signing out is not the console's headline action, so it is not painted in
   the brand colour it used to borrow. Quiet until you go for it. */
/* Icon only in the rail: at 218px wide, the word "Sign out" ate the space the
   name and address needed and truncated both to an ellipsis. The label comes
   back in .console-plain below, where the footer is a wide horizontal bar. */
.sidebar .me .me-signout { display: inline-flex; align-items: center; justify-content: center; gap: 6px; flex-shrink: 0; width: 30px; height: 30px; padding: 0; border-radius: var(--radius-sm); color: var(--text-3); font-weight: 600; white-space: nowrap; }
.sidebar .me .me-signout > span { display: none; }
.sidebar .me .me-signout svg { width: 15px; height: 15px; flex-shrink: 0; }
.sidebar .me .me-signout:hover { background: var(--btn-tint); color: var(--text); }
.sidebar .me .me-signout:focus-visible { outline: 2px solid var(--btn); outline-offset: 2px; }
.me-avatar { display: flex; width: 32px; height: 32px; border-radius: 999px; background: var(--brand-light); color: var(--brand-text); font-weight: 700; font-size: 12px; align-items: center; justify-content: center; flex-shrink: 0; }
/* The burger stays a quiet 36px bordered square, after nfc.cool's .sk-nav-toggle.
   The theme switch is a labelled button (.btn.small.tertiary in the markup):
   "Dark" / "Light" says what the next press does, which an icon never did. */
.nav-toggle { width: 36px; height: 36px; padding: 0; background: transparent; border: 1px solid var(--border); border-radius: var(--radius-sm); color: var(--text-2); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
.nav-toggle:hover { background: var(--btn-tint); border-color: var(--btn-hover); color: var(--text); }
/* Icon-only controls still need a visible focus ring: the same 2px/offset-3
   pattern every other control (.btn, .stepper) uses. */
.nav-toggle:focus-visible { outline: 2px solid var(--btn); outline-offset: 3px; }
/* Both themes named, the current one lit -- a segmented control, the same
   shape as .interval-toggle. Which half looks on is decided from [data-theme],
   stamped on <html> before first paint, so it is right in the first frame and
   needs no script (syncThemeToggle only carries the fact to aria-pressed). */
.sidebar .theme-switch { margin: 10px 12px 12px; display: grid; grid-template-columns: 1fr 1fr; gap: 3px; padding: 3px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); }
.theme-switch button { display: inline-flex; align-items: center; justify-content: center; gap: 6px; padding: 6px 8px; border: 0; border-radius: var(--radius-sm); background: transparent; color: var(--text-3); font: inherit; font-size: 12px; font-weight: 600; cursor: pointer; }
.theme-switch button svg { width: 14px; height: 14px; flex-shrink: 0; }
.theme-switch button:hover { color: var(--text); }
.theme-switch button:focus-visible { outline: 2px solid var(--btn); outline-offset: 2px; }
:root[data-theme="light"] .theme-switch [data-theme-set="light"],
:root[data-theme="dark"] .theme-switch [data-theme-set="dark"] { background: var(--card); color: var(--text); box-shadow: var(--shadow); }
/* The bar's shape: one icon, the contrast mark, matching nfc.cool's shipped
   .sk-theme-toggle down to the 36px square and the 8px corner. A horizontal
   strip has room for one control, not for a pair of words -- so the sidebar
   shows the switch and every bar layout below swaps to this. Both are in the
   markup; only CSS chooses, so neither can flash. */
.theme-toggle-icon { display: none; align-items: center; justify-content: center; width: 36px; height: 36px; padding: 0; border: 1px solid var(--border); border-radius: var(--radius-sm); background: transparent; color: var(--text-2); cursor: pointer; }
.theme-toggle-icon svg { width: 16px; height: 16px; }
.theme-toggle-icon:hover { background: var(--btn-tint); border-color: var(--btn-hover); color: var(--text); }
.theme-toggle-icon:focus-visible { outline: 2px solid var(--btn); outline-offset: 3px; }
.nav-toggle { display: none; }
.nav-toggle svg { width: 20px; height: 20px; }
.nav-toggle-close { display: none; }
.sidebar.is-open .nav-toggle-open { display: none; }
.sidebar.is-open .nav-toggle-close { display: inline; }
/* The page column. Its padding is the mockup's; the gap is the vertical rhythm
   between a page's blocks, so nothing needs a margin of its own. */
.main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--stack-gap); padding: 12px 24px 24px 20px; }
/* HTMX swaps this wrapper whole, which is the only reason it exists. As one
   child of .main it swallowed the column's gap, so the invite card sat flush
   on the table below it -- zero pixels, at every width. Same column, same
   rhythm, so a swapped panel is spaced like anything else on the page. */
#members-panel { display: flex; flex-direction: column; gap: var(--stack-gap); }

/* The setup wizard and the signed-out pages keep their own shell: the sidebar
   collapses to the mockup's top bar at every width and the page is a column. */
.console-plain { flex-direction: column; align-items: stretch; }
.console-plain .sidebar { position: sticky; top: var(--gutter); width: auto; align-self: stretch; height: auto; flex-direction: row; align-items: center; gap: 10px; padding: 10px 16px; }
.console-plain .sidebar .logo-link { margin: 0; }
/* A sticky top bar leaves a strip of page above it; paint that strip so the
   content scrolling underneath never shows through the gutter. */
.console-plain .sidebar::before, .console.console-plain .sidebar::before { content: ""; position: absolute; left: calc(var(--gutter) * -1); right: calc(var(--gutter) * -1); bottom: calc(100% + 1px); height: var(--gutter); background: var(--bg); }
/* Names the flow in the bar, after the mockup's "Business setup" divider. */
.bar-label { display: flex; align-items: center; flex: 1; gap: 16px; font-size: 15px; font-weight: 600; color: var(--text-2); }
.bar-label::before { content: ""; width: 1px; height: 24px; background: var(--border); }
.console-plain .sidebar .logo { height: 20px; }
.console-plain .sidebar .theme-switch { display: none; }
.console-plain .sidebar .theme-toggle-icon { display: inline-flex; order: 3; align-self: center; }
.console-plain .sidebar-menu { display: contents; }
.console-plain .org-switch, .console-plain .nav, .console-plain .nav-toggle { display: none; }
.console-plain .sidebar .me { margin: 0 0 0 auto; padding: 0; border: 0; order: 2; }
.console-plain .sidebar .me > .me-who { max-width: 220px; }
/* The wizard's footer is a wide bar, so the word fits and reads better here. */
.console-plain .sidebar .me .me-signout { width: auto; height: auto; padding: 5px 8px; }
.console-plain .sidebar .me .me-signout > span { display: inline; }
.console-plain .main { align-self: center; width: 100%; max-width: 780px; padding: 12px 8px 24px; }

/* --- Page header: identical on every screen ------------------------------- */
/* .page-head-text carries the fixed min-height so the actions sit at the same y
   whether or not a page's subtitle wraps — this is what stopped the buttons
   "jumping" between Overview, Members, Cards and Billing. */
.page-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; min-height: 52px; }
.page-head-text { display: flex; flex-direction: column; justify-content: center; gap: 2px; min-height: 52px; }
.page-head .sub { font-size: 14px; color: var(--text-2); }
/* A section's own nav, under the page head. Settings is a section of pages, so
   its members sit on one line here instead of each taking a sidebar row -- the
   sidebar is for what a team works in, this is for what it sets. Underline
   rather than pill: these are siblings within a page you are already on, not
   separate destinations competing with the sidebar's own active row. */
/* Already a scrolling row: overflow-x: auto here, white-space: nowrap on the
   items. Four sections fit a 390px screen, so it only scrolls once there are
   more, and these three lines are what make that scroll behave when it does --
   no scrollbar gutter under a tab row, momentum on touch, and an item that
   never lands flush against the edge it scrolled from. */
.section-nav {
  display: flex; gap: 4px; margin-top: calc(var(--stack-gap) * -1 + 6px);
  border-bottom: 1px solid var(--border);
  overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
  scroll-padding-inline: var(--gutter);
}
.section-nav::-webkit-scrollbar { display: none; }
.section-nav a { padding: 9px 12px; margin-bottom: -1px; border-bottom: 2px solid transparent; color: var(--text-3); font-size: 14px; font-weight: 600; white-space: nowrap; }
.section-nav a:hover { color: var(--text); }
.section-nav a.is-active { color: var(--text); border-bottom-color: var(--btn); }
.section-nav a:focus-visible { outline: 2px solid var(--btn); outline-offset: -2px; border-radius: var(--radius-sm); }
.actions { display: flex; align-items: center; gap: 10px; }
/* Pushes what follows it to the far end of a flex row. */
.spacer { margin-left: auto; }

/* --- Buttons -------------------------------------------------------------- */
/* nfc.cool has one button (.landing-cta-button): outline, 1.5px border, radius 10,
   Titillium 600, letter-spacing .04em. Secondary IS that button; primary is its
   filled twin; tertiary is the site's grey utility button (.sk-lang-btn). */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; white-space: nowrap; height: 42px; padding: 0 24px; border: 1.5px solid var(--btn); border-radius: var(--radius); background: var(--btn); color: var(--btn-on); font: inherit; font-size: 15px; font-weight: 600; letter-spacing: 0.04em; cursor: pointer; text-decoration: none; transition: background .2s ease, border-color .2s ease, color .2s ease, transform .2s ease; }
.btn:hover { background: var(--btn-hover); border-color: var(--btn-hover); color: var(--btn-on); transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn:focus-visible { outline: 2px solid var(--btn); outline-offset: 3px; }
/* Ink, not blue: the border is already the blue, and a blue label beside a
   blue border puts two blues on one control. The light stop is only 3.48:1
   as text in any case. */
.btn.secondary { background: transparent; color: var(--text); }
.btn.secondary:hover { background: var(--btn-tint); border-color: var(--btn-hover); color: var(--text); }
.btn.tertiary { background: transparent; border: 1px solid var(--border); color: var(--text-2); padding: 0 18px; }
.btn.tertiary:hover { background: var(--btn-tint); border-color: var(--btn-hover); color: var(--text); }
.btn.danger { background: transparent; border-color: var(--danger-btn); color: var(--danger-btn); }
.btn.danger:hover { background: var(--danger-tint); border-color: var(--danger-btn); color: var(--danger-btn); }
/* Quiet: a destructive action that shares a row with the control it undoes.
   "Remove" beside "Replace" at full .danger weight reads as its peer -- two
   boxes competing, the red one loudest -- when it is the one you rarely want.
   It keeps the word and drops the box until a pointer or the keyboard arrives,
   and then it is unmistakably the red one. Text stays --text-2, not --text-3:
   a control's label owes 4.5:1 even at rest. */
.btn.danger.quiet { border-color: transparent; color: var(--text-2); }
.btn.danger.quiet:hover, .btn.danger.quiet:focus-visible { border-color: var(--danger-btn); color: var(--danger-btn); background: var(--danger-tint); }
.btn.large { height: 48px; padding: 0 30px; font-size: 16px; }
.btn.small { height: 32px; padding: 0 14px; font-size: 13px; }
.btn.tertiary.small { padding: 0 12px; }
.btn.block { width: 100%; }
.btn.square, .stepper { width: 36px; height: 36px; padding: 0; border: 1px solid var(--border); border-radius: var(--radius-sm); background: transparent; color: var(--text-2); font-size: 18px; font-weight: 600; letter-spacing: 0; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
.btn.square:hover, .stepper:hover { background: var(--btn-tint); border-color: var(--btn-hover); color: var(--text); transform: none; }
.stepper:focus-visible { outline: 2px solid var(--btn); outline-offset: 3px; }
.btn:disabled, .btn[aria-disabled="true"] { opacity: .6; cursor: not-allowed; transform: none; }
.btn svg { width: 1.15em; height: 1.15em; flex-shrink: 0; }
/* Every file chooser in the console is a <label class="btn"> over a hidden
   input, so it was inheriting the FIELD label's `margin: 14px 0 4px` and
   sitting 14px below the real button beside it. A label that is a button is
   not a field's caption. */
label.btn { margin: 0; }

/* --- Surfaces ------------------------------------------------------------- */
.card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow); padding: var(--card-pad-y) var(--card-pad-x); }
/* Every card opens with the same title row: a title, optionally something on the
   right (a count, a status pill, an action). */
.card-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
.card-head:only-child, .card-head:last-child { margin-bottom: 0; }
.card-head .stack { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.card > .card-head + .muted, .card > .card-head + .hint { margin-top: -4px; }
.stat-card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow); padding: 16px 18px; display: flex; flex-direction: column; gap: 6px; }
.stat-value { font-size: 28px; font-weight: 700; line-height: 1; }
/* A stat whose value is a word, not a number: it needs the smaller size to fit. */
.stat-value.text { font-size: 20px; line-height: 1.3; }
.stat-value small { font-size: 14px; color: var(--text-2); font-weight: 400; margin-left: 6px; }
.bar { height: 6px; border-radius: 999px; background: var(--border-light); overflow: hidden; }
.bar > i { display: block; height: 6px; background: var(--brand); }
.panel { padding: 16px 18px; background: var(--bg); border-radius: var(--radius); }
.empty { padding: 26px 20px; text-align: center; color: var(--text-2); font-size: 14px; }
/* An empty state that carries its action: the sentence, then the button. */
.empty p { margin: 0 0 14px; }
/* A card that IS the empty state carries the padding once, not twice. */
.card.empty { padding: 26px 20px; }

/* --- Grids ---------------------------------------------------------------- */
.stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; }
.grid-2 { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; align-items: start; }
/* The one spot a 2-column grid needs an uneven split (Settings' Brand card next
   to its narrower App Clip preview): a modifier on the same class, not a second one. */
.grid-2.wide { grid-template-columns: 1.2fr 1fr; }
/* Settings: two even columns of stacked cards, only at the desktop breakpoint --
   from the tablet rail down the page is one column, stacks one after another.
   A modifier on .grid-2.wide, scoped to Settings; .stats/.stat-row (Overview)
   must not change. */
.grid-2.wide.settings-columns { grid-template-columns: 1fr; }
@media (min-width: 1200px) {
  .grid-2.wide.settings-columns { grid-template-columns: 1.2fr 1fr; }
}
/* A column of grouped blocks (a form's cards, a status panel). */
.stack { display: flex; flex-direction: column; gap: 16px; }

/* --- Tables and the capsule / toolbar row --------------------------------- */
/* No `overflow: hidden` here, deliberately. It existed to stop the last row's
   hover background squaring off the rounded bottom corners, and it also
   clipped anything a row wanted to open -- a row's ... menu was cut off at the
   table's edge. The corners are kept by rounding the last row itself, which
   costs one rule and lets a menu escape. */
.table { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow); display: flex; flex-direction: column; }
/* THE capsule row. One padding, one gap, one alignment — wherever filters, a
   search box or a table's title bar appear, on every page. */
.toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 14px 20px; border-bottom: 1px solid var(--border); }
.toolbar:last-child { border-bottom: 0; }
.toolbar > a.spacer, .toolbar > .spacer > a { font-size: 14px; font-weight: 600; }
.table .head, .table .row { display: grid; grid-template-columns: 2.2fr 1fr 1.2fr 1fr 1.4fr; align-items: center; padding: 10px 20px; font-size: 14px; border-bottom: 1px solid var(--border-light); }
.table .head { font-size: 12px; font-weight: 700; color: var(--text-2); text-transform: uppercase; letter-spacing: .05em; border-bottom-color: var(--border); }
.table .row:last-child { border-bottom: 0; border-radius: 0 0 var(--radius-lg) var(--radius-lg); }
.table .row:hover { background: var(--bg); }
.table .cell-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 8px; }

/* --- A row's ... menu ------------------------------------------------------
   THE RULE FOR A TABLE ROW'S ACTIONS: one trigger, not a row of buttons.

   A row used to end in every action it had, spelled out: a role select, "Add
   card" and "Remove" on Members; "View", "Edit" and "Delete" on Cards. Three
   or four controls per row, repeated down the page, and the eye had to re-read
   the same cluster on every line to find the one row it wanted. Worse, the
   destructive action sat permanently one pixel from the safe one.

   So: the actions of a row live behind a single ... trigger, and the row shows
   nothing but that. Use it for ANY table row that has more than one action.

     <details class="row-menu">
       <summary aria-label="Actions for <what>"><svg>…</svg></summary>
       <div class="row-menu-panel">…the buttons, in their normal classes…</div>
     </details>

   <details> rather than a scripted popover: it opens and closes with no
   JavaScript at all, and the console's script only adds the niceties (close on
   an outside click or Escape). Items keep their ordinary .btn classes, so a
   destructive action is still the one danger button the console has; the panel
   restyles them into menu rows rather than inventing a second button system.

   A VALUE the row displays does not belong in here: a member's role is a
   select in the Role column, not an action. A per-row SETTING with no column
   of its own does, at the TOP and above a .row-menu-sep, so it is not read as
   an action -- the per-member card allowance, which was a select repeated on
   every row of a column that showed the same default six times over. When a
   setting is off its default, the row says so with a chip; the control that
   changes it is in here. */
.row-menu { position: relative; display: inline-block; }
.row-menu > summary { list-style: none; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: 1px solid var(--border); border-radius: var(--radius-sm); color: var(--text-2); cursor: pointer; transition: background .2s ease, border-color .2s ease, color .2s ease; }
.row-menu > summary::-webkit-details-marker, .row-menu > summary::marker { display: none; content: ""; }
.row-menu > summary:hover, .row-menu[open] > summary { background: var(--btn-tint); border-color: var(--btn-hover); color: var(--text); }
.row-menu > summary:focus-visible { outline: 2px solid var(--btn); outline-offset: 2px; }
.row-menu > summary svg { width: 18px; height: 18px; }
.row-menu-panel { position: absolute; right: 0; top: calc(100% + 6px); z-index: 40; min-width: 190px; padding: 6px; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow-pop); display: flex; flex-direction: column; gap: 2px; text-align: left; }
.row-menu-panel form { display: block; width: 100%; margin: 0; }
/* Menu rows, not pills: full width, left aligned, no border of their own. The
   colour each button already carries is what still separates them. */
/* One text colour for every non-destructive item. The classes carry their own
   (.tertiary is --text-2, .secondary is --text), which outside a menu is the
   point but inside one made "View" read as disabled next to "Edit". */
.row-menu-panel .btn { width: 100%; height: 34px; justify-content: flex-start; padding: 0 10px; border-color: transparent; background: transparent; color: var(--text); font-weight: 600; letter-spacing: 0; }
.row-menu-panel .btn:hover { background: var(--btn-tint); border-color: transparent; transform: none; }
.row-menu-panel .btn.danger, .row-menu-panel .btn.danger:hover { color: var(--danger-btn); }
.row-menu-panel .btn.danger:hover { background: var(--danger-tint); }
/* A row's one SETTING, above a divider so it is not read as an action. The
   caption is the label the row no longer has to carry in a column. */
.row-menu-field { padding: 6px 10px 8px; }
.row-menu-field > span { display: block; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--text-3); margin-bottom: 6px; }
.row-menu-field form { margin: 0; }
.row-menu-field select { width: 100%; }
.row-menu-sep { height: 1px; margin: 4px 6px; background: var(--border); }
/* The allowance chip, shown only when the member is NOT on the team default. */
.cell-cards .pill { margin-left: 8px; }
/* A plain <table> (cards, domains) lives in the same panel; only the rows scroll.
   min-width:0 stops the table's min-content width leaking out through the flex item. */
.table-scroll { min-width: 0; max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
table { width: 100%; border-collapse: collapse; background: transparent; }
th, td { text-align: left; padding: 10px 20px; border-bottom: 1px solid var(--border-light); font-size: 14px; }
th { font-size: 12px; font-weight: 700; color: var(--text-2); text-transform: uppercase; letter-spacing: .05em; border-bottom-color: var(--border); }
tr:last-child td { border-bottom: 0; }
td.cell-actions { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 8px; }
.person { display: flex; align-items: center; gap: 12px; min-width: 0; }
.person .name { font-weight: 600; }
.person .email { font-size: 12px; color: var(--text-2); }
.avatar { width: 32px; height: 32px; flex-shrink: 0; border-radius: 999px; background: var(--brand-light); color: var(--brand-text); font-weight: 700; font-size: 12px; display: flex; align-items: center; justify-content: center; }
/* A list row that names a thing rather than a person (Cards' Name+Job): the
   same anatomy as .person -- a bold title, a muted line under it -- with no
   avatar. */
.entity { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.entity .name { font-weight: 600; }
/* The Job value: its own grid column on desktop; folded into .entity's second
   line on phones instead of repeating on the meta line (see the phone block). */
.entity .hint { display: none; }
.cell-job { min-width: 0; }
/* The Cards cell holds one link per card, comma separated; it has to wrap
   rather than push the actions column off the row. */
.cell-cards { min-width: 0; overflow-wrap: anywhere; }

/* --- Pills and filter capsules -------------------------------------------- */
/* A pill carries a border, as the design system's does: its neutral fill is
   one step off the card, which in dark mode is 1.1:1, so without an edge a tag
   is a smudge. Tinted pills keep the border transparent so every pill measures
   the same and the tint stays the only thing that colours them. */
.pill { display: inline-block; padding: 3px 10px; border-radius: 999px; font-size: 12px; font-weight: 700; line-height: 1.5; background: var(--border-light); color: var(--text-2); border: 1px solid var(--border); }
.pill.live { background: var(--success-light); color: var(--success); border-color: transparent; }
.pill.joined { background: var(--brand-light); color: var(--brand-text); border-color: transparent; }
.pill.trial { background: var(--accent); color: var(--on-accent); border-color: transparent; }
.pill.inactive, .pill.removed { background: var(--border-light); color: var(--text-3); }
.pill.failed { background: var(--danger-tint); color: var(--danger-btn); border-color: transparent; }
/* Parked reads as "needs attention", not as "broken": the card, its link and
   its contacts are all intact and waiting for an admin to hand it on. So the
   accent's tint rather than .failed's danger red. */
.pill.parked { background: var(--accent-tint, var(--border-light)); color: var(--text-2); border-color: var(--border); }
.chip { display: inline-block; padding: 4px 12px; border-radius: 999px; font-size: 12px; font-weight: 700; line-height: 1.4; border: 1px solid var(--border); color: var(--text-2); background: var(--card); white-space: nowrap; cursor: pointer; }
.chip:hover { border-color: var(--btn-hover); color: var(--text); }
/* Selected is the brand, not ink. Blue as an AREA is the light stop, so a
   chosen capsule fills with the same value the filled button does and the
   two read as one family; in dark that flips to the brand yellow with a
   near-black label, which is the mode flip the system is built on. Ink
   here made the one selected thing on the page look switched OFF. */
.chip.is-active { background: var(--btn); color: var(--btn-on); border-color: var(--btn); }
.chip.is-active:hover { background: var(--btn-hover); border-color: var(--btn-hover); color: var(--btn-on); }
.chips { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }

/* --- Checklist ------------------------------------------------------------ */
.checklist { display: flex; flex-direction: column; }
.checklist .item { display: flex; align-items: center; gap: 14px; padding: 12px 0; border-top: 1px solid var(--border-light); font-size: 15px; }
.checklist .item.done { color: var(--text-2); text-decoration: line-through; }
.checklist .item.next { background: var(--tint-soft); margin: 0 calc(var(--card-pad-x) * -1); padding-left: var(--card-pad-x); padding-right: var(--card-pad-x); font-weight: 600; }
.checklist .detail { margin-left: auto; font-size: 13px; color: var(--text-2); text-decoration: none; }

/* --- Messages, banners, errors -------------------------------------------- */
.banner { padding: 12px 16px; border-radius: var(--radius); font-size: 14px; font-weight: 600; }
.banner.success { background: var(--success-light); color: var(--success); }
.banner.error { background: var(--danger-tint); color: var(--danger); }
.messages { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 8px; }
.messages li { padding: 10px 14px; border-radius: var(--radius); font-size: 14px; }
.messages li.success { background: var(--success-light); color: var(--success); }
.messages li.error { background: var(--danger-tint); color: var(--danger); }
.errorlist { color: var(--danger); font-size: 13px; margin: 0; padding-left: 18px; }
.text-danger { color: var(--danger); }
form.inline { display: inline; margin: 0; }

/* --- Forms ---------------------------------------------------------------- */
/* A fixed height, and a line box small enough to sit inside it.
   The original bug was the second half: `height: 42px` with 10px padding left
   a 20px content box holding a 22.5px line box (15px at the inherited 1.5),
   and a line box that does not fit its content box cannot be centred in it, so
   every field drew its text 1px low. A select showed it worst because its box
   is the widest and emptiest. Dropping line-height to 1.2 makes the box 18px,
   which fits, and the select now measures exactly as a flex-centred box does.
   The height has to stay DECLARED even though 8 + 18 + 8 + 2 would derive it:
   WebKit ignores line-height on a single-line text input, so with height:auto
   an input took the font's natural 23px line box and stood 47px next to a 42px
   select. Declaring it pins both engines to 42.
   Padding is 8 rather than 11 for the same reason: it leaves a 24px content
   box, which is wide enough for WebKit's 23px line box to fit and centre too.
   Chrome still draws a fixed-height INPUT's text 1px low and no padding or
   line-height value moves it -- only height:auto does, which is the trade
   above. The select, which is what a person actually sees this on, is right. */
input[type=text], input[type=email], input[type=password], input[type=number], input[type=url],
input[type=search], input:not([type]), textarea, select {
  font: inherit; font-size: 15px; line-height: 1.2; color: var(--text); background: var(--card);
  border: 1px solid var(--field-border); border-radius: var(--radius); padding: 8px 12px;
  height: 42px; width: 100%;
}
/* Without this the platform draws the control: its own corner radius (which
   does not match --radius) and its own indicator. The chevron below is two
   gradient triangles, so it inherits the text colour in both themes. The rule
   must sit AFTER the block above, whose `background` shorthand would reset it. */
select {
  appearance: none; -webkit-appearance: none;
  padding-right: 32px;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 16px center, right 12px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
textarea { height: auto; line-height: 1.5; resize: vertical; }
input[type=checkbox], input[type=radio] { width: auto; height: auto; }
input[type=file] { max-width: 100%; height: auto; padding: 8px 10px; }
/* Only in a one-line control row does the file input take the free width. */
.form-row input[type=file] { flex: 1 1 200px; min-width: 0; }
input:focus, textarea:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 4px var(--brand-light); }
/* A dropdown colours under the POINTER, not after the click. Clicking a select
   leaves it focused, so the ring outlives the choice and sits there glowing on
   a row nobody is looking at any more. :focus-visible cannot be the filter:
   Chrome matches it on a plain mouse click for a select, because a select stays
   keyboard-operable once focused (a button does not stay focused, and does not
   match) -- measured, not assumed. So the shell records which modality moved
   focus on <html data-focus>, and only a keyboard focus rings. A text field is
   deliberately NOT included: you need to see where you are typing. */
select:focus { outline: none; }
select:hover { border-color: var(--brand); }
:root[data-focus="key"] select:focus { border-color: var(--brand); box-shadow: 0 0 0 4px var(--brand-light); }
label { display: block; margin: 14px 0 4px; font-size: 13px; font-weight: 600; color: var(--text-2); }
/* A stack of labelled fields inside a card: the label sits on the field, 14px apart. */
.field-stack { display: flex; flex-direction: column; gap: 14px; }
.field-stack > label, .field { display: flex; flex-direction: column; gap: 4px; margin: 0; font-size: 13px; font-weight: 400; color: var(--text); }
.field-stack .muted, .field-stack .hint { font-weight: 400; }
/* `display: flex` is declared HERE, not inherited from `.field-stack > label`.
   Without it the four checkbox rows that sit outside a field stack (App Clip's
   Maps toggle, Settings' logo enforcement) fell back to the plain `label`
   block: gap did nothing, so the box touched its text, and there was no
   align-items, so the box sat on the text baseline instead of centred. */
.checkbox-row { display: flex; flex-direction: row !important; align-items: center; gap: 8px; }

/* New leads, on the sidebar's Leads row. Only ever drawn when there are some:
   a zero on every page would be a permanent nought, not news. */
.nav .nav-count {
  margin-left: auto;
  min-width: 20px;
  padding: 0 6px;
  border-radius: 10px;
  background: var(--brand-light);
  color: var(--brand-text);
  font-size: 11px;
  font-weight: 600;
  line-height: 18px;
  text-align: center;
}

/* display:flex above beats the UA's [hidden] rule, so say it again. */
.checkbox-row[hidden] { display: none !important; }
/* One-of-several choice (New Card's Holder): a radio, its label, and the input
   or helper that belongs to that option. Rows are spaced like .field-stack, the
   option's own control keeps the standard 42px height, and the body lines up
   under the label rather than under the radio. */
.choice-list { display: flex; flex-direction: column; gap: 14px; }
.choice { display: flex; flex-direction: column; gap: 6px; }
.choice > .choice-head { display: flex; align-items: center; gap: 10px; margin: 0; font-size: 14px; font-weight: 600; color: var(--text); cursor: pointer; }
.choice input[type=radio] { width: 18px; height: 18px; margin: 0; flex-shrink: 0; accent-color: var(--btn); }
.choice input[type=radio]:focus-visible { outline: 2px solid var(--btn); outline-offset: 3px; }
.choice-body { display: flex; flex-direction: column; gap: 4px; margin-left: 28px; }
/* A select that acts as a control inside a table row, not a form field. */
.select-inline { width: auto; height: 32px; padding: 0 26px 0 10px; font-size: 13px;
  background-position: right 12px center, right 8px center; background-size: 4px 4px, 4px 4px; }
/* A form laid out as one line of controls (invite, add-domain, import). */
.form-row { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; }
.form-row > * { margin-top: 0; }
.form-row input, .form-row select, .form-row textarea { flex: 1 1 200px; width: auto; min-width: 0; }
.form-row .btn { flex: 0 0 auto; }
/* Wraps, because .btn is white-space: nowrap and the Plan card puts "Manage
   payment and invoices", "Switch to yearly" and an info mark in one row: on a
   narrow screen that overflowed sideways instead of stacking. */
.form-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 4px; }

/* The Leads toolbar's pickers and search, folded behind one disclosure so a
   phone shows the status capsules and nothing else until asked. Kept as a
   <details>, so it needs no script, the way .row-menu does. */
/* Parts of one card, divided by a rule instead of by three borders. Used where
   several single controls answer one question and each had grown a card of its
   own -- the lead page's Handling block. The label is a part's name, quieter
   than a .section-title, which the card already has one of. */
.card-part { padding-block: 14px; border-block-start: 1px solid var(--border-light); }
.card-part:first-of-type { padding-block-start: 0; border-block-start: 0; }
.card-part:last-of-type { padding-block-end: 0; }
.part-label { display: block; font-size: 13px; font-weight: 600; color: var(--text-2); margin-block-end: 8px; }

/* Label and value rows inside a card: the same shape Overview's figures take,
   reused wherever a block of facts would otherwise be a sentence joining them
   with middots. Bleeds to the card's edges, so the rules line up with it. */
.facts-rows { display: grid; margin: 4px calc(var(--card-pad-x) * -1) 0; }
.facts-rows > div {
  display: grid; grid-template-columns: 1fr auto; align-items: baseline;
  gap: 0 12px; padding: 11px var(--card-pad-x);
  border-block-start: 1px solid var(--border-light);
}
.facts-rows > div:first-child { border-block-start: 0; }
.facts-rows dt { font-size: 14px; color: var(--text-2); }
.facts-rows dd { margin: 0; font-size: 16px; font-weight: 600; text-align: end; font-variant-numeric: tabular-nums; }
.facts-rows dd small { font-size: 12.5px; font-weight: 400; color: var(--text-3); margin-inline-start: 4px; }

/* A setting stated as a sentence with a switch beside it. Still an
   <input type=checkbox>, so the form, the keyboard and a screen reader are
   unchanged; only the paint is a switch. The whole row is the label, so the
   text is a target too, not just the 42px control.

   The off track takes --field-border, not --border: this is a control's own
   edge, where 3:1 is the bar, and --border is 1.23:1 on a card. */
.switch-row {
  display: grid; grid-template-columns: 1fr auto; align-items: center;
  gap: 2px 16px; padding-block: 14px; border-top: 1px solid var(--border-light);
  cursor: pointer;
}
.switch-row:first-of-type { border-top: 0; }
.switch-row > .label { grid-column: 1; font-size: 14px; font-weight: 600; }
.switch-row > .hint { grid-column: 1; margin: 0; }
/* Django's CheckboxSelectMultiple, painted as switches: #id_x > div > label >
   input + text. The label becomes the row, and the input is ordered after the
   text because an anonymous text item cannot be given an order of its own. */
.switch-list label {
  display: grid; grid-template-columns: 1fr auto; align-items: center;
  gap: 0 16px; padding-block: 13px; border-block-start: 1px solid var(--border-light);
  cursor: pointer; font-size: 14px; font-weight: 600;
}
/* The widget wraps the choices in a div of its own, so these are descendant
   selectors, not child ones. */
.switch-list > div > div:first-child label { border-block-start: 0; }
.switch-list label > input[type=checkbox] { order: 2; }

.switch-row > input[type=checkbox],
.switch-list label > input[type=checkbox] {
  grid-column: 2; grid-row: 1 / span 2; align-self: center; flex: 0 0 auto;
  appearance: none; -webkit-appearance: none; margin: 0; padding: 0;
  width: 42px; height: 26px; border: 0; border-radius: 99px;
  background: var(--field-border); position: relative; cursor: pointer;
  transition: background .15s ease;
}
.switch-row > input[type=checkbox]::after,
.switch-list label > input[type=checkbox]::after {
  content: ""; position: absolute; inset-block-start: 3px; inset-inline-start: 3px;
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--switch-knob); box-shadow: var(--shadow);
  transition: inset-inline-start .15s ease;
}
.switch-row > input[type=checkbox]:checked,
.switch-list label > input[type=checkbox]:checked { background: var(--btn); }
.switch-row > input[type=checkbox]:checked::after,
.switch-list label > input[type=checkbox]:checked::after { inset-inline-start: 19px; }
.switch-row > input[type=checkbox]:focus-visible,
.switch-list label > input[type=checkbox]:focus-visible { outline: 2px solid var(--btn); outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) {
  .switch-row > input[type=checkbox], .switch-row > input[type=checkbox]::after { transition: none; }
}
/* A switch row inside a field stack. `.field-stack > label` is (0,1,1) and
   `.switch-row` is (0,1,0), so the stack's flex column won on specificity
   wherever the two met and the knob dropped BELOW its own sentence instead of
   sitting beside it -- source order cannot fix this, only matching weight can.
   Card settings' "private card" row has been drawn that way unnoticed. */
.field-stack > label.switch-row {
  display: grid; grid-template-columns: 1fr auto; gap: 2px 16px;
}

/* A form that wraps more than one card: it stands where a .stack child would
   have, so it carries the same gap. Settings uses it to keep the brand fields
   and the member locks in one autosave while giving each its own card. */
.card-group { display: grid; gap: var(--stack-gap); }

/* A card whose head is its own disclosure. The head keeps the card's padding,
   so open or closed it is the same object; only the marker is added. */
.fold > summary { cursor: pointer; list-style: none; margin-bottom: 0; }
.fold > summary::-webkit-details-marker { display: none; }
.fold > summary::after {
  content: ""; flex: 0 0 auto; width: 7px; height: 7px; margin-inline-start: 2px;
  border-inline-end: 1.5px solid var(--text-3); border-block-end: 1.5px solid var(--text-3);
  transform: rotate(45deg); transition: transform .15s ease;
}
.fold[open] > summary::after { transform: rotate(225deg); margin-block-start: 4px; }
.fold > summary:focus-visible { outline: 2px solid var(--btn); outline-offset: 2px; }
.fold[open] > summary { margin-bottom: 12px; }
@media (prefers-reduced-motion: reduce) { .fold > summary::after { transition: none; } }

/* The lead list's spine. A date said once above its rows, instead of repeated
   in a column down the whole list. */
.day {
  padding: 10px var(--card-pad-x) 6px; background: var(--bg);
  font-size: 12px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--text-3); border-block-start: 1px solid var(--border);
}
.day:first-of-type { border-block-start: 0; }

.filter-more { flex: 1 1 100%; min-width: 0; }
.filter-more > summary { cursor: pointer; font-size: 14px; font-weight: 600; color: var(--brand-text); padding: 6px 2px; width: fit-content; }
.filter-more > summary:focus-visible { outline: 2px solid var(--btn); outline-offset: 2px; }
.filter-more > .more-fields { display: grid; gap: 8px; padding-block-start: 8px; }
@media (min-width: 768px) {
  .filter-more { display: contents; }
  .filter-more > summary { display: none; }
  /* Overrides the UA's display:none on a closed details' children, so the
     desktop toolbar keeps the single flex row it always had. */
  .filter-more > .more-fields { display: contents; }
}
/* A text field and its one action as a single control (the design system's
   .input-group): the field's border wraps both and the button sits inside it,
   so "Add a tag" + Add never breaks onto two lines in a narrow column. The
   package draws it as a pill for the marketing site's search bar; here it keeps
   the console's field radius, because every other control on the page is 10px
   and 999px is rounder than the roundest step of the 8/10/14 ramp. The button
   nests one step down so its corners sit inside the 4px of padding. */
.input-group { display: flex; align-items: center; gap: 4px; padding: 4px; background: var(--card); border: 1px solid var(--field-border); border-radius: var(--radius); }
.input-group:focus-within { border-color: var(--brand); box-shadow: 0 0 0 4px var(--brand-light); }
.input-group input { flex: 1 1 auto; min-width: 0; width: auto; height: 32px; padding: 0 12px; border: 0; border-radius: var(--radius-sm); background: transparent; font-size: 14px; }
.input-group input:focus { outline: none; box-shadow: none; }
.input-group .btn { flex: 0 0 auto; border-radius: var(--radius-sm); }
/* Autosave (static/js/autosave.js): the Save button is for a browser without
   JavaScript; with it, the status line takes the button's place. */
html.js-autosave form[data-autosave] .form-actions:not(.form-actions-always) { display: none; }
/* One banner for the whole console: a per-form status line put the verdict
   below the fold on any page carrying more than one autosave form. Fixed to
   the bottom, out of the way of the field being edited, and quiet -- this
   reports, it does not ask for anything. */
.save-banner { position: fixed; inset-block-end: 18px; inset-inline-end: 18px; z-index: 40;
  max-width: min(340px, calc(100vw - 36px)); padding: 10px 16px; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--card); color: var(--text-2);
  box-shadow: var(--shadow-pop); font-size: 13.5px; font-weight: 600; }
.save-banner.is-saved { border-color: var(--success); color: var(--success); }
.save-banner.is-error { border-color: var(--danger); color: var(--danger); }
@media (max-width: 599px) {
  .save-banner { inset-inline: 12px; inset-block-end: 12px; max-width: none; text-align: center; }
}
/* A repeatable contact row: its fields grow, the remove button sits on their baseline. */
.fs-row { display: flex; align-items: flex-end; flex-wrap: wrap; gap: 10px; margin-bottom: 14px; }
.fs-rows > .fs-row:last-child { margin-bottom: 0; }
.fs-fields { flex: 1; min-width: 0; display: grid; gap: 10px; grid-template-columns: 2fr 1fr; }
.fs-field { display: flex; flex-direction: column; gap: 4px; margin: 0; font-size: 13px; font-weight: 400; color: var(--text); }
.add-row { align-self: flex-start; margin-top: 14px; }
.row-remove { margin-bottom: 3px; }
/* One control, and now literally one: matching the heights and corners below
   got the three objects to the same size, but they still read as three things
   floating left of a wide card with dead space beside them. As a grid inside a
   single border the value is centred by construction, the two targets sit at
   the ends where a thumb reaches, and the card has one left and one right edge
   all the way down. The trailing price sentence moved to the hint under it, so
   there is no fourth item to wrap. */
.seat-stepper {
  /* minmax(0, 1fr), not 1fr: a bare 1fr keeps an automatic min-content floor,
     and a number input asked to size itself wants about 327px at 32px bold. The
     track refused to shrink below that, the grid overflowed its own 280px box,
     and overflow: hidden clipped the + button off the end. */
  display: grid; grid-template-columns: 56px minmax(0, 1fr) 56px; align-items: stretch;
  height: 56px; width: 100%; max-width: 280px;
  border: 1px solid var(--field-border); border-radius: var(--radius);
  background: var(--card); overflow: hidden;
}
/* The -/+ buttons ARE the spinner, so the browser drew a second one. It also
   pushed the figure off-centre: a number input centres its text in the box
   *minus* the spinner, not in the box. */
.seat-stepper input[type=number] {
  appearance: textfield; -moz-appearance: textfield;
  width: auto; min-width: 0; height: auto; padding: 0; text-align: center;
  font-size: 32px; font-weight: 700;
  border: 0; border-inline: 1px solid var(--border); border-radius: 0;
  background: transparent;
}
.seat-stepper input[type=number]::-webkit-outer-spin-button,
.seat-stepper input[type=number]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.seat-stepper .stepper { width: auto; height: auto; font-size: 24px; border: 0; border-radius: 0; }
.color-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* The colour fills its container. Left alone, the platform insets the swatch
   inside its own padding and draws it with its own corner radius, so a rounded
   rectangle floats inside a rounded box. It is also 42px, the height of every
   other control, so it lines up with the hex field beside it. */
.color-row input[type=color] { width: 48px; height: 42px; padding: 0; border: 1px solid var(--field-border); border-radius: var(--radius); background: var(--card); cursor: pointer; overflow: hidden; }
.color-row input[type=color]::-webkit-color-swatch-wrapper { padding: 0; }
.color-row input[type=color]::-webkit-color-swatch { border: 0; border-radius: calc(var(--radius) - 1px); }
.color-row input[type=color]::-moz-color-swatch { border: 0; border-radius: calc(var(--radius) - 1px); }
/* No height here: 40px made this the only 40px control in the file, 2px short
   of the swatch beside it and of the field above it, at every width. No fixed
   width either: next to a 48px swatch, 120px left a gap on every screen. */
.color-row input[type=text] { width: auto; flex: 1 1 auto; min-width: 0; font-family: 'SF Mono', Menlo, monospace; text-transform: uppercase; }
/* Logo row: mark + button on ONE line, the hint on its own line below (never let the button wrap). */
.logo-row { display: flex; align-items: center; gap: 14px; flex-wrap: nowrap; }
.logo-row .btn { flex-shrink: 0; white-space: nowrap; }
.logo-row input[type=file] { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; }
.logo-mark { position: relative; width: 56px; height: 56px; flex-shrink: 0; border-radius: 12px; color: #fff; font-weight: 700; font-size: 24px; display: flex; align-items: center; justify-content: center; }
/* Two slots side by side, each previewing its logo on the background it has to
   survive -- a mark drawn for white paper vanishes on a dark card, which is the
   whole reason the two are uploaded separately. The backgrounds are fixed, not
   theme tokens: the point is to show both extremes whatever theme the admin is
   in. */
.logo-pair { display: flex; gap: 20px; flex-wrap: wrap; align-items: flex-start; }
.logo-slot { display: flex; flex-direction: column; align-items: center; gap: 8px; }
/* The preview is the subject of this slot, so it is not allowed to be smaller
   than its own caption: a 56px mark over a 166px row of buttons read as a
   stamp with a label stuck under it. At 220px a wordmark is legible at a
   glance, which is the whole point of a preview, and two slots plus the gap
   still leave room in the column. The padding is not decoration: a wordmark
   is far wider than it is tall, so `object-fit: contain` runs it edge to edge
   and it collided with the badge's own rounded corners. Scoped to .logo-slot:
   the onboarding step draws a bare .logo-mark in a different layout. */
.logo-slot .logo-mark { width: 220px; height: 220px; padding: 24px; font-size: 72px; }
.logo-slot input[type=file] { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; }
.logo-slot .btn { flex-shrink: 0; white-space: nowrap; max-width: 140px; overflow: hidden; text-overflow: ellipsis; }
.logo-slot .hint { font-size: 12px; }
.logo-mark.on-light { background: #fff; color: #161B22; box-shadow: inset 0 0 0 1px var(--border); }
.logo-mark.on-dark { background: #161B22; color: #fff; }
/* Nothing sits ON a thumbnail. The x that used to hang off this mark was the
   console's only icon-on-media control: 22px against a 42/36/32 button scale,
   999px against an 8/10/14 radius ramp, and offset with top/right, which Rule 8
   does not allow. Media actions live in the header row of the block that owns
   the media -- .card-head, the same shape .page-head already uses -- or, where a
   block holds two thumbnails (the logo pair), in each slot's own .actions row. */
/* An uploaded brand logo fills any mark that would otherwise show the initial
   (the brand card, the App Clip badge, the sidebar's org mark) -- contained, so
   a wide wordmark and a square glyph both sit inside the same box. */
.mark-img { width: 100%; height: 100%; object-fit: contain; border-radius: inherit; display: block; }
/* A form column that reads at a comfortable measure and stays centred. */
.form-narrow { max-width: 620px; margin-left: auto; margin-right: auto; width: 100%; }

/* --- App Clip preview -----------------------------------------------------
   Apple's card: a 3:2 header image, then one row with the title and subtitle on
   the left and the action button on the right, then Apple's own "Powered by"
   footer. There is no brand colour band and no logo badge here -- an earlier
   version drew both, which promised a card Apple does not render. The image is
   the whole visual, which is why it has a hard size. */
.appclip-preview { padding: 4px 0; }
.appclip-card { border-radius: 16px; overflow: hidden; background: var(--card); box-shadow: var(--shadow-card); border: 1px solid var(--border); max-width: 380px; }
/* 3:2, matching the 1800x1200 Apple takes, so the preview crops exactly as the
   real card does. */
.appclip-shot { aspect-ratio: 3 / 2; width: 100%; display: block; }
.appclip-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.appclip-shot.is-empty, .appclip-image-slot.is-empty { display: flex; align-items: center; justify-content: center; background: repeating-linear-gradient(45deg, var(--border-light), var(--border-light) 10px, var(--card) 10px, var(--card) 20px); color: var(--text-3); font-size: 13px; font-weight: 700; text-align: center; }
.appclip-image-empty { padding: 0 12px; }
.appclip-row { display: flex; align-items: center; gap: 12px; padding: 12px 14px; }
.appclip-texts { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.appclip-texts strong { font-size: 15px; overflow-wrap: anywhere; }
.appclip-texts .hint { font-size: 13px; overflow-wrap: anywhere; }
/* Apple's own button: a filled blue pill, its wording chosen from Apple's list.
   Not themeable -- showing it in a team's brand colour would be a lie. */
.appclip-cta { flex-shrink: 0; background: #007AFF; color: #fff; font-weight: 700; font-size: 14px; padding: 8px 20px; border-radius: 999px; }
.appclip-footer { display: flex; justify-content: space-between; gap: 12px; padding: 10px 14px; border-top: 1px solid var(--border); font-size: 12px; color: var(--text-3); }
/* The place's card in Maps: place, address, and the Maps action as the button. */
.appclip-maps { display: flex; flex-direction: column; gap: 6px; margin-top: 14px; max-width: 380px; }
.appclip-place { display: flex; align-items: center; gap: 12px; padding: 12px 14px; border: 1px solid var(--border); border-radius: 16px; background: var(--card); box-shadow: var(--shadow-card); }

/* The upload control, previewing at the same aspect ratio it enforces. */
.appclip-image-field { display: flex; flex-direction: column; gap: 10px; align-items: flex-start; margin-bottom: 4px; }
.appclip-image-slot { position: relative; width: 100%; max-width: 300px; aspect-ratio: 3 / 2; border-radius: 10px; overflow: hidden; border: 1px solid var(--border); }
.appclip-image-slot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.appclip-image-field input[type=file] { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; }
.appclip-image-field .btn { white-space: nowrap; max-width: 300px; overflow: hidden; text-overflow: ellipsis; }

/* The wizard's brand preview: the business card's logo and colour, which is
   what that step actually configures. */
.brand-preview { display: flex; flex-direction: column; gap: 8px; }
.brand-preview-card { display: flex; align-items: center; gap: 12px; padding: 16px; border-radius: 14px; color: #fff; }
.brand-preview-texts { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.brand-preview-texts .hint { color: rgba(255,255,255,0.8); }

/* --- Setup wizard --------------------------------------------------------- */
.setup-shell { max-width: 720px; margin: 0 auto; width: 100%; display: flex; flex-direction: column; gap: 22px; }
.setup-progress { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; }
.setup-progress li { display: flex; flex-direction: column; gap: 8px; font-size: 13px; font-weight: 600; color: var(--text-3); }
.setup-progress li.is-current, .setup-progress li.is-done { color: var(--text); }
.setup-progress .bar { height: 4px; background: var(--border); }
.setup-progress .bar-on { background: var(--brand); }
.setup-progress .bar-done { background: var(--success); }
/* The deferred trial: the way past Checkout, and what the dashboard then says. */
/* Its own card now, so the top border and the inset that used to separate it
   from the step above are the card's own. */
.setup-later { margin-top: 18px; padding: 18px 20px; text-align: center; }
.setup-footnote { margin-top: 14px; padding-inline: 4px; text-align: center; }
.setup-later .hint { margin-top: 8px; }
.preview-note p { margin: 0 0 14px; color: var(--text-2); font-size: 14px; }
.setup-card { padding: 32px 36px; }
.setup-card > h1 { margin-bottom: 6px; }
.setup-grid { display: grid; grid-template-columns: 1fr 300px; gap: 28px; align-items: start; }
.setup-fields { display: flex; flex-direction: column; }
.setup-actions { grid-column: 1 / -1; display: flex; justify-content: space-between; align-items: center; gap: 10px; padding-top: 16px; margin-top: 6px; border-top: 1px solid var(--border-light); }
.setup-actions .btn:only-child { margin-left: auto; }
.price-box { padding: 16px 18px; background: var(--bg); border-radius: var(--radius); margin: 18px 0; }
.price-box strong { font-size: 26px; }
.price-box .hint { display: block; margin-top: 6px; }
/* Monthly / yearly: two radio cards; the checked one takes the button tint. */
.interval-toggle { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin: 18px 0 0; padding: 0; border: 0; }
.interval-toggle .seg { display: flex; flex-direction: column; gap: 2px; padding: 12px 14px; border: 1.5px solid var(--border); border-radius: var(--radius); cursor: pointer; }
.interval-toggle .seg input { position: absolute; opacity: 0; width: 0; height: 0; }
.interval-toggle .seg-name { font-weight: 600; }
.interval-toggle .seg-price { font-size: 13px; color: var(--text-3); }
.interval-toggle .seg.on { border-color: var(--btn); background: var(--btn-tint); }
.interval-toggle .seg:has(input:focus-visible) { outline: 2px solid var(--btn); outline-offset: 3px; }
@media (max-width: 599px) { .interval-toggle { grid-template-columns: 1fr; } }
.stat-row { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px; width: 100%; margin: 18px 0; }
.stat-row .stat { padding: 16px; background: var(--bg); border-radius: var(--radius); display: flex; flex-direction: column; gap: 4px; }
.stat-row strong { font-size: 18px; }
.setup-done { text-align: center; align-items: center; display: flex; flex-direction: column; }
.auth-card { max-width: 440px; width: 100%; margin: 24px auto; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow); padding: 32px 36px; display: flex; flex-direction: column; gap: 14px; }
.auth-card > * { margin: 0; }
.auth-card form { display: flex; flex-direction: column; gap: 10px; }
.auth-card form > p { margin: 0; }
.auth-card form label { margin: 4px 0 0; }
.auth-card form .btn { margin-top: 8px; }
/* The signup page says what it is. Brand yellow carries the word, the sentence
   next to it stays muted body text -- the yellow measures 1.45:1 and is never
   text on a light surface (see @nfccool/design GUIDELINE.md). */
.beta-badge { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--text-3); }
.beta-badge span { background: var(--accent); color: var(--on-accent); border-radius: 999px; padding: 2px 10px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; }
.store-buttons { display: flex; gap: 10px; margin: 18px 0; }

/* --- Tablet: the sidebar collapses to an icon rail, still sticky -----------
   768px up to the desktop breakpoint — @nfccool/design's md and xl, so no
   fifth breakpoint enters the system. iPad portrait (834) and landscape
   (1024, 1180) all land on the rail; the full sidebar starts at 1200. */
@media (min-width: 768px) and (max-width: 1199px) {
  .sidebar { width: 76px; padding: 16px 8px; align-items: center; }
  .sidebar .logo-link { display: none; }
  .sidebar-menu { width: 100%; align-items: center; }
  .org-switch { padding: 0; margin-bottom: 14px; background: none; border: 0; }
  .org-switch > span, .org-switch small { display: none; }
  .org-switch .mark { width: 36px; height: 36px; border-radius: var(--radius); font-size: 15px; }
  .nav { width: 100%; align-items: center; gap: 4px; }
  .nav a { width: 60px; flex-direction: column; gap: 4px; padding: 8px 0; border-radius: var(--radius); font-size: 11px; font-weight: 600; text-align: center; }
  .nav a.is-active { font-weight: 700; }
  .nav svg { width: 20px; height: 20px; }
  .sidebar .me { flex-direction: column; gap: 6px; padding: 12px 0 0; width: 100%; }
  .sidebar .me > .me-who { display: none; }
  .sidebar .me a { font-size: 11px; }
  .me-avatar { display: flex; }
  /* The labelled switch does not fit a 76px rail: stretched to the rail's width
     its two segments still need about 140px, so "Dark" hung outside the sidebar
     and over the page. The rail already drops the logo, the org name and the
     address for the same reason, so the theme control does what it does on a
     phone and becomes its 36px icon. */
  .sidebar .theme-switch { display: none; }
  .sidebar .theme-toggle-icon { display: inline-flex; margin: 10px 0 0; }
  /* The count sits ON the icon in the rail. A nav item is a column here, icon
     over label, so `margin-left: auto` gave it a third row of its own and it
     read as a detached number under the word rather than a count of it. */
  .nav a { position: relative; }
  .nav .nav-count {
    position: absolute; inset-block-start: 2px; inset-inline-end: 4px;
    margin: 0; min-width: 16px; padding: 0 4px; font-size: 10px; line-height: 16px;
  }
  .main { padding: 8px 4px 24px; }
  :root { --stack-gap: 18px; }
  .console-plain .sidebar { width: auto; padding: 10px 16px; }
  .console-plain .main { padding: 12px 8px 24px; }
}

/* --- Phone: the sidebar becomes a floating top bar with the burger ---------
   Below the tablet rail's 768px floor. */
@media (max-width: 767px) {
  /* The gutter GROWS here, it does not shrink. At 12px the page had no edge:
     content sat flush to the frame while every card kept 18px inside it. The
     space comes off the card's padding instead, because the page needs its
     frame more than a card needs its inset. */
  :root { --gutter: 16px; --card-pad-x: 16px; --card-pad-y: 20px; --stack-gap: 16px; }
  .console { flex-direction: column; align-items: stretch; gap: 14px; }
  .sidebar { position: sticky; top: var(--gutter); width: auto; align-self: stretch; height: auto; flex-direction: row; align-items: center; gap: 10px; padding: 10px 14px; }
  .sidebar .logo-link { flex: 1; margin: 0; }
  .sidebar::before { content: ""; position: absolute; left: calc(var(--gutter) * -1); right: calc(var(--gutter) * -1); bottom: calc(100% + 1px); height: var(--gutter); background: var(--bg); }
  .sidebar .logo { height: 20px; }
  .sidebar .theme-switch { display: none; }
  .sidebar .theme-toggle-icon { display: inline-flex; order: 1; align-self: center; }
  .nav-toggle { display: inline-flex; order: 2; }
  /* The burger drops the menu panel down under the bar, as its own floating card. */
  .sidebar-menu { display: none; position: absolute; top: calc(100% + 10px); left: 0; right: 0; flex: none; padding: 12px 14px 14px; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-pop); max-height: calc(100vh - 96px); overflow-y: auto; animation: menu-slide-down .18s ease both; }
  .sidebar.is-open .sidebar-menu { display: flex; }
  .sidebar-menu .org-switch { margin: 0 0 10px; }
  .sidebar-menu .nav { font-size: 15px; }
  .sidebar-menu .nav a { padding: 11px 12px; }
  .sidebar-menu .me { margin-top: 12px; }
  .sidebar-menu .me:first-child { margin-top: 0; border-top: 0; padding-top: 4px; }
  body.menu-open { overflow: hidden; }
  .main { padding-inline: var(--gutter); padding-block: 2px 8px; }
  .console-plain .sidebar { padding: 10px 14px; }
  /* Keep Sign out reachable in the wizard on a phone; only the address goes. */
  .console-plain .sidebar .me > .me-who { display: none; }
  .console-plain .bar-label { font-size: 13px; }
  .console-plain .main { padding-inline: var(--gutter); padding-block: 2px 8px; }
  /* Four tiles become one card of four rows. Border, radius and shadow each say
     "separate object", and all three on four numbers cut the first screen into
     four competing blocks carrying twelve text objects between them. One card
     gives a left edge to read down and a right column of figures that aligns,
     and it is about 90px shorter. The secondary hint goes: "2 joined, 3
     invited" is arithmetic on the value above it, and both live one tap away. */
  .stats {
    display: block; background: var(--card); border: 1px solid var(--border);
    border-radius: var(--radius-lg); box-shadow: var(--shadow);
  }
  .stat-card {
    display: grid; grid-template-columns: 1fr auto; align-items: baseline;
    gap: 0 12px; padding: 12px var(--card-pad-x);
    border: 0; border-top: 1px solid var(--border-light);
    border-radius: 0; box-shadow: none;
  }
  .stat-card:first-child { border-top: 0; }
  .stat-card .stat-label { font-size: 14px; text-transform: none; letter-spacing: 0; color: var(--text-2); }
  .stat-card .stat-value { font-size: 19px; text-align: end; }
  .stat-card .hint { display: none; }
  .stat-card .bar { grid-column: 1 / -1; }
  .eyebrow { font-size: 11px; }
  /* .main is a flex column, so the figures can lead without touching the
     template. The preview note and the checklist both go away for good once a
     team is set up; the figures are what the page is actually for. The heading
     stays above them: it names the page, and pushed below the figures the page
     opened on four numbers belonging to nothing. */
  .main > .page-head { order: -2; }
  .main > .stats { order: -1; }
  .grid-2, .grid-2.wide, .setup-grid, .stat-row { grid-template-columns: 1fr; }
  .page-head { flex-wrap: wrap; min-height: 0; }
  .page-head-text { min-height: 0; }
  .page-head .actions { flex: 1 1 100%; flex-wrap: wrap; }
  .page-head .actions .btn { flex: 1 1 auto; }
  .setup-card { padding: 24px 20px; }
  .setup-progress { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .setup-actions { flex-direction: column-reverse; gap: 10px; }
  .setup-actions .btn { width: 100%; }
  .setup-actions .btn:only-child { margin-left: 0; }
  .auth-card { margin: 8px auto; padding: 24px 20px; }
  .toolbar { padding: 12px 16px; }
  th, td { padding: 10px 16px; }
  /* Every list table (people, cards, domains) on phones: no header row, each
     row reads as one list item -- the person/entity block full width, then
     the remaining data cells inline in one muted meta line separated by "·"
     (no attr(data-label) blocks), actions on the meta line when there's one
     or wrapped onto their own right-aligned line when there's more. */
  .table .head { display: none; }
  /* Three columns per row: the subject, a marker, the trigger. Subgrid was the
     first attempt, so the tracks would be the table's and shared by every row.
     It does not work here: nothing occupies column two or three of the TABLE on
     its own -- the toolbar, the head and the rows all span the full width -- so
     the auto tracks were sized by those spanning items and came out arbitrary,
     161px of empty space with a pill floating in it. The alignment that matters
     is the right edge, and that comes from the trigger being a fixed 32px with
     the marker pushed against it, which needs no shared tracks at all. */
  .table .row {
    display: grid; grid-template-columns: 1fr auto auto;
    align-items: center; gap: 3px 10px; padding: 13px var(--card-pad-x);
  }
  /* The subject spans both lines of the row; the markers take one line each
     beside it. */
  .table .row > .person, .table .row > .entity { grid-column: 1; grid-row: 1 / span 2; min-width: 0; }
  .entity .hint { display: block; }
  /* The second line truncates, it does not wrap. A wrapping company name or
     email is what made rows different heights, and a list of identical rows is
     most of what "calm" means. The name may still wrap: it is the row's
     subject. */
  .entity .hint, .person .email { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .cell-job { display: none; }
  /* Status takes the middle column, so the pill sits on the line the eye lands
     on and every row's marker is at the same x. The rest drop under the name as
     their own full-width line -- with columns there is nothing to separate, so
     the middot run is gone, and a missing value can no longer strand one. */
  /* grid-row: 1 on all three, or auto-placement drops the marker and the menu
     BELOW the full-width cells instead of beside the name. */
  /* The status is the one marker, pushed against the trigger, so every marker
     in the list ends on the same edge and the column is read by running down
     the right side. It spans both of the row's lines, like the trigger does, so
     the two centre on the same axis instead of the pill sitting up on the name's
     line while the ... floats between them. */
  .table .row > .cell-status {
    grid-column: 2; grid-row: 1 / span 2; align-self: center; justify-self: end;
  }
  /* The role leaves the phone row altogether. Beside the status it competed
     ("Manager" next to "Card live" read as one run of two unrelated words), and
     on its own line it was a third line for standing information. The row's ...
     menu carries the role select, whose selected option states the current role,
     so nothing is lost by dropping it here. */
  .table .row > .cell-role { display: none; }
  .table .row > .cell:not(.cell-actions):not(.cell-status):not(.cell-role) {
    grid-column: 1 / -1; min-width: 0; font-size: 13px; color: var(--text-3);
    font-variant-numeric: tabular-nums;
  }
  /* The actions take their OWN line under the row's text rather than floating
     to the right of the meta line. Right-aligned they began at whatever x the
     meta line happened to end on, so every row started its buttons somewhere
     different and the list had no left edge to read down. Full width gives it
     one, and the destructive action goes to the far end, so the thumb reaching
     for "Add card" or "Edit" is not landing next to "Remove" or "Delete". */
  /* The actions are one 32px trigger now, so they no longer need a line of
     their own: they sit at the end of the row's first line, where a thumb
     expects them. The old rules here spread a cluster of three buttons across
     full width and pushed the destructive one to the far end; there is no
     cluster left to arrange. */
  .table .row > .cell-actions { grid-column: 3; grid-row: 1 / span 2; margin-inline-start: 0; margin-block-start: 0; min-width: 0; display: flex; align-items: center; justify-content: flex-end; gap: 8px; }
  /* Opened near the bottom of the screen a right-anchored panel is fine, but
     at the very edge of a narrow screen it would run off: pin it to the row's
     right edge and let it size to the text. */
  .row-menu-panel { min-width: 170px; }
  /* A phone row was carrying four values joined by middots plus a hint that
     broke the run onto a second ragged line: role, status, a hint, cards, and
     for most rows two of those said nothing. What is left is what the row
     cannot be read without. The role is NOT dropped any more: it is the one
     place the role is shown at all, now that the duplicate select has left the
     actions cluster. */
  /* "None" and "-" are a value's way of saying there is no value. The extra
     .cell is not decoration: the rule above it is (0,4,0) and this would be
     (0,3,1), so without it `display: inline` wins and the cell stays. */
  /* The card count leaves the phone list entirely: it is a fact about cards on
     a screen about people, one tap from the screen that is about cards. That
     also settles the old "None" question, since there is no cell to fill. */
  .table .row > .cell-cards { display: none; }
  /* Defaults go silent. A badge reading the expected state on nine rows out of
     ten is wallpaper, and it makes the tenth harder to see: .pill.joined is
     "Joined" on Team and "Public" on Cards. What stays is Invited, Removed,
     Private, and a delivery failure. */
  .table .row .pill.joined { display: none; }
  /* Cards: the assignee restates the card's name on a member's own card, and
     "Unassigned" is a deliberate state on a shared card, not a fault. */
  .table .row > .cell-assignee { display: none; }
  /* A lead row: who captured it is answered by the member filter, and tags are
     unbounded width on the narrowest screen. Both stay a filter and a detail. */
  .table.leads .row > .cell-capturer, .table.leads .row > .cell-tags { display: none; }
  /* The day header carries the date now, so the row keeps only the time. */
  .table.leads .row > .cell-when { display: none; }
  /* "Open" is a second copy of the link the lead's own name already is, and as
     a full-width button in the last column it squeezed the name into wrapping. */
  .table.leads .row > .cell-actions { display: none; }
  /* The pill already carries the soft hints: "Joined" IS "no card yet", and
     "Invited" IS "Sending...". A delivery failure is not derivable, so it stays. */
  .table .row .cell .hint:not(.text-danger) { display: none; }
  .table .row .cell br { display: none; }
  .table .row .cell .hint { display: block; margin-top: 2px; }
  .table .row > .cell-actions:empty, .table .row > .cell-actions > .muted:only-child { display: none; }
  .table .cell-actions { gap: 8px; }
  .unit { display: inline; }
  /* Checklist: the detail sits under its label instead of fighting it for width. */
  .checklist .item { flex-wrap: wrap; gap: 2px 14px; }
  .checklist .detail { flex-basis: 100%; margin-left: 0; }
  .person .email { overflow-wrap: anywhere; }
  .table-scroll th, .table-scroll td { white-space: nowrap; }
  /* Controls stop shrinking. 42px is not a new touch size: it is what .btn and
     every input in this file already are, so this declines the 32px override
     rather than inventing a height. */
  .select-inline { height: 42px; font-size: 15px; padding-inline: 12px 28px; width: 100%; }
  /* There is no small button on a phone. .btn.small is 32px, which is the size
     the selects just stopped being, and it sits beside them: "Send" next to
     the follow-up picker, "Add" next to the tag field, "Upload" under a logo. A
     control has one height, and 32px is under thumb size anyway. */
  .btn.small { height: 42px; padding-inline: 18px; font-size: 15px; }
  /* ...but not the role select inside a list row. Full width it became the
     loudest thing on the row, taller than the person it belongs to. The design
     called for the role as muted text; for an admin it is genuinely the
     control, so it stays a select and sizes to its content instead. */
  .table .row > .cell-role .select-inline { width: auto; }
  .toolbar > .btn { flex: 1 1 100%; }
  /* Fixed widths that never had a phone override. A 120px label column left
     about 170px for the value, so an email broke mid string against the
     overflow-wrap: anywhere under it. */
  /* The page is ordered by what you do. Right after a conversation the job is
     to set where they stand and write down what was said; the record is
     reference and can wait, so the working panel comes first and Contact drops
     below it. */
  .grid-2 > #lead-panel { order: -1; }
  .lead-facts { grid-template-columns: 1fr; gap: 12px 0; }
  .lead-facts dt { font-size: 12px; color: var(--text-3); }
  /* Two fixed 220px squares that wrap: roughly 460px of scrolling logo. Side by
     side they fit one row, and on light beside on dark becomes the comparison
     that is the reason for having both. */
  .logo-pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
  .logo-slot .logo-mark { width: 100%; height: auto; aspect-ratio: 1; padding: 14px; font-size: 32px; }
  .logo-slot .btn { max-width: none; width: 100%; }
  /* The button under it is full width here, so the two share one edge. */
  .seat-stepper { max-width: none; }
  /* The mark stays inline, immediately right of the title it belongs to. It was
     briefly display: block here, to let the bubble sit in flow, and that dropped
     the mark onto a line of its own under every label.

     The bubble cannot leave the screen: the mark drops out of the positioned
     ancestor chain (position: static), so the bubble resolves against the page
     box instead of a 18px button, and both inline edges are pinned to the
     gutter. With no block inset set it still opens at its static position, which
     is directly under the mark it belongs to. Anchored to the mark at
     left: -8px, it ran off whichever edge the mark happened to be near.

     Opening on tap is the base rule's :focus, added because :focus-visible is a
     keyboard heuristic that does not match a touch tap, and a phone has no
     hover at all. */
  /* The mark stays a mark, right of its title, and the bubble opens on a tap:
     the include's button keeps focus on click (preventDefault stops the <label>
     most of these sit inside from stealing it) and the base rule opens on :focus
     as well as :hover.

     The bubble is framed by the CARD, not by the mark. Anchored to an 18px mark
     it ran off whichever edge the mark was near: at left: -8px off the right,
     centred off the left for a mark early in a row. Pinned to the card's padding
     it cannot overflow whatever the mark's x, and no cap has to be guessed.

     Vertically it keeps the desktop's behaviour, opening upward: inset-block is
     auto, so the box starts at its static position on the mark's own line, and
     the transform lifts it clear above that line. */
  .card { position: relative; }
  .info-tip { position: static; }
  .info-tip-bubble {
    inset-block: auto; inset-inline: var(--card-pad-x);
    width: auto; max-width: none;
    transform: translateY(calc(-100% - 6px));
  }
  .form-actions { display: grid; }
  .form-actions .btn { width: 100%; }
  .fs-fields { grid-template-columns: 1fr; }
  .setup-card { padding: 20px 16px; }
}
@keyframes menu-slide-down { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .sidebar-menu { animation: none; }
  .btn, .btn:hover, .chip, .nav a { transition: none; transform: none; }
}

/* DNS record rows on the Domains page: a label/value grid rather than a table,
   so the value can wrap on a phone without the label leaving its side. */
.dns-record { display: grid; grid-template-columns: 72px 1fr; gap: 6px 12px; margin: 8px 0 4px; }
.dns-record dt { color: var(--text-2); font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; align-self: center; }
.dns-record dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
.dns-record dd code { font-size: 13px; }

/* One input per invitee on the setup wizard: a stack of full-width fields, with
   Add another sitting under them rather than in the row. */
.email-rows { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.email-rows input { width: 100%; }

/* Crop tool (static/js/image-crop.js, _image_crop.html): opens under an image
   field -- the App Clip image when a pick misses Apple's rule, the card photo
   on every pick. The frame keeps the output's shape. */
.crop-tool { width: 100%; max-width: 480px; display: flex; flex-direction: column; gap: 10px; padding: 12px; margin-top: 12px; border: 1px solid var(--border); border-radius: 10px; background: var(--bg); }
.crop-tool-note { margin: 0; font-size: 13px; color: var(--text-2); }
.crop-tool-frame { position: relative; border-radius: 8px; overflow: hidden; border: 1px solid var(--border); background: #fff; }
.crop-tool canvas { display: block; width: 100%; height: auto; cursor: grab; touch-action: none; }
.crop-tool canvas:active { cursor: grabbing; }
/* The card shows its photo round: a circular window over the square frame. */
.crop-tool.is-round .crop-tool-frame::after { content: ""; position: absolute; inset: 0; border-radius: 50%; box-shadow: 0 0 0 999px rgba(255, 255, 255, 0.6); pointer-events: none; }
.crop-tool-controls { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.crop-tool-zoom { display: flex; align-items: center; gap: 8px; flex: 1 1 160px; min-width: 0; }
.crop-tool-zoom input[type=range] { flex: 1; min-width: 0; margin: 0; }
.crop-tool .hint { margin: 0; }

/* Card photo: a round slot centred in its card, actions beneath it, the file
   input behind a button. The slot is sized to judge a crop by, not to hint
   that a photo exists -- it is the one thing this card is about. */
.photo-field { display: flex; flex-direction: column; align-items: center; }
.photo-slot { position: relative; width: 132px; height: 132px; flex-shrink: 0; border-radius: 50%; background: var(--brand-light); color: var(--brand-text); display: flex; align-items: center; justify-content: center; }
.photo-slot img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; object-position: 50% 30%; display: block; }
.photo-slot .photo-empty { display: flex; }
.photo-slot .photo-empty svg { width: 56px; height: 56px; }
/* Peers, so one geometry for both: same border width, same padding. Remove
   borrows the quiet border and keeps its red for hover. The brand and card
   logo slots pair the same two actions, so they get the same treatment: a
   brand-yellow Replace beside a red Remove was two loud buttons arguing. */
.photo-actions { display: flex; justify-content: center; gap: 8px; margin-top: 14px; }
.photo-actions .btn, .logo-slot .actions .btn { border-width: 1px; padding: 0 14px; }
.photo-actions .btn.danger.quiet, .logo-slot .actions .btn.danger.quiet { border-color: var(--border); }
.photo-field input[type=file] { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; }
/* The .vcf chooser: the last raw file input in the console, now behind a
   button like every other upload. Picking the file submits the form. */
.import-field input[type=file] { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; }
.photo-field .field-stack { width: 100%; min-width: 0; text-align: center; }
.photo-field .btn { white-space: nowrap; max-width: 100%; overflow: hidden; text-overflow: ellipsis; }

/* --- Leads (piece 1) --------------------------------------------------------
   The list is the shared .table panel; only its column ruler and the status
   pills belong to this page. The pill colours are the --status-* tokens, so
   no literal lives down here. */
.pill.status-new { color: var(--status-new); background: var(--status-new-tint); border-color: transparent; }
.pill.status-contacted { color: var(--status-contacted); background: var(--status-contacted-tint); border-color: transparent; }
.pill.status-qualified { color: var(--status-qualified); background: var(--status-qualified-tint); border-color: transparent; }
.pill.status-converted { color: var(--status-converted); background: var(--status-converted-tint); border-color: transparent; }
.pill.status-archived { color: var(--status-archived); background: var(--status-archived-tint); border-color: transparent; }
.table.leads .head, .table.leads .row { grid-template-columns: 2fr 1.4fr 1fr 1.4fr 1fr .8fr; }
/* The AI assistant's tables: yours is five columns, the admin's team list six. Both
   were written as real <table>/<thead>/<tr> markup, which cannot work here:
   `.table` is `display: flex` and the columns come from `.head`/`.row` divs, so
   a <thead> and a <tbody> became two flex items that each sized to their own
   content -- the header's rule stopped short and no column lined up. */
.table.tokens .head, .table.tokens .row { grid-template-columns: 2.2fr 1fr 1.3fr 1.3fr auto; }
.table.tokens.team .head, .table.tokens.team .row { grid-template-columns: 2fr 1.6fr 1fr 1.2fr 1.2fr auto; }
.table.audit .head, .table.audit .row { grid-template-columns: 1.3fr 1fr 2.2fr .6fr; }
/* Integrations and follow-ups shipped the same way on the same day. */
.table.hooks .head, .table.hooks .row { grid-template-columns: 2.4fr 1.4fr auto; }
.table.deliveries .head, .table.deliveries .row { grid-template-columns: 1.2fr 1.2fr 2fr .5fr; }
.table.follow-ups .head, .table.follow-ups .row { grid-template-columns: 1.4fr 2.2fr auto; }
/* A row carrying a panel instead of cells (a follow-up's edit disclosure).
   A grid has no colspan, so the row stops being a grid for this one case.
   Declared AFTER the per-table column rules: same specificity, source order
   decides, and a media query would add none of its own. */
.table .row.row-full { display: block; }
/* The token's own prefix: a code fragment inside a name cell, not a column. */
.table.tokens .row code { font-size: 12.5px; }
/* A row whose subject is over: revoked, expired, removed. Dimmed rather than
   dropped, because it is still the evidence that it existed. `is-muted` was
   already on the revoked token rows with no rule behind it anywhere, so a dead
   token was drawn exactly like a live one. */
.table .row.is-muted { color: var(--text-3); }
.table .row.is-muted code { opacity: .7; }
/* One long unbroken string -- a token, a request path -- inside a cell or a
   card. `break` was likewise used and never defined, so the freshly minted
   token ran past the card's padding instead of wrapping. */
.break { overflow-wrap: anywhere; word-break: break-word; min-width: 0; }
.table.leads .cell-tags { display: flex; flex-wrap: wrap; gap: 4px; min-width: 0; }

/* The leads phone rules live HERE, after the six-column rule above, and not in
   the file's main phone block. Both selectors are (0,3,0) and a media query adds
   no specificity, so the block earlier in the file lost on source order alone:
   the leads row kept its desktop grid on a phone and every name wrapped inside
   a 93px column. */
@media (max-width: 767px) {
  .table.leads .row { grid-template-columns: 1fr auto auto; }
}
.toolbar input[type="search"].spacer { flex: 1 1 160px; min-width: 120px; }
/* The lead page: details left, the working panel right (grid-2 exists). */
.lead-facts { display: grid; grid-template-columns: 120px 1fr; gap: 6px 12px; font-size: 14px; }
.lead-facts dt { color: var(--text-2); }
.lead-facts dd { margin: 0; overflow-wrap: anywhere; }
.lead-photo { width: 96px; height: 96px; border-radius: var(--radius-lg); object-fit: cover; }
/* The thumbnail is a button, so it must not look like one: no chrome, and the
   image keeps its own corners. The cursor and the focus ring are what say it
   opens -- a 96px selfie with a border would read as a form field. */
.lead-photo-open {
  display: block; padding: 0; border: 0; background: none;
  border-radius: var(--radius-lg); cursor: zoom-in; line-height: 0;
}
.lead-photo-open:focus-visible { outline: 2px solid var(--btn); outline-offset: 3px; }
/* The photo at full size. `max-height` in viewport units, because the overlay
   the app burns in runs down the image and a tall selfie would otherwise push
   its own Save button off the screen. */
.photo-modal {
  max-width: min(92vw, 640px); padding: 0; border: 0;
  border-radius: var(--radius-lg); background: var(--card); color: var(--text);
  box-shadow: var(--shadow-lg, var(--shadow));
}
.photo-modal::backdrop { background: var(--scrim); }
.photo-modal img { display: block; width: 100%; max-height: 72vh; object-fit: contain; background: var(--bg); }
.photo-modal-actions {
  display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 10px;
  padding: 14px var(--card-pad-x, 20px);
}
.photo-modal-actions form { margin: 0; }
.notes { display: flex; flex-direction: column; gap: 10px; }
.note { padding: 10px 12px; border: 1px solid var(--border-light); border-radius: var(--radius); background: var(--bg); font-size: 14px; }
.note .meta { display: block; font-size: 12px; color: var(--text-3); margin-top: 4px; }
.timeline { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
.timeline li { display: flex; justify-content: space-between; gap: 12px; padding: 8px 0; border-top: 1px solid var(--border-light); font-size: 14px; }
.timeline li .when { color: var(--text-3); white-space: nowrap; font-size: 12px; }
.tag-row { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.tag-row form { display: inline; }
.tag-row .pill button { border: 0; background: none; color: inherit; font: inherit; padding: 0 0 0 6px; cursor: pointer; }
