/* ── Tasqr shared design tokens & components ─────────────────────────────── */
/* Single source of truth for tokens, chrome, and shared UI components.       */
/* Every page links to this — app, portal, homepage, 404, docs and marketing
   layouts — and auth serves the same file from its own origin; only
   page-specific styles go inline. */

/* ── Reset ── */
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth}

/* ── Design tokens ── */
/* Signal design tokens — flat matte palette (flat, clean, sharp).
   Dark is the default; light follows the OS. Force either theme with
   data-theme="dark|light" on <html> (or any subtree root, e.g. <body>). */
:root{
  color-scheme:dark;
  /* Surfaces */
  --bg:#08090B;
  --surface:#101116;
  --surface2:#181A20;
  --deep:#0B0C10;
  /* Text */
  --ink:#F4F5F7;
  --muted:#A2A6AF;
  /* Brand */
  --accent:#4F89DC;
  --on-accent:#08090B;
  /* Semantic */
  --mint:#1E9D70;
  --amber:#B78E25;
  --warn:#CD645B;
  --violet:#8E78D7;
  /* Task status colors */
  --pending:#4A87D9;
  --in-progress:#B78E25;
  --blocked:#CD645B;
  --paused:#7E828C;
  --completed:#1E9D70;
  --failed:#CF616E;
  --canceled:#3A3D45;
  /* Borders */
  --line:#2E3138;
  --line2:#3B3F48;
  /* Controls */
  --select-chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3 4.5 6 8 9 4.5' fill='none' stroke='%23A2A6AF' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  /* Layout */
  --sidebar-w:220px;
  --topnav-h:60px;
  /* One height for every solid-fill label chip (role badge, early-access tag) so
     they read as the same component at different font sizes. */
  --badge-h:23px;
}
/* Light — OS preference (a forced data-theme below wins) */
@media (prefers-color-scheme: light){
  :root{
    color-scheme:light;
    --bg:#FFFFFF;
    --surface:#F4F4F6;
    --surface2:#EAEBEE;
    --deep:#F0F0F3;
    --ink:#17191D;
    --muted:#565B66;
    --accent:#326DC1;
    --on-accent:#FFFFFF;
    --mint:#008458;
    --amber:#8F6800;
    --warn:#B84A42;
    --violet:#6F5FC3;
    --pending:#3065BA;
    --in-progress:#8F6800;
    --blocked:#B84A42;
    --paused:#6B7078;
    --completed:#008458;
    --failed:#A62F3F;
    --canceled:#B4B8C0;
    --line:#DCDEE3;
    --line2:#C6C9D0;
    --select-chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3 4.5 6 8 9 4.5' fill='none' stroke='%23565B66' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  }
}
/* Light — forced */
[data-theme="light"]{
  color-scheme:light;
  --bg:#FFFFFF;
  --surface:#F4F4F6;
  --surface2:#EAEBEE;
  --deep:#F0F0F3;
  --ink:#17191D;
  --muted:#565B66;
  --accent:#326DC1;
  --on-accent:#FFFFFF;
  --mint:#008458;
  --amber:#8F6800;
  --warn:#B84A42;
  --violet:#6F5FC3;
  --pending:#3065BA;
  --in-progress:#8F6800;
  --blocked:#B84A42;
  --paused:#6B7078;
  --completed:#008458;
  --failed:#A62F3F;
  --canceled:#B4B8C0;
  --line:#DCDEE3;
  --line2:#C6C9D0;
  --select-chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3 4.5 6 8 9 4.5' fill='none' stroke='%23565B66' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
/* Dark — forced */
[data-theme="dark"]{
  color-scheme:dark;
  --bg:#08090B;
  --surface:#101116;
  --surface2:#181A20;
  --deep:#0B0C10;
  --ink:#F4F5F7;
  --muted:#A2A6AF;
  --accent:#4F89DC;
  --on-accent:#08090B;
  --mint:#1E9D70;
  --amber:#B78E25;
  --warn:#CD645B;
  --violet:#8E78D7;
  --pending:#4A87D9;
  --in-progress:#B78E25;
  --blocked:#CD645B;
  --paused:#7E828C;
  --completed:#1E9D70;
  --failed:#CF616E;
  --canceled:#3A3D45;
  --line:#2E3138;
  --line2:#3B3F48;
  --select-chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3 4.5 6 8 9 4.5' fill='none' stroke='%23A2A6AF' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}

/* ── Base ── */
/* `overflow-x:hidden`, and deliberately NOT `clip`, on body alone.
   The distinction matters twice over. Putting any overflow on `html` stops
   body's overflow propagating to the viewport, and that propagation is the
   entire mechanism by which `body.panel-open{overflow:hidden}` locks the page
   behind an open panel — move it and a drag inside the panel scrolls the page.
   And `clip` on the root propagates as clip, which leaves the viewport with no
   scrollable overflow region in EITHER axis: measured at 8521px of content in a
   900px viewport with scrollTop refusing to move at all.
   `clip` was introduced to stop iOS panning the scrollport that `hidden`
   leaves. That panning turned out to be pinch-zoom, which no overflow value
   affects. browser.test.js covers both directions now: the page must scroll
   when idle and must not while an overlay is open. */
body{background:var(--bg);color:var(--ink);font-family:'IBM Plex Sans',sans-serif;font-size:14px;line-height:1.5;min-height:100vh;overflow-x:hidden}
/* Inline formatting rendered from customer/agent-authored prose (task text, standup
   and runbook reports) — see escFormat()/appendFormattedText() in common.js. strong/em
   fall back to the UA default (bold/italic); code gets a chip to match the mono UI. */
code{font-family:'IBM Plex Mono',monospace;font-size:0.92em;background:var(--surface);border:1px solid var(--line);border-radius:2px;padding:1px 5px}
.hidden{display:none!important}
.muted{color:var(--muted);font-size:13px}
.divider{height:1px;background:var(--line);margin:32px 0}

/* ── Touch: controls never smaller than 16px ── */
/* iOS Safari zooms the visual viewport whenever a control with a computed font
   below 16px takes focus, and once zoomed the page pans horizontally no matter
   what overflow says — on every section, until the page is reloaded. It reads
   exactly like a layout bug, and no amount of clipping fixes it, because the
   document is not what moved.
   The other way to stop it is maximum-scale=1 on the viewport meta, which works
   by disabling pinch-zoom for everyone. That fails WCAG 1.4.4, so it is not an
   option; sizing the controls is.
   `!important` is deliberate and is the one place in this file that earns it:
   the rule has to beat page-level selectors like `.board-toolbar input`, which
   live in app.html's own <style> and therefore come later in the cascade than
   anything here. Listing all nine of them instead would put the fix back in two
   files and rot the moment a tenth is added.
   Scoped to `pointer:coarse` rather than a width, because the trigger is the
   input method, not the screen — a touch laptop zooms too. */
@media (pointer:coarse){
  input,select,textarea{font-size:16px!important}
}

/* ── Native form controls ── */
/* A <select>'s OPEN dropdown list is drawn by the UA, not by page CSS, unless
   base-select is available (below). Without it these two rules are as close as
   the popup gets: color-scheme (which the token blocks declare) is what makes
   the platform draw its menu in the right theme, and the option colors reach
   the list on Windows and Linux. macOS ignores the option colors entirely --
   an AppKit menu there follows only the light/dark scheme. That ceiling applies
   to the open list alone; the closed control is fully ours either way. */
select, option, optgroup { color-scheme: inherit }
option, optgroup { background-color: var(--surface); color: var(--ink) }

/* Only Chromium has base-select today. Everywhere else -- Safari and Firefox --
   an `appearance: auto` <select> is drawn by the platform: macOS paints an
   AppKit menulist that ignores background, border, radius and font, so every
   dropdown in the app reads off-brand however well the class around it is
   tokenized. So the BASE rule opts out of the native control and draws the
   affordance ourselves; the @supports block below hands it back to base-select
   where that exists. Keep this order -- the fallback has to be the default,
   because it is the one most browsers get.
   `--select-pad-x` is the horizontal padding the select's own class sets. The
   fallback needs a wider right gutter so the label never runs under the
   chevron; base-select supplies its own icon and gap, so it puts the symmetric
   value back. A class that styles a select therefore sets `padding-block` and
   `padding-left` plus `--select-pad-x` -- never a `padding` shorthand, which
   would outrank the gutter below, and never a `background` shorthand, which
   would wipe the chevron. */
select {
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
  padding-right: calc(var(--select-pad-x, 10px) + 20px);
  background-image: var(--select-chevron);
  background-repeat: no-repeat;
  background-position: right var(--select-pad-x, 10px) center;
  background-size: 12px 12px;
}

/* appearance:none also drops the platform's gray-out for a disabled select, so
   restate it -- same treatment the disabled buttons get. Chromium's base-select
   does not draw one either, so this covers both paths. */
select:disabled { opacity: 0.5; cursor: not-allowed }

@supports (appearance: base-select) {
  select, ::picker(select) { appearance: base-select }
  /* base-select turns the select into a flex container. Its default
     align-items leaves the picker icon riding at the top of the line box, so
     center it explicitly and let the label take the free space. It also draws
     its own ::picker-icon, so the fallback chevron and its gutter come back off. */
  select {
    display: inline-flex;
    align-items: center;
    gap: 14px;
    padding-right: var(--select-pad-x, 10px);
    background-image: none;
  }
  /* The UA icon is a solid triangle; Signal uses a thin stroked chevron. Drawn
     as a mask so it inherits a token color rather than baking one into the
     data URI. */
  /* Chevron artwork for the picker icon. MASK ONLY -- the stroke is black
     because a mask uses alpha, so the color comes from the element. As a
     plain background-image this paints black and vanishes on dark. */
  select::picker-icon {
    --chevron:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3 4.5 6 8 9 4.5' fill='none' stroke='black' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    color: transparent;
    width: 12px;
    height: 12px;
    background-color: var(--muted);
    -webkit-mask: var(--chevron) center / 12px 12px no-repeat;
    mask: var(--chevron) center / 12px 12px no-repeat;
    transition: transform .12s;
  }
  select:open::picker-icon { transform: rotate(180deg) }
  ::picker(select) {
    background: var(--surface);
    border: 1px solid var(--line2);
    border-radius: 0;
    padding: 0;
    margin-top: 2px;
    box-shadow: none;
  }
  /* Not nested under ::picker(): option padding and ::checkmark both live on
     the option itself. Without the padding the labels sit flush against the
     edge once the checkmark column is gone. */
  option {
    padding: 7px 14px;
    font-family: 'IBM Plex Mono', monospace;
    font-size: 12px;
    color: var(--ink);
    background: var(--surface);
    border-radius: 0;
  }
  option:hover { background: color-mix(in oklab, var(--ink) 6%, var(--surface)) }
  option:checked { background: color-mix(in oklab, var(--accent) 18%, var(--surface)) }
  /* The UA draws a checkmark column that indents every label. ::checkmark is a
     pseudo-element on option itself -- nesting it under ::picker() does not
     reach it. Selection is shown by the :checked fill above instead. */
  option::checkmark { display: none }
}

/* One brand focus ring. The UA default is a blue that belongs to no theme, and
   the rules that set only border-color on :focus left it drawing on top. Use
   :focus-visible so it appears for keyboard users and not on mouse clicks --
   never remove it outright. */
select:focus-visible, input:focus-visible, textarea:focus-visible,
button:focus-visible, a:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* ── Status indicator ── */
/* A status is read-only state, so it is an indicator, not a chip: a square dot
   in the status color plus a mono label, no fill. Matches .seat-static and the
   board's .col-dot, and the design's cards/app-page-*.html. statusBadge() in
   common.js (dashboard, portal) and the homepage's inline
   stream script both emit this markup -- so the rule lives here, not in one
   page's inline CSS. */
.status-badge{
  display:inline-flex;align-items:center;gap:7px;white-space:nowrap;
  font-family:'IBM Plex Mono',monospace;font-size:11px;color:var(--ink);
}
.status-dot{width:8px;height:8px;flex-shrink:0}

/* ── Top nav ── */
#topnav{
  position:fixed;top:0;left:0;right:0;height:var(--topnav-h);
  background:var(--bg);border-bottom:1px solid var(--line);
  display:flex;align-items:center;gap:16px;padding:0 20px;z-index:100;
}
.brand{font-family:'Hanken Grotesk',sans-serif;font-weight:700;font-size:20px;letter-spacing:-0.04em;color:var(--ink);text-decoration:none}
.brand .q{color:var(--accent)}
.topnav-sep{width:1px;height:20px;background:var(--line);flex-shrink:0}
#org-slug{font-family:'IBM Plex Mono',monospace;font-size:13px;color:var(--muted)}
#tier-badge{
  padding:3px 8px;border-radius:2px;font-size:11px;font-weight:600;letter-spacing:0.06em;text-transform:uppercase;
  background:color-mix(in oklab, var(--ink) 6%, var(--bg));color:var(--muted);border:1px solid var(--line2);
}
.topnav-right{margin-left:auto;display:flex;align-items:center;gap:12px}
#org-email{font-size:12px;color:var(--muted)}
#signout-btn{
  padding:6px 14px;background:transparent;border:1px solid var(--line2);border-radius:2px;
  color:var(--muted);font-family:'IBM Plex Mono',monospace;font-size:12px;cursor:pointer;transition:all .15s;
}
#signout-btn:hover{border-color:var(--accent);color:var(--accent)}

/* ── Mobile nav toggle + scrim ── */
/* Both are inert above the drawer breakpoint; the responsive block at the foot
   of this file is what turns them on. Declared here, with the rest of the top
   nav, because that is where the button lives in the markup. */
#mobile-nav-toggle{
  display:none;flex:none;width:36px;height:36px;margin-left:-6px;
  align-items:center;justify-content:center;
  background:transparent;border:1px solid transparent;border-radius:2px;
  color:var(--ink);cursor:pointer;transition:border-color .12s,color .12s;
}
#mobile-nav-toggle:hover{border-color:var(--line2)}
#mobile-nav-toggle[aria-expanded="true"]{border-color:var(--accent);color:var(--accent)}
#mobile-nav-toggle svg{width:18px;height:18px;display:block}
#nav-scrim{display:none}
/* Identity at the head of the drawer. Inert while the sidebar is a sidebar —
   the top nav carries the email there. */
.sidebar-identity{display:none}

/* ── Layout ── */
.layout{display:flex;padding-top:var(--topnav-h);min-height:100vh}

/* ── Sidebar ── */
#sidebar{
  width:var(--sidebar-w);flex-shrink:0;
  border-right:1px solid var(--line);
  padding:24px 0;position:fixed;top:var(--topnav-h);bottom:0;overflow-y:auto;
  /* Every overlay here is a position:fixed scroller sitting over a scrollable
     page. Without this, a drag that reaches the overlay's scroll end — or a
     drag in an overlay whose content is too short to scroll at all — chains
     through to the document and scrolls the page behind it. `contain` keeps
     the gesture in the overlay. */
  overscroll-behavior:contain;
  display:flex;flex-direction:column;
}
.sidebar-section{margin-bottom:8px}
.sidebar-label{
  padding:4px 20px;font-size:10px;letter-spacing:0.12em;text-transform:uppercase;
  color:var(--muted);font-family:'IBM Plex Mono',monospace;
}
.nav-item{
  display:flex;align-items:center;gap:10px;
  padding:9px 20px;color:var(--muted);text-decoration:none;font-size:13px;font-weight:500;
  transition:all .12s;cursor:pointer;border:none;background:none;width:100%;text-align:left;
}
.nav-item:hover{color:var(--ink);background:color-mix(in oklab, var(--ink) 4%, var(--bg))}
.nav-item.active{color:var(--accent);background:color-mix(in oklab, var(--accent) 10%, var(--bg))}
.nav-item .nav-icon{width:16px;text-align:center;flex-shrink:0}
.sidebar-bottom{margin-top:auto;padding-top:8px;border-top:1px solid var(--line)}
.nav-badge{
  margin-left:auto;padding:1px 4px;border-radius:2px;font-size:9px;min-width:14px;text-align:center;line-height:1.4;
  font-family:'IBM Plex Mono',monospace;background:var(--warn);color:var(--bg);
}
.nav-badge.zero{background:var(--surface2);color:var(--muted)}

/* ── Main content ── */
/* min-width:0 is load-bearing, not tidying. #main is a flex item, and a flex
   item's default `min-width:auto` refuses to shrink below its content's
   min-content width — so one unbreakable child sets a floor for the entire
   main column. A single nowrap assignee email in the Recent Tasks table
   (`.task-assignee`, ~200px that cannot wrap) dragged #main to 505px on a
   375pt phone, laying out every card 130px too wide; `body{overflow-x:hidden}`
   then clipped the excess with no scrollbar to reach it. Wide children must
   scroll inside their own container (`.scroll-x`) instead of widening
   this one. */
#main{margin-left:var(--sidebar-w);flex:1;min-width:0;padding:28px 32px;min-height:calc(100vh - var(--topnav-h))}

/* The one primitive for content that cannot shrink to fit: tables (min-content
   is the sum of their columns), charts (a bar per day), anything with a fixed
   track. Give it its own horizontal scroll box and it stays reachable instead
   of widening its ancestors until something clips it. overflow-y stays hidden
   so this never adds a vertical scrollbar of its own, and overscroll-behavior
   keeps a sideways drag from turning into a page gesture.
   Named for what it does, not what first needed it — it started as
   .table-scroll and immediately had to cover the usage chart. */
.scroll-x{overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain}
.page-header{margin-bottom:24px}
.page-title{font-family:'Hanken Grotesk',sans-serif;font-size:22px;font-weight:700;letter-spacing:-0.02em}
.page-sub{color:var(--muted);font-size:13px;margin-top:4px}

/* ── Role badges ── */
/* inline-flex + a fixed height, not padding around a line box: the badge's text
   is then centered by the box rather than by the font's ascender/descender, which
   is what left "Owner" sitting low in its fill. */
.role-badge{display:inline-flex;align-items:center;height:var(--badge-h);padding:0 8px;border-radius:2px;font-family:'IBM Plex Mono',monospace;font-size:11px;font-weight:600;line-height:1;letter-spacing:0.04em;text-transform:capitalize}
.role-badge.owner{background:var(--violet);color:var(--on-accent)}
.role-badge.admin{background:var(--accent);color:var(--on-accent)}
.role-badge.user{background:color-mix(in oklab, var(--ink) 5%, var(--bg));color:var(--muted);border:1px solid var(--line)}

/* ── Profile meta card ── */
/* Sized to its contents (width:fit-content), not to a fixed column: two rows of
   label + value looked stranded inside a 480px box, with the value pinned to a
   far edge. Labels share one column so the values line up under each other. */
.profile-meta{background:var(--surface);border:1px solid var(--line);border-radius:0;padding:2px 20px;margin-bottom:24px;width:fit-content;max-width:100%}
.meta-row{display:flex;align-items:center;gap:28px;padding:10px 0}
.meta-row:not(:last-child){border-bottom:1px solid var(--line)}
.meta-row .label{flex:0 0 auto;min-width:52px;font-size:11px;letter-spacing:0.08em;text-transform:uppercase;color:var(--muted)}
.meta-row .value{min-width:0;overflow-wrap:anywhere;font-family:'IBM Plex Mono',monospace;font-size:13px}

/* ── Tag pill checkboxes (profile tag selection) ── */
.tag-checklist{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:16px;max-width:560px}
.tag-check{display:flex;align-items:center;gap:6px;padding:6px 12px;background:var(--surface);border:1px solid var(--line);border-radius:2px;cursor:pointer;font-size:12px;color:var(--muted);transition:all .12s;user-select:none}
.tag-check:hover{border-color:var(--line2);color:var(--ink)}
.tag-check input[type=checkbox]{position:absolute;opacity:0;width:0;height:0}
.tag-check:hover .toggle-box{border-color:var(--accent)}
.tag-check input[type=checkbox]:checked+.toggle-box{background:var(--accent);border-color:var(--accent)}
.tag-check input[type=checkbox]:checked+.toggle-box::after{content:'✓'}
.tag-check:has(input:checked){color:var(--accent);background:color-mix(in oklab, var(--accent) 10%, var(--bg));border-color:color-mix(in oklab, var(--accent) 32%, var(--bg))}
.strict-tag-badge{font-size:12px;padding:6px 12px;border-radius:2px;background:var(--surface);color:var(--muted);border:1px solid var(--line);opacity:0.7;cursor:default}
.no-tags-msg{font-size:12px;color:var(--muted);padding:8px 0}

/* ── Tag icons ── */
/* Glyph icons on tag pills. ◆ and ⊘ both sit low and slightly left of their
   advance width at this size, so they read as misaligned next to the label.
   Nudge optically rather than trusting the font's metrics. */
.strict-icon,.default-icon{
  font-size:10px;display:inline-block;vertical-align:1.5px;margin-left:1px;
}
.strict-icon{color:var(--amber)}
.default-icon{color:var(--mint)}

/* ── Canonical checkbox (settings forms, create forms) ── */
/* Hides the native input; renders a custom box that inherits the brand style. */
.toggle-row{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--muted);cursor:pointer;white-space:nowrap;user-select:none}
.toggle-row input[type=checkbox]{position:absolute;opacity:0;width:0;height:0}
.toggle-box{width:14px;height:14px;border:1px solid var(--line2);border-radius:2px;background:var(--bg);display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;transition:all .12s;font-size:10px;color:var(--on-accent)}
.toggle-row:hover .toggle-box{border-color:var(--accent)}
.toggle-row input[type=checkbox]:checked+.toggle-box{background:var(--accent);border-color:var(--accent)}
.toggle-row input[type=checkbox]:checked+.toggle-box::after{content:'✓'}

/* ── Settings section ── */
.settings-block{max-width:520px;margin-bottom:36px}
.settings-section{max-width:560px;margin-top:8px}
.settings-title{font-family:'Hanken Grotesk',sans-serif;font-size:18px;font-weight:600;letter-spacing:-0.02em;margin-bottom:4px}
.settings-sub{font-size:13px;color:var(--muted);margin-bottom:20px}
.settings-label{font-size:11px;letter-spacing:0.1em;text-transform:uppercase;color:var(--muted);margin-bottom:6px}
.section-header{display:flex;align-items:center;gap:10px;margin-bottom:6px}
.section-header h3{font-family:'Hanken Grotesk',sans-serif;font-size:15px;font-weight:600;letter-spacing:-0.01em}
.section-desc{font-size:12px;color:var(--muted);margin-bottom:14px}
.key-placeholder{background:var(--surface);border:1px solid var(--line);border-radius:2px;padding:12px 16px;font-family:'IBM Plex Mono',monospace;font-size:13px;color:var(--muted);letter-spacing:0.08em;margin-bottom:12px}
/* One line in both states — a key split across three lines reads as broken
   rather than as a value. What differs is what happens to the overflow.
   Masked, it is a run of bullets carrying no information, so it simply clips at
   the edge; there is nothing there worth scrolling to.
   Revealed, it is the real key, shown once, so the box scrolls horizontally and
   the whole value stays reachable and selectable — the clipboard fallback tells
   the person to select it by hand. Copying is unaffected either way: copyKey
   reads textContent, not what is on screen.
   Both are display:block with their own overflow, so the containment happens
   here rather than being left to an ancestor to clip. */
#key-value{display:block;white-space:nowrap;overflow:hidden}
#key-value.revealed{overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;color:var(--mint);font-size:12px}
#reset-key-result{display:none;margin-top:16px;background:var(--surface);border:1px solid var(--line);border-radius:2px;padding:16px}
.new-key-label{font-size:11px;letter-spacing:0.1em;text-transform:uppercase;color:var(--muted);margin-bottom:6px}
#reset-key-value{font-family:'IBM Plex Mono',monospace;font-size:12px;color:var(--mint);word-break:break-all;margin-bottom:10px}
.key-warn{font-size:11px;color:var(--amber);margin-bottom:10px}
#reset-key-error{display:none;margin-top:10px;font-size:12px;color:var(--warn)}

/* ── Buttons ── */
.btn-primary{padding:6px 14px;background:var(--accent);color:var(--on-accent);border:none;border-radius:2px;font-family:'IBM Plex Mono',monospace;font-size:11px;font-weight:600;cursor:pointer;transition:filter .12s}
.btn-primary:hover{filter:brightness(1.1)}
.btn-secondary{padding:6px 14px;background:var(--surface2);color:var(--muted);border:1px solid var(--line);border-radius:2px;font-family:'IBM Plex Mono',monospace;font-size:11px;cursor:pointer;transition:all .12s}
.btn-secondary:hover{border-color:var(--line2);color:var(--ink)}
.btn-icon{background:none;border:1px solid var(--line);border-radius:2px;color:var(--muted);cursor:pointer;font-size:14px;padding:3px 7px;transition:all .12s;font-family:inherit}
.btn-icon:hover{border-color:var(--line2);color:var(--ink)}
.btn-icon.danger:hover{border-color:var(--warn);color:var(--warn)}
.save-btn,.profile-save-btn{padding:9px 20px;background:var(--accent);color:var(--on-accent);border:none;border-radius:2px;font-family:'IBM Plex Mono',monospace;font-size:12px;font-weight:600;cursor:pointer;transition:filter .15s}
.save-btn:hover,.profile-save-btn:hover{filter:brightness(1.1)}
.save-btn:disabled,.profile-save-btn:disabled{opacity:0.5;cursor:not-allowed}
.reset-btn{padding:10px 20px;background:transparent;border:1px solid var(--line2);border-radius:2px;color:var(--muted);font-family:'IBM Plex Mono',monospace;font-size:12px;font-weight:600;cursor:pointer;transition:all .15s}
.reset-btn:hover{border-color:var(--warn);color:var(--warn)}
.reset-btn:disabled{opacity:0.5;cursor:not-allowed}
.copy-btn{padding:7px 14px;background:transparent;border:1px solid var(--line2);border-radius:2px;color:var(--muted);font-family:'IBM Plex Mono',monospace;font-size:11px;cursor:pointer;transition:all .15s}
.copy-btn:hover{border-color:var(--accent);color:var(--accent)}
.overlay-btn{display:inline-block;padding:12px 24px;background:var(--accent);color:var(--on-accent);font-family:'IBM Plex Mono',monospace;font-size:13px;font-weight:600;border-radius:2px;text-decoration:none;border:none;cursor:pointer;transition:filter .15s}
.overlay-btn:hover{filter:brightness(1.1)}

/* ── Data table ── */
.data-table{width:100%;border-collapse:collapse}
.data-table th{text-align:left;padding:10px 14px;font-size:11px;letter-spacing:0.08em;text-transform:uppercase;color:var(--muted);border-bottom:1px solid var(--line);font-weight:600}
.data-table td{padding:12px 14px;border-bottom:1px solid var(--line);font-size:13px}
.data-table tr:last-child td{border-bottom:none}
.data-table tr:hover td{background:color-mix(in oklab, var(--ink) 2%, var(--bg))}
.timestamp{font-family:'IBM Plex Mono',monospace;font-size:11px;color:var(--muted)}
/* An assignee is a machine value, not a chip: plain mono muted text (the
   dashboard, the homepage stream and the kanban cards all render it this way). */
.task-assignee{font-family:'IBM Plex Mono',monospace;font-size:11px;color:var(--muted);white-space:nowrap}
.table-empty{padding:40px 0;text-align:center;color:var(--muted);font-size:13px}

/* ── Panel meta rows ── */
/* Key/value rows in the task and org detail panels; teams.js also emits
   .panel-meta-key inside team rows on both the dashboard and the portal. */
.panel-meta-row{display:flex;justify-content:space-between;align-items:center;padding:6px 0}
.panel-meta-key{font-size:12px;color:var(--muted)}
.panel-meta-val{font-family:'IBM Plex Mono',monospace;font-size:12px}

/* ── Tag & team management ── */
/* One component, two consumers: teams.js renders identical markup on the
   dashboard and the portal. These rules lived as a copy in each page's inline
   CSS and the copies drifted — the portal's was missing the .expandable /
   .expanded rules teams.js emits. The component lives here so it can't fork. */
.tag-toolbar{display:flex;align-items:center;gap:10px;margin-bottom:12px}
.tag-search{flex:1;padding:7px 12px;background:var(--surface);border:1px solid var(--line);border-radius:2px;color:var(--ink);font-family:'IBM Plex Mono',monospace;font-size:12px;outline:none}
.tag-search:focus{border-color:var(--accent)}
.tag-search::placeholder{color:var(--muted)}
.btn-add-tag{display:flex;align-items:center;gap:5px;padding:7px 14px;background:var(--accent);color:var(--on-accent);border:none;border-radius:2px;font-family:'IBM Plex Mono',monospace;font-size:12px;font-weight:600;cursor:pointer;transition:filter .12s;white-space:nowrap}
.btn-add-tag:hover{filter:brightness(1.1)}
.tag-create-inline{display:flex;align-items:center;flex-wrap:wrap;gap:10px;padding:10px 14px;background:var(--surface);border:1px solid var(--accent);border-radius:0;margin-bottom:8px}
.tag-create-inline input[type=text]{flex:1;min-width:140px;padding:6px 10px;background:var(--bg);border:1px solid var(--line);border-radius:2px;color:var(--ink);font-family:'IBM Plex Mono',monospace;font-size:12px;outline:none}
.tag-create-inline input[type=text]:focus{border-color:var(--accent)}
/* Same reasoning as .tag-desc-input: the description needs a line of its own. `order`
   keeps the name + toggles on line 1 and the buttons on line 3, rather than letting the
   100% basis shove everything after it down a line. */
.tag-create-inline #new-tag-description,.tag-create-inline #new-team-description{order:1;flex:1 1 100%;min-width:0}
.tag-create-inline .create-actions{order:2;display:flex;gap:8px;margin-left:auto;align-items:center}
.tag-create-inline button.confirm{padding:6px 14px;background:var(--accent);color:var(--on-accent);border:none;border-radius:2px;font-family:'IBM Plex Mono',monospace;font-size:12px;font-weight:600;cursor:pointer}
.tag-create-inline button.cancel{padding:6px 10px;background:none;color:var(--muted);border:1px solid var(--line);border-radius:2px;font-family:'IBM Plex Mono',monospace;font-size:12px;cursor:pointer}
.tag-create-inline button.cancel:hover{border-color:var(--muted);color:var(--ink)}
.tag-grid{display:flex;flex-direction:column;gap:8px}
/* Every child is top-aligned against a fixed 24px first line, and each one's own
   height + margin is state-independent. Expanding only adds line boxes under the
   description, so the pill, badges and buttons never move. */
.tag-row{display:flex;flex-wrap:wrap;align-items:flex-start;gap:10px;padding:10px 14px;background:var(--surface);border:1px solid var(--line);border-radius:0;transition:border-color .12s}
.tag-row.expandable{cursor:pointer}
.tag-row.expandable:hover{border-color:var(--line2)}
.tag-row.hidden-by-search{display:none}
.tag-pill{font-family:'IBM Plex Mono',monospace;font-size:12px;line-height:16px;padding:3px 8px;margin-top:1px;border-radius:2px;background:var(--accent);color:var(--on-accent);white-space:nowrap}
.tag-desc{flex:1;min-width:0;font-size:12px;line-height:24px;color:var(--muted);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tag-row.expanded .tag-desc{overflow:visible;text-overflow:clip;white-space:normal}
.tag-desc.empty{opacity:0.5;font-style:italic}
/* The editor takes a full-width line of its own below the badges and buttons: a 200-char
   description is unreadable in the ~400px the description column leaves between them.
   `order:1` sorts it after the buttons, so the wrap puts it on line 2 rather than pushing
   the buttons down to line 3. */
.tag-desc-input{order:1;flex:1 1 100%;min-width:0;margin-top:2px;padding:6px 10px;background:var(--bg);border:1px solid var(--accent);border-radius:2px;color:var(--ink);font-size:12px;outline:none}
.strict-badge,.default-badge{font-family:'IBM Plex Mono',monospace;font-size:10px;line-height:14px;padding:2px 6px;margin-top:2px;border-radius:2px;font-weight:600;letter-spacing:0.06em;text-transform:uppercase}
.strict-badge.on{background:var(--amber);color:var(--on-accent);border:1px solid var(--amber)}
.strict-badge.off{background:color-mix(in oklab, var(--ink) 5%, var(--bg));color:var(--muted);border:1px solid var(--line)}
.default-badge.on{background:var(--mint);color:var(--on-accent);border:1px solid var(--mint)}
.default-badge.off{display:none}
/* 14px glyph + 16px line box + 3px padding + 1px border = the 24px first line. */
.tag-row .btn-icon{line-height:16px}
/* One flex item holding all four, so they wrap together. As individual items
   they broke up — a lone × on its own row reads as a separate control. */
.tag-actions{display:flex;align-items:center;gap:10px;margin-left:auto;flex:none}
.toggle-strict-btn.active{color:var(--amber);border-color:color-mix(in oklab, var(--amber) 35%, var(--bg))}
.toggle-default-btn.active{color:var(--mint);border-color:color-mix(in oklab, var(--mint) 35%, var(--bg))}
/* Hover previews the state the button sets, the way .btn-icon.danger:hover does. */
.toggle-strict-btn:hover{color:var(--amber);border-color:var(--amber)}
.toggle-default-btn:hover{color:var(--mint);border-color:var(--mint)}
.tag-count-badge{padding:2px 8px;border-radius:2px;font-family:'IBM Plex Mono',monospace;font-size:10px;background:color-mix(in oklab, var(--ink) 5%, var(--bg));color:var(--muted);border:1px solid var(--line)}
/* In a team row it shares a line with the icon buttons, so it takes their box:
   16px line box + 3px padding + 1px border = the same 24px. */
.tag-row .tag-count-badge{line-height:16px;padding:3px 8px}
.idp-only-badge{font-family:'IBM Plex Mono',monospace;font-size:10px;line-height:14px;padding:2px 6px;margin-top:2px;border-radius:2px;font-weight:600;letter-spacing:0.06em;text-transform:uppercase;background:var(--violet);color:var(--on-accent);border:1px solid var(--violet)}
.toggle-idp-only-btn.active{color:var(--violet);border-color:color-mix(in oklab, var(--violet) 35%, var(--bg))}
.toggle-idp-only-btn:hover{color:var(--violet);border-color:var(--violet)}
/* Full-width panel below the row's first line, same trick as .tag-desc-input:
   order:2 (after every other flex child) sorts it onto its own line. */
.team-expanded{order:2;flex:1 1 100%;margin-top:10px;padding-top:10px;border-top:1px solid var(--line);display:flex;flex-direction:column;gap:12px;cursor:default}
.team-bundle-row{display:flex;flex-wrap:wrap;align-items:center;gap:6px}
.team-tag-chips{display:flex;flex-wrap:wrap;gap:6px}
/* The bundle chips sit beside the add-tag select, so they take its box: a 16px
   line box + 5px padding on the chip is the select's 16 + 4 + its 1px border. */
.team-bundle-row .tag-pill{padding-block:5px;margin-top:0}
/* The label centers on the two boxes, not on the chip's baseline: "Tags"
   hangs a descender, so a baseline-matched label reads low against a filled
   chip. A 1px lift puts the word's ink block on the boxes' center line. */
.team-bundle-row .panel-meta-key{position:relative;top:-1px}
.removable-chip{cursor:pointer;transition:all .12s}
.removable-chip:hover{background:var(--warn);color:var(--on-accent)}
/* padding-block + padding-left + --select-pad-x, never the shorthand: the
   shared `select` rule owns padding-right so it can widen the gutter for the
   fallback chevron and drop it again under base-select. background-COLOR, not
   the shorthand, for the same reason -- the shorthand would wipe the chevron. */
.team-tag-add-select{--select-pad-x:8px;padding-block:4px;padding-left:8px;line-height:16px;background-color:var(--surface2);border:1px solid var(--line2);border-radius:2px;color:var(--ink);font-family:'IBM Plex Mono',monospace;font-size:11px;outline:none}
.team-tag-add-select:focus{border-color:var(--accent)}
.team-members-title{font-size:10px;letter-spacing:0.1em;text-transform:uppercase;color:var(--muted);margin-bottom:6px}
.team-member-row{display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--line)}
.team-member-row:last-of-type{border-bottom:none}
.team-member-email{flex:1;min-width:0;font-family:'IBM Plex Mono',monospace;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.source-badges{display:flex;gap:4px;flex-shrink:0}
.source-badge{font-family:'IBM Plex Mono',monospace;font-size:9px;padding:1px 6px;border-radius:2px;text-transform:uppercase;letter-spacing:0.05em;font-weight:600}
.source-badge.manual{background:var(--accent);color:var(--on-accent)}
.source-badge.idp{background:var(--violet);color:var(--on-accent)}
.team-add-member-row{display:flex;gap:8px;margin-top:8px}
.team-add-member-input{flex:1;min-width:0;padding:6px 10px;background:var(--bg);border:1px solid var(--line);border-radius:2px;color:var(--ink);font-family:'IBM Plex Mono',monospace;font-size:12px;outline:none}
.team-add-member-input:focus{border-color:var(--accent)}
.member-team-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}

/* ── Prose code (docs + marketing sub-pages) ── */
/* The docs layout's .content and the marketing layout's .page both carry
   `prose`; their inline/pre code styling was a byte-identical copy in each. */
/* `anywhere`, not `break-word`: only `anywhere` shrinks the element's
   min-content width, and it is the min-content width of an unbreakable token
   that widens a page. A redirect URI in the SSO docs laid /docs/enterprise out
   at 440px inside a 390px phone, which body{overflow-x:hidden} turns into
   content that pinch-zoom reaches and scrolling does not. Inside <pre> this is
   inert — white-space:pre refuses to wrap at all — so the code blocks keep
   their own horizontal scroller. */
.prose code{font-family:'IBM Plex Mono',monospace;font-size:13px;background:var(--surface);border:1px solid var(--line);padding:2px 6px;border-radius:2px;color:var(--mint);overflow-wrap:anywhere}
.prose pre{background:var(--surface);border:1px solid var(--line);border-radius:0;padding:20px 24px;overflow-x:auto;margin:18px 0 24px;position:relative}
.prose pre code{background:none;border:none;padding:0;font-size:13px;line-height:1.65;color:var(--ink)}

/* ── Docs / marketing table skeleton ── */
/* .cmp (marketing sub-pages), .feat-table and .price-table (docs) share one
   skeleton: mono uppercase headers, --line row borders, borderless last row.
   Each layout keeps only its deltas (margins, paddings, first-column face). */
.cmp,.feat-table,.price-table{width:100%;border-collapse:collapse;font-size:14px}
.cmp th,.feat-table th,.price-table th{font-family:'IBM Plex Mono',monospace;font-size:10px;letter-spacing:0.15em;text-transform:uppercase;color:var(--muted);text-align:left;padding:0 12px 10px;border-bottom:1px solid var(--line)}
.cmp td,.feat-table td,.price-table td{padding:11px 12px;border-bottom:1px solid var(--line);color:var(--muted)}
.cmp tr:last-child td,.feat-table tr:last-child td,.price-table tr:last-child td{border-bottom:none}
/* Undoes .prose code's overflow-wrap:anywhere inside a table. Every one of these
   lives in a .scroll-x, so a cell that cannot fit scrolls sideways instead of
   widening the page — and a column of docs tables is identifiers and paths,
   where breaking `claude_desktop_config.json` across three lines to save 40px of
   column costs more than the sideways drag does. */
.cmp code,.feat-table code,.price-table code{overflow-wrap:normal}

/* ── Dot-grid backdrop ── */
/* The homepage and the 404 page share this fixed backdrop. It is the one
   sanctioned radial-gradient: a 1px dot *pattern*, not a color wash — the
   "no radial gradients" rule targets glows (see docs/signal.md). A page can
   add its own mask-image rule on body::before to fade the field. */
body.dot-grid::before{
  content:"";
  position:fixed;inset:0;pointer-events:none;z-index:0;
  background-image:radial-gradient(color-mix(in oklab, var(--accent) 10%, var(--bg)) 1px, transparent 1px);
  background-size:30px 30px;
}

/* ── Overlay / loading ── */
/* Class-based contract (not an ID list) so any full-screen overlay — including
   ones app.html and portal.html don't share today — gets the same rule by
   adding the class, not by growing a selector list here. */
.overlay{
  position:fixed;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;
  background:var(--bg);z-index:200;
}
/* A tier gate blocks one section, not the whole product. Once the chrome is up,
   the overlay covers the content column only, so the sidebar and top nav stay
   visible and clickable and the person can navigate back out of the gated
   section instead of being stranded on a full-page block. */
.overlay.overlay-inset{top:var(--topnav-h);left:var(--sidebar-w);right:0;bottom:0}
/* The one deliberate circle: a rotating ring reads as a spinner, a rotating
   square reads as a glitch. The no-circles rule is about chrome -- dots, chips,
   badges -- not about this. */
#loading-overlay .spinner{
  width:32px;height:32px;border:2px solid var(--line2);border-top-color:var(--accent);
  border-radius:50%;animation:spin .7s linear infinite;margin-bottom:16px;
}
@keyframes spin{to{transform:rotate(360deg)}}
.overlay-card{background:var(--surface);border:1px solid var(--line);border-radius:0;padding:40px;max-width:480px;width:90%;text-align:center}
.overlay-icon{font-size:40px;margin-bottom:16px}
.overlay-title{font-family:'Hanken Grotesk',sans-serif;font-size:20px;font-weight:600;margin-bottom:8px;letter-spacing:-0.02em}
.overlay-body{font-size:13px;color:var(--muted);line-height:1.6;margin-bottom:24px}

/* ── Billing banner (past-due warning, shown on portal + app) ── */
.billing-banner{display:flex;align-items:center;gap:12px;padding:12px 16px;border-radius:0;margin-bottom:24px;font-size:13px;line-height:1.5;border:1px solid}
.billing-banner.warn{background:var(--surface);border-color:var(--amber);color:var(--ink)}
.billing-banner .banner-btn{margin-left:auto;flex-shrink:0;padding:7px 14px;background:var(--amber);color:var(--bg);border:none;border-radius:2px;font-family:'IBM Plex Mono',monospace;font-size:12px;font-weight:600;cursor:pointer;transition:filter .12s}
.billing-banner .banner-btn:hover{filter:brightness(1.1)}

/* ── Seat cell (portal members list + app team table) ── */
/* State and action are separate things: the indicator is status language
   (dot + mono label, .seat-static), and the change is an explicit labeled
   button — the old mint pill that toggled on click read as a badge, not a
   control. .seat-action rides on .btn-secondary; .danger previews revoke. */
.seat-cell{display:inline-flex;align-items:center;gap:10px}
.seat-action{font-size:10px;padding:3px 10px;white-space:nowrap}
.seat-action.danger:hover{border-color:var(--warn);color:var(--warn)}
/* Seat indicator: status language (dot + label), not a button */
.seat-static{font-family:'IBM Plex Mono',monospace;font-size:11px;display:inline-flex;align-items:center;gap:7px;color:var(--muted);white-space:nowrap}
.seat-static::before{content:'';width:8px;height:8px;background:var(--line2)}
.seat-static.on{color:var(--ink)}
.seat-static.on::before{background:var(--mint)}

/* ── Toast ── */
#toast{position:fixed;bottom:24px;right:24px;padding:10px 18px;border-radius:2px;font-size:13px;font-family:'IBM Plex Mono',monospace;z-index:300;transition:opacity .3s;pointer-events:none;opacity:0}
#toast.show{opacity:1}
#toast.success{background:var(--mint);color:var(--bg)}
#toast.error{background:var(--warn);color:var(--bg)}

/* ── Early access banner ── */
.ea-banner{display:flex;gap:12px;align-items:flex-start;padding:12px 16px;margin-bottom:24px;border-radius:2px;background:var(--surface);border:1px solid var(--line2)}
.ea-banner .ea-tag{flex:none;margin-top:1px;display:inline-flex;align-items:center;height:var(--badge-h);padding:0 8px;border-radius:2px;line-height:1;font-family:'IBM Plex Mono',monospace;font-size:10px;font-weight:600;letter-spacing:0.08em;text-transform:uppercase;white-space:nowrap;color:var(--bg);background:var(--amber);border:1px solid var(--amber)}
/* Doubled class on purpose. A page's own typography -- the docs layout's
   `.content p`, the marketing layout's `.page p` -- matches `.ea-banner p`
   on specificity and loads later, so it won and re-inflated the banner's
   type and re-added a bottom margin inside the box. Doubling raises
   specificity just enough that the component owns its internals wherever
   it is dropped, instead of every layout needing its own override. */
.ea-banner.ea-banner p{margin:0;font-size:13px;line-height:1.6;color:var(--muted)}
.ea-banner strong{color:var(--ink);font-weight:600}
@media(max-width:640px){.ea-banner{flex-direction:column;gap:8px}}

/* ── Distilled runbooks (F7) — list + detail pane ── */
.rb-layout{display:grid;grid-template-columns:300px minmax(0,1fr);gap:20px;align-items:start}
.rb-index{display:flex;flex-direction:column;gap:12px;position:sticky;top:calc(var(--topnav-h) + 24px)}
.rb-search{width:100%;padding:9px 12px;background:var(--surface);border:1px solid var(--line);border-radius:2px;color:var(--ink);font-family:'IBM Plex Mono',monospace;font-size:12px}
.rb-search::placeholder{color:var(--muted)}
.rb-search:focus{outline:none;border-color:var(--accent)}
.rb-list{display:flex;flex-direction:column;gap:4px;max-height:calc(100vh - 240px);overflow-y:auto}
.rb-item{text-align:left;padding:10px 12px;background:none;border:1px solid transparent;border-radius:2px;cursor:pointer;font-family:inherit;transition:background .12s ease,border-color .12s ease}
.rb-item:hover{background:var(--surface)}
.rb-item.active{background:var(--surface);border-color:var(--line2)}
.rb-item-topic{font-family:'Hanken Grotesk',sans-serif;font-size:13px;font-weight:600;letter-spacing:-0.01em;color:var(--ink);margin-bottom:3px;line-height:1.3}
.rb-item-meta{font-family:'IBM Plex Mono',monospace;font-size:11px;color:var(--muted)}
.rb-list-empty{font-size:12px;color:var(--muted);padding:12px}
.rb-detail{background:var(--surface);border:1px solid var(--line);border-radius:0;padding:24px 28px;min-height:220px}
.rb-detail h3{font-family:'Hanken Grotesk',sans-serif;font-size:18px;font-weight:600;letter-spacing:-0.01em;color:var(--ink);margin-bottom:6px}
.rb-detail-meta{font-family:'IBM Plex Mono',monospace;font-size:11px;color:var(--muted);margin-bottom:18px;padding-bottom:14px;border-bottom:1px solid var(--line)}
.rb-detail-body{font-family:'IBM Plex Mono',monospace;font-size:12.5px;line-height:1.7;color:var(--ink);white-space:pre-wrap;overflow-wrap:anywhere;background:none;border:none;margin:0}
.rb-detail-empty{color:var(--muted);font-size:13px}
.rb-empty{background:var(--surface);border:1px solid var(--line);border-radius:0;padding:44px 32px;text-align:center;color:var(--muted);font-size:13px;line-height:1.6;max-width:560px}
.rb-sources{margin-top:22px}
.rb-src-head{font-size:10px;letter-spacing:0.12em;text-transform:uppercase;color:var(--muted);margin-bottom:10px;padding-bottom:6px;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;align-items:baseline;gap:12px}
.rb-src-count{font-family:'IBM Plex Mono',monospace;letter-spacing:0.04em}
.rb-src-list{display:flex;flex-direction:column;gap:2px}
.rb-src-row{display:flex;align-items:center;gap:12px;width:100%;text-align:left;padding:7px 8px;margin:0 -8px;background:none;border:1px solid transparent;border-radius:2px;cursor:pointer;font-family:inherit}
.rb-src-row:hover{background:color-mix(in oklab, var(--ink) 4%, var(--bg));border-color:var(--line)}
.rb-src-row.gone{cursor:default}
.rb-src-row.gone:hover{background:none;border-color:transparent}
.rb-src-id{font-family:'IBM Plex Mono',monospace;font-size:11px;color:var(--muted);flex-shrink:0;width:70px}
.rb-src-title{font-size:12px;color:var(--ink);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.rb-src-title.gone{color:var(--muted);font-style:italic}
.rb-pager{display:flex;align-items:center;justify-content:flex-end;gap:10px;margin-top:14px}
.rb-pager-range{font-family:'IBM Plex Mono',monospace;font-size:11px;color:var(--muted)}
.pager-btn{width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;background:none;border:1px solid var(--line2);border-radius:2px;color:var(--muted);font-family:'IBM Plex Mono',monospace;font-size:14px;line-height:1;cursor:pointer;padding:0}
.pager-btn:hover{border-color:var(--accent);color:var(--accent)}
.pager-btn:disabled{opacity:.35;cursor:default;border-color:var(--line)}
.pager-btn:disabled:hover{border-color:var(--line);color:var(--muted)}
@media(max-width:820px){.rb-layout{grid-template-columns:minmax(0,1fr)}.rb-index{position:static}.rb-list{max-height:none}}

/* ── Marketing chrome (index, /pages, /docs) ── */
/* One nav and one button family for every public page. Each layout used to carry
   its own copy and the three drifted (three button sizes, two casings). */
.site-nav{
  position:fixed;top:0;left:0;right:0;z-index:100;
  display:flex;align-items:center;justify-content:space-between;
  padding:0 32px;height:60px;
  background:color-mix(in srgb, var(--bg) 92%, transparent);backdrop-filter:blur(14px);border-bottom:1px solid var(--line);
}
.site-nav .nav-brand{
  font-family:'Hanken Grotesk',sans-serif;font-weight:700;font-size:20px;letter-spacing:-0.04em;
  color:var(--ink);text-decoration:none;-webkit-font-smoothing:auto;
}
.site-nav .nav-brand .q{color:var(--accent)}
.site-nav .nav-links{display:flex;gap:6px;align-items:center}
.site-nav .nav-links a{
  font-family:'IBM Plex Mono',monospace;font-size:12px;letter-spacing:0.04em;text-transform:uppercase;
  color:var(--muted);text-decoration:none;padding:7px 13px;border-radius:2px;transition:all .18s ease;
}
.site-nav .nav-links a:hover,.site-nav .nav-links a.active{color:var(--ink);background:var(--surface)}
.site-nav .nav-links a.active{color:var(--accent)}
/* Mobile: brand + CTA on the first row, the links as a scrollable strip under them. */
@media (max-width:700px){
  .site-nav{flex-wrap:wrap;height:auto;padding:10px 16px 0;row-gap:4px}
  .site-nav .nav-brand{order:1}
  .site-nav .nav-cta{order:2}
  .site-nav .nav-links{
    order:3;flex:0 0 100%;flex-wrap:nowrap;gap:2px;overflow-x:auto;white-space:nowrap;padding:2px 0 8px;
    scrollbar-width:none;-webkit-overflow-scrolling:touch;
    mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent);
    -webkit-mask-image:linear-gradient(to right,#000 calc(100% - 28px),transparent);
  }
  .site-nav .nav-links::-webkit-scrollbar{display:none}
  .site-nav .nav-links a{flex:none}
}

/* Buttons: the same element in the header, the hero and the closing band. */
.cta-btn{
  display:inline-flex;align-items:center;gap:10px;text-decoration:none;cursor:pointer;
  font-family:'IBM Plex Mono',monospace;font-size:13px;font-weight:600;letter-spacing:0.03em;
  padding:11px 20px;border-radius:2px;border:1px solid transparent;transition:all .18s ease;white-space:nowrap;
}
.cta-btn svg{flex-shrink:0}
.cta-btn.cta-primary{background:var(--accent);color:var(--on-accent)}
.cta-btn.cta-primary:hover{filter:brightness(1.1)}
.cta-btn.cta-ghost{background:var(--surface);color:var(--accent);border-color:var(--accent)}
.cta-btn.cta-ghost:hover{background:color-mix(in oklab, var(--accent) 12%, var(--bg))}
.cta-row{display:flex;gap:14px;flex-wrap:wrap;align-items:center;margin:32px 0 8px}
.cta-hint{margin-top:12px;font-family:'IBM Plex Mono',monospace;font-size:12px;color:var(--muted);letter-spacing:0.02em}
.cta-hint span{color:var(--mint)}

.eyebrow{
  display:inline-flex;align-items:center;gap:9px;
  font-family:'IBM Plex Mono',monospace;font-size:12px;letter-spacing:0.2em;text-transform:uppercase;
  color:var(--accent);margin-bottom:24px;
}
.lede{font-size:clamp(16px,1.5vw,19px);line-height:1.62;color:var(--muted);max-width:60ch;text-wrap:pretty}
.lede strong{color:var(--ink);font-weight:600}
.pre-label{
  font-family:'IBM Plex Mono',monospace;font-size:10px;letter-spacing:0.15em;text-transform:uppercase;
  color:var(--muted);margin-bottom:14px;display:block;
}
.pre-label span{color:var(--accent)}
.callout{
  border-radius:0;padding:16px 20px;margin:20px 0;font-size:14px;line-height:1.65;
  display:flex;gap:14px;align-items:flex-start;
  background:var(--surface);border:1px solid var(--line2);color:var(--ink);
}
.callout.callout p{margin:0;color:var(--ink);font-size:14px;line-height:1.65}
.callout-icon{flex-shrink:0;margin-top:2px;width:18px;height:18px;color:var(--accent)}
.callout-icon svg{width:18px;height:18px;display:block}
.callout.warn .callout-icon{color:var(--amber)}
.callout.beta .callout-icon{color:var(--mint)}
.callout.warn{border-color:var(--amber)}
.callout.beta{border-color:var(--mint)}
.site-footer{position:relative;z-index:1;padding:36px clamp(20px,5vw,80px) 40px;border-top:1px solid var(--line);background:var(--bg)}
.foot-row,.foot-disclaimer{max-width:1200px;margin-left:auto;margin-right:auto}
.foot-row{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:20px}
.foot-brand{font-family:'Hanken Grotesk',sans-serif;font-weight:700;font-size:18px;letter-spacing:-0.04em;color:var(--ink);text-decoration:none}
.foot-brand .q{color:var(--accent)}
.foot-links{display:flex;gap:18px;flex-wrap:wrap}
.foot-links a{font-family:'IBM Plex Mono',monospace;font-size:11px;letter-spacing:0.04em;text-transform:uppercase;color:var(--muted);text-decoration:none}
.foot-links a:hover{color:var(--ink)}
.foot-legal{font-family:'IBM Plex Mono',monospace;font-size:11px;letter-spacing:0.04em;color:var(--muted)}
.foot-disclaimer{margin-top:22px;padding-top:18px;border-top:1px solid var(--line);font-family:'IBM Plex Mono',monospace;font-size:11px;line-height:1.6;letter-spacing:0.02em;color:var(--muted)}

/* Inline SVG icons in place of emoji, so every device draws the same glyph. */
.overlay-icon svg{width:36px;height:36px;display:block;margin:0 auto;color:var(--accent)}
.overlay-icon.warn svg{color:var(--amber)}
.nav-icon svg{width:14px;height:14px;display:inline-block;vertical-align:-2px}
.banner-icon svg{width:16px;height:16px;display:inline-block;vertical-align:-3px;margin-right:6px}

/* ── Scrollbar ── */
/* The standard properties, not `::-webkit-scrollbar`, and the difference is
   layout rather than taste: styling a scrollbar the webkit way replaces the
   platform's scrollbar with a custom one, and a custom scrollbar is ALWAYS
   classic -- it takes 6px of layout width out of the page on every device,
   including the macOS default where the platform would have drawn an overlay
   over the content and taken nothing. That is what made the dashboard twitch
   sideways each time a task was opened: `body.panel-open{overflow:hidden}`
   (app.html) takes the scrollbar away, the viewport grows by its width, and
   everything anchored to the right edge -- the fixed top bar included -- slides
   with it.
   `scrollbar-width`/`scrollbar-color` are inherited, so setting them here
   styles every scroller on the page, and they leave overlay scrollbars overlaid.
   Keeping them standard is necessary but it is not what fixed the twitch, and
   the difference matters because the obvious fix does not work: Chromium
   reserves no `scrollbar-gutter` for the VIEWPORT under `overflow:hidden` --
   measured, with these two properties at their initial values as well, so it
   is not a custom-scrollbar effect -- while honoring it on an ordinary scroll
   container. Where the platform draws a classic scrollbar (Windows and Linux,
   and macOS set to always show them) the dashboard therefore scrolls #main
   rather than the document, and the gutter belongs to that box; see app.html.
   Firefox has only ever had these; Chromium 121+ and Safari 18.2+ honor them. */
:root{scrollbar-width:thin;scrollbar-color:var(--line2) transparent}

/* ── Responsive ── */
/* One breakpoint here, at 720px, and it is not the one app.html uses.
   app.html's own 900px query reflows the content grids (.stat-grid,
   .kanban-board, .usage-grid) — that is about columns fitting.
   720px is a different question: where a 220px sidebar stops paying for itself.
   A portrait tablet at 820px still leaves ~600px for content, so it keeps the
   real sidebar; only phones get the drawer. Do not merge the two numbers —
   at 900px this file would hand tablets a drawer they do not need, and
   `#main{margin-left:0}` would slide the content under a sidebar still on
   screen.
   Below here the sidebar is off-canvas and the toggle is the only way to reach
   it — see initMobileNav in common.js, whose matchMedia carries the matching
   721px min-width. Change both together.
   This block replaced `#sidebar{display:none}`, which left a phone able to
   reach Overview and no other section. */
@media(max-width:720px){
  #mobile-nav-toggle{display:flex}

  /* A tag row is three stacked rows on a phone: identity, then the description
     in full, then the controls. As one wrapping line the description was
     squeezed to "No de…" between the badges and the buttons, which told you
     nothing — and a description is the whole point of the field, since a tag
     without one leaves agents guessing. Forcing the two later groups to full
     width is what creates the breaks; the badges stay on line one because they
     are what is left. */
  .tag-desc{flex:1 1 100%;order:1;white-space:normal;overflow:visible;text-overflow:clip;line-height:1.5}
  .tag-actions{flex:1 1 100%;order:2;margin-left:0;justify-content:flex-end}
  /* The row no longer truncates, so the tap-to-expand affordance has nothing
     left to reveal. */
  .tag-row.expandable{cursor:default}

  /* Both of these are the sidebar's footprint, so they belong with the
     breakpoint that takes it off-canvas — not with the content-grid one.
     The tighter padding is phone ergonomics: 32px of gutter on a 375pt screen
     is 17% of the width. */
  #main{margin-left:0;padding:20px 16px}
  .overlay.overlay-inset{left:0}

  /* The sidebar keeps its markup, its .nav-item buttons and showSection().
     Only its position changes.
     `visibility` is not decoration here: a drawer that is merely translated
     off-screen stays in the tab order and the accessibility tree, so Tab from
     the top nav walks into nav items nobody can see. Hidden is the closed
     state; `inert` on #main covers the mirror case while it is open.
     The 0s-with-delay timing is deliberate — a plain `visibility .22s` holds
     the old value for the whole duration, which hides the slide-out correctly
     but makes the slide-IN animate while still invisible. So: reveal
     immediately when opening, defer hiding until the slide-out has finished. */
  #sidebar{
    z-index:96;width:min(280px,84vw);background:var(--bg);
    visibility:hidden;
    transform:translateX(-100%);
    transition:transform .22s ease, visibility 0s linear .22s;
  }
  #sidebar.open{
    visibility:visible;
    transform:translateX(0);
    transition:transform .22s ease, visibility 0s linear 0s;
  }

  .sidebar-identity{
    display:block;padding:0 20px 14px;margin-bottom:6px;border-bottom:1px solid var(--line);
  }
  #sidebar-email{
    display:block;font-family:'IBM Plex Mono',monospace;font-size:11px;
    color:var(--ink);line-height:1.45;overflow-wrap:anywhere;
  }

  /* Sits above the task/org panels (z-index 90) and below the top nav (100),
     so the toggle stays visible and tappable to close what it opened.
     Translucency follows the site nav's pattern — color-mix against --bg,
     never rgba() of a color. */
  #nav-scrim{
    display:block;position:fixed;top:var(--topnav-h);left:0;right:0;bottom:0;z-index:95;
    background:color-mix(in srgb, var(--bg) 82%, transparent);
    opacity:0;visibility:hidden;
    transition:opacity .2s ease, visibility 0s linear .2s;
  }
  #nav-scrim.open{
    opacity:1;visibility:visible;
    transition:opacity .2s ease, visibility 0s linear 0s;
  }

  /* The email is the widest item in the row and is what pushed the bar past the
     viewport. It already appears on Profile, so it leaves the bar rather than
     overflowing it; the slug truncates instead of shoving Sign out off-screen. */
  #topnav{gap:10px;padding:0 12px}
  #org-email{display:none}
  #org-slug{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
  .topnav-right{flex-shrink:0}
}

/* No slide, but visibility must still flip — `transition:none` alone would be
   fine, yet spelling it out keeps the open/closed states unambiguous. */
@media (prefers-reduced-motion: reduce){
  #sidebar,#sidebar.open,#nav-scrim,#nav-scrim.open{transition:none}
}
