/* ============================================================
   ticketbot design system: tokens and component classes, in the harbor palette.
   Aesthetic: warm paper + ink, hairline grid, editorial serif
   accents, monospace numerics. Precision over decoration.
   Started as a snapshot of the navi kit (0.4.0); now owned here.
   Reusable components belong in this file, one-offs in app.css.
   ============================================================ */

/* ---------- 1. TOKENS ----------
   Harbor is the only palette: two seeds (--seed / --seed-dark), a neutral
   tint and a five-colour chart ramp. Every neutral, accent tint and shadow
   is mixed from those, so a colour change is one of these declarations.
   ------------------------------------------------------------------ */
:root {
  color-scheme: light;
  /* type */
  --font-sans: "Instrument Sans", ui-sans-serif, system-ui, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", monospace;

  /* display face — the UI sans, heavier and tighter */
  --font-display: var(--font-sans);
  --display-weight: 600;
  --display-tracking: -0.035em;

  --text-2xs: 0.6875rem;  /* 11 */
  --text-xs:  0.75rem;    /* 12 */
  --text-sm:  0.8125rem;  /* 13 */
  --text-base:0.875rem;   /* 14 */
  --text-md:  1rem;       /* 16 */
  --text-lg:  1.125rem;   /* 18 */
  --text-xl:  1.5rem;     /* 24 */
  --text-2xl: 2rem;       /* 32 */
  --text-3xl: 2.75rem;    /* 44 */

  /* spacing — 4px base */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 20px; --s6: 24px; --s8: 32px; --s10: 40px; --s12: 48px;

  --radius-sm: 6px;
  --radius:    10px;
  --radius-lg: 14px;
  --radius-full: 999px;

  --sidebar-w: 236px;
  --sidebar-w-collapsed: 64px;
  --topbar-h: 58px;

  --ease: cubic-bezier(.32,.72,0,1);
  --dur: 180ms;
  --dur-view: 140ms;   /* content swap — short enough to survive a re-render */

  /* ---- palette: harbor (the app icon uses the same teal) ---- */
  --seed:      #0E6F80;
  --seed-dark: #3FB0C4;
  --tint:      #5E7480;
  --c2: #B4531F; --c3: #3E7D3A; --c4: #A8770F; --c5: #5C5FA6; --c6: #8B949B;

  /* ---- neutrals, mixed from --tint ---- */
  --bg:        color-mix(in srgb, var(--tint)  8%, #FBFAF7);
  --bg-sunken: color-mix(in srgb, var(--tint) 13%, #F4F2EF);
  --surface:   color-mix(in srgb, var(--tint)  2%, #FFFFFF);
  --surface-2: color-mix(in srgb, var(--tint)  5%, #FFFFFF);
  --ink:       color-mix(in srgb, var(--tint) 10%, #131210);
  --ink-2:     color-mix(in srgb, var(--tint) 16%, #43403B);
  --muted:     color-mix(in srgb, var(--tint) 22%, #5F5B54);
  --faint:     color-mix(in srgb, var(--tint) 22%, #635F58);
  --line:      color-mix(in srgb, var(--tint) 16%, #E4E1DB);
  --line-soft: color-mix(in srgb, var(--tint) 10%, #EEECE8);

  /* ---- accent, mixed from --seed ---- */
  --accent:      var(--seed);
  --accent-ink:  #FFFFFF;
  --accent-soft: color-mix(in srgb, var(--accent) 11%, var(--surface));
  --accent-line: color-mix(in srgb, var(--accent) 34%, var(--surface));
  --accent-on-soft: color-mix(in srgb, var(--accent) 86%, var(--ink));

  /* ---- semantic ---- */
  --ok:        #1F6B4A;  --ok-soft:   color-mix(in srgb, var(--ok)   10%, var(--surface));
  --warn:      #8A6000;  --warn-soft: color-mix(in srgb, var(--warn) 12%, var(--surface));
  --bad:       #A32B23;  --bad-soft:  color-mix(in srgb, var(--bad)  10%, var(--surface));
  --info:      #2C5680;  --info-soft: color-mix(in srgb, var(--info) 10%, var(--surface));
  --bad-ink:   #FFFFFF;

  /* ---- charts: series 1 is always the accent ---- */
  --chart-1: var(--accent);
  --chart-2: var(--c2);
  --chart-3: var(--c3);
  --chart-4: var(--c4);
  --chart-5: var(--c5);
  --chart-6: var(--c6);

  /* ---- flow canvas: the trigger node's hue. A chart slot rather than the
     accent so a trigger never reads as an if node. ---- */
  --trigger: var(--chart-5);

  /* ---- shadows, tinted to match the neutrals ---- */
  --sh: color-mix(in srgb, var(--tint) 55%, #000000);
  --shadow-sm: 0 1px 2px color-mix(in srgb, var(--sh) 7%, transparent);
  --shadow:    0 2px 4px color-mix(in srgb, var(--sh) 6%, transparent),
               0 8px 20px -8px color-mix(in srgb, var(--sh) 14%, transparent);
  --shadow-lg: 0 8px 16px -6px color-mix(in srgb, var(--sh) 14%, transparent),
               0 24px 48px -12px color-mix(in srgb, var(--sh) 24%, transparent);
}

/* ---------- 1c. DARK ---------- */
[data-theme="dark"] {
  color-scheme: dark;   /* native pickers, spinners and scrollbars follow the theme */
  --bg:        color-mix(in srgb, var(--tint)  7%, #0D0D0C);
  --bg-sunken: color-mix(in srgb, var(--tint)  4%, #070706);
  --surface:   color-mix(in srgb, var(--tint)  8%, #151513);
  --surface-2: color-mix(in srgb, var(--tint) 11%, #1A1A18);
  --ink:       color-mix(in srgb, var(--tint)  8%, #F5F3EC);
  --ink-2:     color-mix(in srgb, var(--tint) 16%, #DCD8CE);
  --muted:     color-mix(in srgb, var(--tint) 30%, #A5A096);
  --faint:     color-mix(in srgb, var(--tint) 26%, #9B968B);
  --line:      color-mix(in srgb, var(--tint) 14%, #282725);
  --line-soft: color-mix(in srgb, var(--tint)  9%, #201F1D);

  --accent:      var(--seed-dark);
  --accent-ink:  color-mix(in srgb, var(--accent) 16%, #0A0A09);
  --accent-soft: color-mix(in srgb, var(--accent) 16%, var(--surface));
  --accent-line: color-mix(in srgb, var(--accent) 32%, var(--surface));

  --ok:        #5FC79A;  --ok-soft:   color-mix(in srgb, var(--ok)   14%, var(--surface));
  --warn:      #E0B052;  --warn-soft: color-mix(in srgb, var(--warn) 14%, var(--surface));
  --bad:       #F08A78;  --bad-soft:  color-mix(in srgb, var(--bad)  14%, var(--surface));
  --info:      #82B4E4;  --info-soft: color-mix(in srgb, var(--info) 14%, var(--surface));
  --bad-ink:   color-mix(in srgb, var(--bad) 18%, #0A0A09);

  --chart-2: color-mix(in srgb, var(--c2) 58%, #EFEDE6);
  --chart-3: color-mix(in srgb, var(--c3) 58%, #EFEDE6);
  --chart-4: color-mix(in srgb, var(--c4) 62%, #EFEDE6);
  --chart-5: color-mix(in srgb, var(--c5) 55%, #EFEDE6);
  --chart-6: color-mix(in srgb, var(--c6) 55%, #23221F);

  --sh: #000000;
  --shadow-sm: 0 1px 2px color-mix(in srgb, var(--sh) 40%, transparent);
  --shadow:    0 2px 4px color-mix(in srgb, var(--sh) 30%, transparent),
               0 8px 20px -8px color-mix(in srgb, var(--sh) 60%, transparent);
  --shadow-lg: 0 8px 16px -6px color-mix(in srgb, var(--sh) 50%, transparent),
               0 24px 48px -12px color-mix(in srgb, var(--sh) 70%, transparent);
}

/* ---------- 2. RESET ---------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html { -webkit-text-size-adjust: 100%; }
body {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: 1.5;
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }
a { color: inherit; text-decoration: none; }
svg { display: block; }
[hidden] { display: none !important; }
ul, ol { list-style: none; padding: 0; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

/* scrollbars */
* { scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
*::-webkit-scrollbar { width: 10px; height: 10px; }
*::-webkit-scrollbar-thumb { background: var(--line); border-radius: 99px; border: 3px solid transparent; background-clip: content-box; }
*::-webkit-scrollbar-thumb:hover { background: var(--faint); background-clip: content-box; }

/* ---------- 3. PRIMITIVES ---------- */
.ico14 { width: 14px; height: 14px; flex-shrink: 0; }
.ico16 { width: 16px; height: 16px; flex-shrink: 0; }
.ico20 { width: 20px; height: 20px; flex-shrink: 0; }
.num { font-family: var(--font-mono); font-feature-settings: "tnum" 1; letter-spacing: -0.02em; }
.serif, .display {
  font-family: var(--font-display);
  font-weight: var(--display-weight);
  letter-spacing: var(--display-tracking);
}
.eyebrow {
  font-size: var(--text-2xs); font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; color: var(--faint);
}
.muted { color: var(--muted); }
.stack { display: flex; flex-direction: column; }
.row { display: flex; align-items: center; }
.gap1{gap:var(--s1)}.gap2{gap:var(--s2)}.gap3{gap:var(--s3)}.gap4{gap:var(--s4)}
.gap5{gap:var(--s5)}.gap6{gap:var(--s6)}.gap8{gap:var(--s8)}
.spread { justify-content: space-between; }
.grow { flex: 1; }
.wrap { flex-wrap: wrap; }

/* ---------- 4. APP SHELL ---------- */
.app { display: flex; min-height: 100vh; }

.sidebar {
  position: fixed; inset: 0 auto 0 0; z-index: 40;
  width: var(--sidebar-w);
  display: flex; flex-direction: column;
  background: var(--surface);
  border-right: 1px solid var(--line);
  transition: width var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.app.collapsed .sidebar { width: var(--sidebar-w-collapsed); }

.brand {
  display: flex; align-items: center; gap: var(--s3);
  height: var(--topbar-h); padding: 0 var(--s5);
  border-bottom: 1px solid var(--line-soft);
  flex-shrink: 0;
}
/* the app icon (/favicon.svg), an <img> with empty alt beside the name; its own
   rounded tile and colours, so nothing here paints it */
.brand-mark { width: 28px; height: 28px; flex-shrink: 0; display: block; }
.brand-name {
  font-family: var(--font-display); font-weight: var(--display-weight);
  font-size: var(--text-md); letter-spacing: var(--display-tracking);
  white-space: nowrap;
}
.brand-name sup {
  font-family: var(--font-mono); font-size: 9px; color: var(--muted);
  vertical-align: super; margin-left: 4px; letter-spacing: 0;
}

.nav { flex: 1; overflow-y: auto; padding: var(--s5) var(--s3); }
.nav-group + .nav-group { margin-top: var(--s6); }
.nav-label { padding: 0 var(--s3) var(--s2); }
.app.collapsed .nav-label { opacity: 0; height: 12px; overflow: hidden; }

.nav-item {
  display: flex; align-items: center; gap: var(--s3);
  width: 100%; padding: 7px var(--s3); border-radius: var(--radius-sm);
  color: var(--ink-2); font-size: var(--text-base); font-weight: 500;
  white-space: nowrap; position: relative;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.nav-item + .nav-item { margin-top: 1px; }
.nav-item svg { width: 17px; height: 17px; flex-shrink: 0; stroke-width: 1.6; opacity: .8; }
.nav-item:hover { background: var(--bg-sunken); color: var(--ink); }
.nav-item.active { background: var(--accent-soft); color: var(--accent-on-soft); font-weight: 600; }
.nav-item.active svg { opacity: 1; }
.nav-item.active::before {
  content: ""; position: absolute; left: -12px; top: 50%; translate: 0 -50%;
  width: 3px; height: 16px; border-radius: 0 3px 3px 0; background: var(--accent);
}
.nav-item .label { transition: opacity var(--dur) var(--ease); }
.app.collapsed .nav-item .label,
.app.collapsed .nav-item .pill { opacity: 0; pointer-events: none; }
.nav-item .pill {
  margin-left: auto; font-family: var(--font-mono); font-size: var(--text-2xs);
  padding: 1px 6px; border-radius: var(--radius-full);
  background: var(--bg-sunken); color: var(--muted);
}
.nav-item.active .pill { background: var(--accent); color: var(--accent-ink); }

.sidebar-foot { padding: var(--s3); border-top: 1px solid var(--line-soft); }
.user-chip {
  display: flex; align-items: center; gap: var(--s3); width: 100%;
  padding: var(--s2); border-radius: var(--radius-sm); text-align: left;
  transition: background var(--dur) var(--ease);
}
.user-chip:hover { background: var(--bg-sunken); }
.user-meta { min-width: 0; }
.user-meta b { display: block; font-size: var(--text-sm); font-weight: 600; }
.user-meta span { display: block; font-size: var(--text-xs); color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.app.collapsed .user-meta, .app.collapsed .user-chip svg:last-child { display: none; }

/* main column */
.main {
  flex: 1; min-width: 0;
  margin-left: var(--sidebar-w);
  transition: margin var(--dur) var(--ease);
}
.app.collapsed .main { margin-left: var(--sidebar-w-collapsed); }

.topbar {
  position: sticky; top: 0; z-index: 30;
  height: var(--topbar-h);
  display: flex; align-items: center; gap: var(--s4);
  padding: 0 var(--s6);
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(12px) saturate(1.4);
  border-bottom: 1px solid var(--line);
}
.crumbs { display: flex; align-items: center; gap: var(--s2); font-size: var(--text-sm); }
.crumbs .sep { color: var(--faint); }
.crumbs .here { font-weight: 600; }

.searchbtn {
  display: flex; align-items: center; gap: var(--s2);
  height: 32px; padding: 0 var(--s2) 0 var(--s3); min-width: 210px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); color: var(--muted); font-size: var(--text-sm);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.searchbtn:hover { border-color: var(--faint); }
.searchbtn svg { width: 15px; height: 15px; }
.kbd {
  display: inline-grid; place-items: center;
  font-family: var(--font-sans); font-size: var(--text-xs); font-weight: 500;
  min-width: 20px; height: 19px; padding: 0 5px;
  border-radius: 5px; border: 1px solid var(--line); border-bottom-width: 2px;
  background: var(--surface); color: var(--ink-2);
}
.keys { display: inline-flex; align-items: center; gap: 3px; }
.keys .sep { color: var(--faint); font-size: var(--text-2xs); }
.searchbtn .kbd { margin-left: auto; }

.icon-btn {
  display: grid; place-items: center; width: 32px; height: 32px;
  border-radius: var(--radius-sm); color: var(--ink-2);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.icon-btn:hover { background: var(--bg-sunken); color: var(--ink); }
.icon-btn svg { width: 17px; height: 17px; stroke-width: 1.7; }
.icon-btn.has-dot { position: relative; }
.icon-btn.has-dot::after {
  content: ""; position: absolute; top: 6px; right: 7px;
  width: 6px; height: 6px; border-radius: 99px; background: var(--accent);
  box-shadow: 0 0 0 2px var(--bg);
}

.view { padding: var(--s8) var(--s6) var(--s12); max-width: 1440px; }
/* a page that is mostly one table uses the whole window (WIDE_PAGES in app.js) */
.view.view-wide { max-width: none; }

/* page header — a record's name on a detail page, or (pageActions) just the row
   of buttons a section page opens with, which needs far less room below it */
.page-head { margin-bottom: var(--s8); }
.page-head:not(:has(.page-title)) { margin-bottom: var(--s5); }
.page-title {
  font-family: var(--font-display); font-weight: var(--display-weight);
  font-size: var(--text-3xl); line-height: 1.06;
  letter-spacing: var(--display-tracking);
}
.page-sub { color: var(--muted); margin-top: var(--s2); max-width: 62ch; }

/* ---------- 5. BUTTONS ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s2);
  height: 34px; padding: 0 var(--s4); border-radius: var(--radius-sm);
  font-size: var(--text-base); font-weight: 500; white-space: nowrap;
  border: 1px solid transparent;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
              color var(--dur) var(--ease), transform 80ms var(--ease);
}
.btn:active { transform: translateY(1px); }
.btn svg { width: 15px; height: 15px; stroke-width: 1.8; }
.btn-primary { background: var(--accent); color: var(--accent-ink); }
.btn-primary:hover { background: color-mix(in srgb, var(--accent) 88%, black); }
.btn-default { background: var(--surface); border-color: var(--line); color: var(--ink); box-shadow: var(--shadow-sm); }
.btn-default:hover { border-color: var(--faint); background: var(--surface-2); }
.btn-ghost { color: var(--ink-2); }
.btn-ghost:hover { background: var(--bg-sunken); color: var(--ink); }
.btn-danger { background: var(--bad); color: var(--bad-ink); }
.btn-danger:hover { background: color-mix(in srgb, var(--bad) 88%, black); }
/* a destructive action beside a primary one: red text, no fill, so the primary keeps the eye */
.btn-ghost.danger { color: var(--bad); }
.btn-ghost.danger:hover { background: var(--bad-soft); color: var(--bad); }
.btn-sm { height: 28px; padding: 0 var(--s3); font-size: var(--text-sm); }
.btn-lg { height: 40px; padding: 0 var(--s5); }
.btn:disabled { opacity: .68; pointer-events: none; cursor: not-allowed; }
.btn-icon { width: 34px; padding: 0; }

.seg {
  display: inline-flex; padding: 2px; gap: 2px;
  background: var(--bg-sunken); border-radius: var(--radius-sm);
  border: 1px solid var(--line-soft);
  /* labels never wrap inside a segment; a control wider than its container scrolls instead */
  max-width: 100%; min-width: 0; overflow-x: auto;
}
.seg button {
  white-space: nowrap;
  padding: 4px var(--s3); border-radius: 5px; font-size: var(--text-sm);
  font-weight: 500; color: var(--muted);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.seg button:hover { color: var(--ink); }
.seg button.on { background: var(--surface); color: var(--ink); box-shadow: var(--shadow-sm); }

/* ---------- 6. SURFACES ---------- */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}
.card-head {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s4);
  padding: var(--s5) var(--s5) var(--s4);
}
.card-head h3 { font-size: var(--text-md); font-weight: 600; letter-spacing: -0.01em; }
.card-head p { font-size: var(--text-sm); color: var(--muted); margin-top: 2px; }
.card-body { padding: 0 var(--s5) var(--s5); }
.card-foot {
  padding: var(--s3) var(--s5); border-top: 1px solid var(--line-soft);
  display: flex; align-items: center; justify-content: space-between; gap: var(--s3);
  font-size: var(--text-sm); color: var(--muted);
}
.card-pad { padding: var(--s5); }

/* Disclosure: a native <details> for content most readers skip. The browser makes the summary a
   focusable toggle (Enter and Space) and announces it as expanded or collapsed, so it needs no
   script. The chevron is the summary's own svg and turns over when it opens. Inline, the
   summary is a quiet text toggle; on a .card, the card head is the toggle. */
.disclosure > summary {
  display: flex; align-items: center; gap: var(--s2);
  list-style: none; cursor: pointer; user-select: none;
}
.disclosure > summary::-webkit-details-marker { display: none; }
.disclosure > summary > svg {
  width: 16px; height: 16px; flex: none; color: var(--muted);
  transition: transform var(--dur) var(--ease);
}
.disclosure[open] > summary > svg { transform: rotate(180deg); }
.disclosure > summary:hover > svg { color: var(--ink); }
.disclosure:not(.card) > summary {
  display: inline-flex; min-height: 24px; border-radius: var(--radius-sm);
  color: var(--ink-2); font-size: var(--text-sm); font-weight: 500;
}
.disclosure:not(.card) > summary:hover { color: var(--ink); }
.disclosure-body { margin-top: var(--s2); }
/* a closed card is its head alone, so the head takes the card's bottom padding too */
.disclosure.card:not([open]) > summary { padding-bottom: var(--s5); }
.disclosure.card > summary:focus-visible { outline-offset: -2px; border-radius: var(--radius-lg); }

.grid { display: grid; gap: var(--s5); }
.g2 { grid-template-columns: repeat(2, minmax(0,1fr)); }
.g3 { grid-template-columns: repeat(3, minmax(0,1fr)); }
.g4 { grid-template-columns: repeat(4, minmax(0,1fr)); }
.g-2-1 { grid-template-columns: 2fr 1fr; }
.g-1-2 { grid-template-columns: 1fr 2fr; }
.span2 { grid-column: span 2; }

/* ---------- 7. STAT TILE ---------- */
.stat { position: relative; overflow: hidden; padding: var(--s5); }
.stat-label { display: flex; align-items: center; gap: var(--s2); }
.stat-value {
  font-family: var(--font-mono); font-size: var(--text-2xl); font-weight: 500;
  letter-spacing: -0.04em; margin-top: var(--s3); line-height: 1;
}
.stat-value small { font-size: var(--text-md); color: var(--muted); letter-spacing: 0; }
.stat-foot { display: flex; align-items: center; gap: var(--s2); margin-top: var(--s3); font-size: var(--text-sm); }
.delta { display: inline-flex; align-items: center; gap: 3px; font-family: var(--font-mono);
  font-size: var(--text-xs); font-weight: 500; padding: 2px 6px; border-radius: var(--radius-full); }
.delta.up { background: var(--ok-soft); color: var(--ok); }
.delta.down { background: var(--bad-soft); color: var(--bad); }
.delta.flat { background: var(--bg-sunken); color: var(--muted); }
.delta svg { width: 11px; height: 11px; stroke-width: 2.4; }
.stat-spark { position: absolute; left: 0; right: 0; bottom: 0; height: 40px;
  opacity: .8; pointer-events: none; }
.stat { padding-bottom: var(--s10); }

/* ---------- 8. BADGES / STATUS ---------- */
.badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 2px var(--s2); border-radius: var(--radius-full);
  font-size: var(--text-xs); font-weight: 500; white-space: nowrap;
  background: var(--bg-sunken); color: var(--ink-2);
}
.badge.ok   { background: var(--ok-soft);   color: var(--ok); }
.badge.warn { background: var(--warn-soft); color: var(--warn); }
.badge.bad  { background: var(--bad-soft);  color: var(--bad); }
.badge.info { background: var(--info-soft); color: var(--info); }
.badge.accent { background: var(--accent-soft); color: var(--accent-on-soft); }
.badge.outline { background: transparent; border: 1px solid var(--line); color: var(--muted); }
/* A column flex stretches its children; a badge keeps the width of its label. */
.stack > .badge { align-self: flex-start; }
.dot { width: 6px; height: 6px; border-radius: 99px; background: currentColor; flex-shrink: 0; }
.dot.pulse { position: relative; }
.dot.pulse::after {
  content: ""; position: absolute; inset: -3px; border-radius: 99px;
  border: 1px solid currentColor; opacity: .5; animation: ping 2s var(--ease) infinite;
}
@keyframes ping { 0%{transform:scale(.6);opacity:.7} 100%{transform:scale(1.5);opacity:0} }

.avatar {
  width: 30px; height: 30px; border-radius: 99px; flex-shrink: 0;
  display: grid; place-items: center;
  font-size: var(--text-xs); font-weight: 600; letter-spacing: .01em;
  background: var(--bg-sunken); color: var(--ink-2);
  border: 1px solid var(--line-soft);
}
.avatar.sm { width: 24px; height: 24px; font-size: 10px; }
.avatar.lg { width: 44px; height: 44px; font-size: var(--text-base); }
.avatar-stack { display: flex; }
.avatar-stack .avatar + .avatar { margin-left: -8px; }
.avatar-stack .avatar { border: 2px solid var(--surface); }

/* ---------- 9. TABLE ---------- */
.table-wrap { overflow-x: auto; }
/* separate borders with no spacing draw the same as collapsed ones, but each rule belongs to
   its cell, so a header cell moved by --thead-y takes its rules (and its rounded corner) along.
   --tbl-menu-w is the trailing column that holds the header menu. */
table.tbl {
  width: 100%; border-collapse: separate; border-spacing: 0; font-size: var(--text-base);
  --tbl-menu-w: calc(var(--s8) + var(--s2));
}
/* The header follows the page scroll under the topbar. position: sticky cannot do it: the
   wrap scrolls sideways for a wide table, which makes the wrap the scroll container the header
   would stick inside. tables.js sets --thead-y on the wrap instead: how far the page has
   scrolled past the wrap's top, clamped to the table. */
.tbl thead th {
  position: relative; z-index: 5; transform: translateY(var(--thead-y, 0px));
  text-align: left; font-size: var(--text-2xs); font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase; color: var(--muted);
  padding: var(--s3) var(--s4); background: var(--surface-2);
  border-top: 1px solid var(--line-soft); border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
/* a header row flush with the card's top edge: its own background is square and
   would paint over the card's rounded corners, so round the end cells to match
   the inside of the card's 1px border. Its top rule goes too — straight, it runs
   out past the arc, and the card's own border already draws that edge */
.card > .table-wrap:first-child .tbl thead tr:first-child th { border-top: 0; }
.card > .table-wrap:first-child .tbl thead tr:first-child th:first-child { border-top-left-radius: calc(var(--radius-lg) - 1px); }
.card > .table-wrap:first-child .tbl thead tr:first-child th:last-child  { border-top-right-radius: calc(var(--radius-lg) - 1px); }
/* A sortable header is a real button so it takes focus and sorts on Enter or Space. It reaches
   into the cell's padding to clear a 24px target without making the header row taller. The
   arrow shows on the sorted column, and on another only while it is hovered or focused. */
.tbl thead th .th-sort {
  display: inline-flex; align-items: center; gap: var(--s1);
  min-height: var(--s6); margin: calc(-1 * var(--s1)) calc(-1 * var(--s2)); padding: var(--s1) var(--s2);
  border-radius: var(--radius-sm); cursor: pointer;
  font: inherit; letter-spacing: inherit; text-transform: inherit; color: inherit;
  transition: color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.tbl thead th .th-sort:hover { color: var(--ink); background: var(--bg-sunken); }
.tbl thead th .sort { display: inline-block; width: 10px; color: var(--faint); }
.tbl thead th:not([aria-sort]) .sort { visibility: hidden; }
.tbl thead th .th-sort:hover .sort, .tbl thead th .th-sort:focus-visible .sort { visibility: visible; }
.tbl thead th[aria-sort] { color: var(--ink); }
.tbl thead th[aria-sort] .sort { color: var(--accent-on-soft); }

/* Column edge: drag to resize, double-click to fit. Pointer only; the rule shows on hover. */
.tbl thead th .col-resize {
  position: absolute; top: 0; bottom: 0; right: 0; width: var(--s2);
  cursor: col-resize; touch-action: none;
}
.tbl thead th .col-resize::after {
  content: ""; position: absolute; top: var(--s2); bottom: var(--s2); right: 0; width: 2px;
  border-radius: var(--radius-full); background: transparent;
  transition: background var(--dur) var(--ease);
}
.tbl thead tr:hover .col-resize::after { background: var(--line); }
.tbl thead th .col-resize:hover::after, .tbl thead th .col-resize.is-active::after { background: var(--accent); }
/* the column being dragged to a new place, and what follows the pointer while it moves */
.tbl thead th.is-col-source { background: var(--accent-soft); color: var(--accent-on-soft); }
.tbl-ghost {
  position: fixed; z-index: 70; pointer-events: none; white-space: nowrap;
  padding: var(--s1) var(--s3); border-radius: var(--radius-sm);
  background: var(--surface); border: 1px solid var(--line); box-shadow: var(--shadow);
  font-size: var(--text-2xs); font-weight: 600; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-2);
}
.tbl-drop {
  position: fixed; z-index: 70; pointer-events: none;
  width: 2px; margin-left: -1px; border-radius: var(--radius-full); background: var(--accent);
}
/* The trailing column holds the header menu (Reset columns) and, in each row, that row's kebab
   (its actions: edit, delete, retry…), so a table has no separate Actions column. */
.tbl col.col-menu, .tbl .th-menu, .tbl .td-menu { width: var(--tbl-menu-w); }
.tbl thead th.th-menu, .tbl tbody td.td-menu { padding: 0 var(--s1); text-align: center; }
.tbl thead th.th-menu .icon-btn, .tbl tbody td.td-menu .icon-btn { margin: 0 auto; }
/* A table with a saved layout: widths come from its <col>s and a cell too narrow for its
   content clips it with an ellipsis rather than pushing the column wider. */
.tbl.is-sized { table-layout: fixed; }
.tbl.is-sized th, .tbl.is-sized td { overflow: hidden; text-overflow: ellipsis; }
/* a double-click fit lays the table out at its natural width for one measurement */
.tbl.is-measuring { table-layout: auto; width: max-content !important; }
.tbl tbody td { padding: var(--s3) var(--s4); border-bottom: 1px solid var(--line-soft); vertical-align: middle; }
.tbl tbody tr { transition: background 120ms var(--ease); }
.tbl tbody tr:hover { background: var(--surface-2); }
.tbl tbody tr.selected { background: var(--accent-soft); }
/* an expanded record under its row (dataTable's detail): sunken so it reads as part of the row
   above, not as another row */
.tbl tbody tr.tbl-detail, .tbl tbody tr.tbl-detail:hover { background: var(--bg-sunken); }
.tbl tbody tr.tbl-detail > td { padding: var(--s4) var(--s5); }
.tbl .r { text-align: right; }
.tbl .c { text-align: center; }
.tbl .nowrap { white-space: nowrap; }
.cell-primary { font-weight: 500; }
.cell-sub { font-size: var(--text-xs); color: var(--muted); }

.toolbar {
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  padding: var(--s4) var(--s5);
}
/* filters in a toolbar size to their content instead of taking a whole row each */
.toolbar .select, .toolbar .input { width: auto; }
/* a group wider than the toolbar (a long .seg) shrinks to the line and scrolls inside itself */
.toolbar > * { min-width: 0; max-width: 100%; }
.bulkbar {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) var(--s5); background: var(--accent-soft);
  border-bottom: 1px solid var(--accent-line); font-size: var(--text-sm);
  color: var(--accent); font-weight: 500;
}

.pagination { display: flex; align-items: center; gap: var(--s1); }
.pagination button {
  min-width: 28px; height: 28px; padding: 0 var(--s2); border-radius: var(--radius-sm);
  font-family: var(--font-mono); font-size: var(--text-sm); color: var(--muted);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.pagination button:hover:not(:disabled) { background: var(--bg-sunken); color: var(--ink); }
.pagination button.on { background: var(--ink); color: var(--bg); }
.pagination button:disabled { color: var(--faint); cursor: not-allowed; }

/* ---------- 10. FORMS ---------- */
.field { display: flex; flex-direction: column; gap: 6px; }
.field > label { font-size: var(--text-sm); font-weight: 500; }
.field .hint { font-size: var(--text-xs); color: var(--muted); }
.field .err { font-size: var(--text-xs); color: var(--bad); }

.input, .select, .textarea {
  width: 100%; height: 34px; padding: 0 var(--s3);
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  font-size: var(--text-base);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.textarea { height: auto; padding: var(--s3); resize: vertical; min-height: 88px; line-height: 1.55; }
.input::placeholder, .textarea::placeholder { color: var(--faint); }
.input:hover, .select:hover, .textarea:hover { border-color: var(--faint); }
.input:focus, .select:focus, .textarea:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
}
.input.invalid { border-color: var(--bad); }
.select {
  appearance: none; padding-right: var(--s8);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%237A766A' stroke-width='2.4' stroke-linecap='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 11px center;
}
.input-group { position: relative; display: flex; align-items: center; }
.input-group svg { position: absolute; left: 10px; width: 15px; height: 15px; color: var(--faint); pointer-events: none; }
.input-group .input { padding-left: 32px; }
.input-affix {
  display: flex; align-items: center; border: 1px solid var(--line);
  border-radius: var(--radius-sm); background: var(--surface); overflow: hidden;
}
.input-affix span {
  padding: 0 var(--s3); height: 32px; display: grid; place-items: center;
  background: var(--bg-sunken); color: var(--muted); font-size: var(--text-sm);
  border-right: 1px solid var(--line);
}
.input-affix .input { border: 0; height: 32px; }
.input-affix .input:focus { box-shadow: none; }
.input-affix:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent); }

.check { display: inline-flex; align-items: flex-start; gap: var(--s3); cursor: pointer; }
.check input { position: absolute; opacity: 0; width: 0; height: 0; }
.check .box {
  width: 16px; height: 16px; margin-top: 2px; flex-shrink: 0;
  border: 1.5px solid var(--faint); border-radius: 4px; background: var(--surface);
  display: grid; place-items: center;
  transition: all var(--dur) var(--ease);
}
.check .box svg { width: 11px; height: 11px; color: var(--accent-ink); opacity: 0; stroke-width: 3; }
.check input:checked + .box { background: var(--accent); border-color: var(--accent); }
.check input:checked + .box svg { opacity: 1; }
.check input:focus-visible + .box { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent); }
.check.radio .box { border-radius: 99px; }
.check.radio .box svg { display: none; }
.check.radio input:checked + .box { border-width: 5px; background: var(--surface); border-color: var(--accent); }

.switch { display: inline-flex; align-items: center; gap: var(--s3); cursor: pointer; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch .track {
  /* display:flex matters — an inline span would give the thumb no box */
  display: flex; align-items: center;
  width: 36px; height: 21px; padding: 2px; flex-shrink: 0;
  border-radius: 99px;
  background: color-mix(in srgb, var(--ink) 10%, var(--bg-sunken));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ink) 8%, var(--line));
  transition: background var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.switch .thumb {
  display: block; width: 17px; height: 17px; border-radius: 99px;
  background: var(--surface);
  box-shadow: 0 1px 2px rgba(23,22,15,.30), 0 0 0 1px rgba(23,22,15,.16);
  transition: transform 220ms var(--ease), width 140ms var(--ease);
}
.switch input:checked + .track {
  background: var(--accent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 80%, black);
}
.switch input:checked + .track .thumb { transform: translateX(15px); }
/* squish on press, like a physical switch */
.switch:active .thumb { width: 21px; }
.switch input:checked:active + .track .thumb { transform: translateX(11px); }
.switch input:focus-visible + .track {
  box-shadow: inset 0 0 0 1px var(--accent), 0 0 0 3px color-mix(in srgb, var(--accent) 24%, transparent);
}
.switch input:disabled + .track { opacity: .45; }
.switch:has(input:disabled) { cursor: not-allowed; }

.switch.sm .track { width: 30px; height: 18px; }
.switch.sm .thumb { width: 14px; height: 14px; }
.switch.sm input:checked + .track .thumb { transform: translateX(12px); }
.switch.sm:active .thumb { width: 17px; }
.switch.sm input:checked:active + .track .thumb { transform: translateX(9px); }

[data-theme="dark"] .switch .thumb {
  background: #F3F1EA;
  box-shadow: 0 1px 3px rgba(0,0,0,.55), 0 0 0 1px rgba(0,0,0,.35);
}

.form-row {
  display: grid; grid-template-columns: 320px minmax(0, 1fr); gap: var(--s8);
  padding: var(--s6) 0; border-bottom: 1px solid var(--line-soft);
}
.form-row:last-child { border-bottom: 0; }
/* the card's own padding already spaces the first and last rows */
.card-pad > .form-row:first-child, .card-body > .form-row:first-child { padding-top: 0; }
.card-pad > .form-row:last-child, .card-body > .form-row:last-child { padding-bottom: 0; }
.form-row h4 { font-size: var(--text-base); font-weight: 600; }
.form-row .desc { font-size: var(--text-sm); color: var(--muted); margin-top: 3px; }

/* ---------- 11. TABS ---------- */
.tabs { display: flex; gap: var(--s5); border-bottom: 1px solid var(--line); }
.tabs button {
  position: relative; padding: var(--s3) 0; font-size: var(--text-base);
  font-weight: 500; color: var(--muted);
  transition: color var(--dur) var(--ease);
}
.tabs button:hover { color: var(--ink); }
.tabs button.on { color: var(--ink); font-weight: 600; }
.tabs button.on::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px;
  height: 2px; background: var(--accent); border-radius: 2px 2px 0 0;
}

/* ---------- 12. LISTS / TIMELINE ---------- */
.list-item {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) var(--s5);
  border-bottom: 1px solid var(--line-soft);
  transition: background 120ms var(--ease);
}
.list-item:last-child { border-bottom: 0; }
.list-item:hover { background: var(--surface-2); }

.timeline { position: relative; padding-left: var(--s6); }
.timeline::before {
  content: ""; position: absolute; left: 7px; top: 6px; bottom: 6px;
  width: 1px; background: var(--line);
}
.tl-item { position: relative; padding-bottom: var(--s5); }
.tl-item:last-child { padding-bottom: 0; }
.tl-item::before {
  content: ""; position: absolute; left: calc(-1 * var(--s6) + 4px); top: 5px;
  width: 7px; height: 7px; border-radius: 99px;
  background: var(--surface); border: 1.5px solid var(--faint);
}
.tl-item.accent::before { border-color: var(--accent); background: var(--accent); }
.tl-time { font-family: var(--font-mono); font-size: var(--text-2xs); color: var(--faint); }

/* meter */
.meter { height: 6px; border-radius: 99px; background: var(--bg-sunken); overflow: hidden; }
.meter > i { display: block; height: 100%; border-radius: 99px; background: var(--accent);
  transition: width 600ms var(--ease); }
/* work whose size is not known yet: a short fill sweeps across instead of growing */
.meter.indeterminate > i { width: 35%; animation: meter-sweep 1.4s var(--ease) infinite; }
@keyframes meter-sweep { from { transform: translateX(-100%); } to { transform: translateX(290%); } }
.meter.bad > i { background: var(--bad); }
/* the global reduced-motion rule would turn the sweep into a flicker; hold a soft full bar */
@media (prefers-reduced-motion: reduce) {
  .meter.indeterminate > i { animation: none; width: 100%; background: var(--accent-soft); }
}

/* ---------- 13. BOARD (kanban) ---------- */
.board { display: grid; grid-template-columns: repeat(4, minmax(260px,1fr)); gap: var(--s4); align-items: start; }
.board-col { background: var(--bg-sunken); border: 1px solid var(--line-soft); border-radius: var(--radius-lg); padding: var(--s3); }
.board-col-head { display: flex; align-items: center; gap: var(--s2); padding: var(--s2) var(--s2) var(--s3); }
.board-col-head .count { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--muted); }
.board-card {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  padding: var(--s3) var(--s4); box-shadow: var(--shadow-sm); cursor: grab;
  transition: box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.board-card + .board-card { margin-top: var(--s2); }
.board-card:hover { box-shadow: var(--shadow); transform: translateY(-1px); border-color: var(--faint); }
.board-card.dragging { opacity: .4; }
.board-col.drop-target { border-color: var(--accent-line); background: var(--accent-soft); }
.board-card h4 { font-size: var(--text-base); font-weight: 500; line-height: 1.4; }

/* ---------- 14. OVERLAYS ---------- */
.scrim {
  position: fixed; inset: 0; z-index: 50;
  background: color-mix(in srgb, var(--bg-sunken) 72%, transparent);
  backdrop-filter: blur(3px);
  opacity: 0; pointer-events: none; visibility: hidden;
  transition: opacity var(--dur) var(--ease), visibility 0s linear var(--dur);
}
.scrim.on { opacity: 1; pointer-events: auto; visibility: visible; transition-delay: 0s; }

.modal {
  position: fixed; z-index: 60; left: 50%; top: 50%;
  translate: -50% -50%;
  width: min(520px, calc(100vw - 32px));
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lg);
  /* visibility (delayed until the fade ends) keeps a closed dialog out of the Tab order */
  opacity: 0; scale: .97; pointer-events: none; visibility: hidden;
  transition: opacity var(--dur) var(--ease), scale var(--dur) var(--ease), visibility 0s linear var(--dur);
}
.modal.on { opacity: 1; scale: 1; pointer-events: auto; visibility: visible; transition-delay: 0s; }
.modal-head { padding: var(--s5) var(--s5) var(--s3); }
.modal-head h3, .drawer-head h3 { font-family: var(--font-display); font-weight: var(--display-weight);
  font-size: var(--text-xl); letter-spacing: var(--display-tracking); }
.modal-body { padding: 0 var(--s5) var(--s5); color: var(--ink-2); }
.modal-foot { display: flex; justify-content: flex-end; gap: var(--s2);
  padding: var(--s4) var(--s5); border-top: 1px solid var(--line-soft); background: var(--surface-2);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg); }

.drawer {
  position: fixed; z-index: 60; inset: 0 0 0 auto;
  width: min(460px, 100vw); background: var(--surface);
  border-left: 1px solid var(--line); box-shadow: var(--shadow-lg);
  transform: translateX(100%); transition: transform 240ms var(--ease), visibility 0s linear 240ms;
  display: flex; flex-direction: column;
  /* hidden once it has slid away, not just moved, so its controls leave the Tab order */
  visibility: hidden;
}
.drawer.on { transform: none; visibility: visible; transition-delay: 0s; }
.drawer-head { display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--s4); padding: var(--s5); border-bottom: 1px solid var(--line-soft); }
.drawer-body { flex: 1; overflow-y: auto; padding: var(--s5); }
.drawer-foot { padding: var(--s4) var(--s5); border-top: 1px solid var(--line-soft);
  display: flex; gap: var(--s2); justify-content: flex-end; background: var(--surface-2); }

/* command palette */
.cmdk {
  position: fixed; z-index: 70; left: 50%; top: 14vh; translate: -50% 0;
  width: min(560px, calc(100vw - 32px));
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow-lg); overflow: hidden;
  opacity: 0; scale: .98; pointer-events: none;
  transition: opacity 140ms var(--ease), scale 140ms var(--ease);
}
.cmdk.on { opacity: 1; scale: 1; pointer-events: auto; }
.cmdk-input { display: flex; align-items: center; gap: var(--s3); padding: var(--s4) var(--s5);
  border-bottom: 1px solid var(--line-soft); }
.cmdk-input svg { width: 17px; height: 17px; color: var(--faint); }
.cmdk-input input { flex: 1; border: 0; background: none; outline: none; font-size: var(--text-md); }
.cmdk-input input::placeholder { color: var(--faint); }
.cmdk-list { max-height: 320px; overflow-y: auto; padding: var(--s2); }
.cmdk-item {
  display: flex; align-items: center; gap: var(--s3); width: 100%;
  padding: var(--s2) var(--s3); border-radius: var(--radius-sm);
  font-size: var(--text-base); color: var(--ink-2); text-align: left;
}
.cmdk-item svg { width: 16px; height: 16px; opacity: .7; }
.cmdk-item .kbd { margin-left: auto; }
.cmdk-item.cursor { background: var(--accent-soft); color: var(--accent-on-soft); }
.cmdk-empty { padding: var(--s8); text-align: center; color: var(--muted); font-size: var(--text-sm); }

/* toasts */
.toasts { position: fixed; z-index: 80; right: var(--s5); bottom: var(--s5);
  display: flex; flex-direction: column-reverse; gap: var(--s2); pointer-events: none; }
.toast {
  display: flex; align-items: flex-start; gap: var(--s3);
  min-width: 280px; max-width: 360px; padding: var(--s3) var(--s4);
  background: var(--surface); border: 1px solid var(--line); border-left: 3px solid var(--accent);
  border-radius: var(--radius); box-shadow: var(--shadow-lg);
  pointer-events: auto; animation: toast-in 260ms var(--ease);
}
.toast.ok  { border-left-color: var(--ok); }
.toast.bad { border-left-color: var(--bad); }
.toast.out { animation: toast-out 180ms var(--ease) forwards; }
.toast > svg { width: 17px; height: 17px; flex-shrink: 0; margin-top: 2px; }
.toast b { font-size: var(--text-sm); font-weight: 600; display: block; }
.toast p { font-size: var(--text-sm); color: var(--muted); }
@keyframes toast-in { from { opacity: 0; transform: translateY(8px) scale(.98); } }
@keyframes toast-out { to { opacity: 0; transform: translateX(12px); } }

/* dropdown menu */
.menu {
  position: absolute; z-index: 65; min-width: 180px; padding: var(--s1);
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--shadow-lg);
  animation: menu-in 120ms var(--ease);
}
@keyframes menu-in { from { opacity: 0; transform: translateY(-4px); } }
.menu button {
  display: flex; align-items: center; gap: var(--s3); width: 100%;
  padding: 6px var(--s3); border-radius: var(--radius-sm);
  font-size: var(--text-sm); color: var(--ink-2); text-align: left;
}
.menu button:hover { background: var(--bg-sunken); color: var(--ink); }
/* keyboard focus fills the item like hover; the ring sits inside so the menu edge never crowds it */
.menu button:focus-visible { outline-offset: -2px; background: var(--bg-sunken); color: var(--ink); }
.menu button.danger { color: var(--bad); }
.menu button.danger:focus-visible { background: var(--bad-soft); color: var(--bad); }
.menu button:disabled { color: var(--faint); cursor: default; }
.menu button:disabled:hover { background: transparent; }
.menu button.danger:hover { background: var(--bad-soft); }
.menu button svg { width: 14px; height: 14px; opacity: .7; }
.menu hr { border: 0; border-top: 1px solid var(--line-soft); margin: var(--s1) 0; }

/* tooltip */
[data-tip] { position: relative; }
/* The bubble exists only while hovered. An invisible absolutely positioned bubble still counts
   as scrollable overflow, so a long tooltip inside a .table-wrap used to give the table a
   phantom horizontal scroll. A keyframe replaces the opacity transition for the fade-in. */
[data-tip]::after {
  content: attr(data-tip); position: absolute; bottom: calc(100% + 7px); left: 50%;
  translate: -50% 0; white-space: nowrap;
  padding: 4px var(--s2); border-radius: 5px;
  background: var(--surface); color: var(--ink); border: 1px solid var(--line); box-shadow: var(--shadow-sm);
  font-size: var(--text-xs); font-weight: 500;
  display: none; pointer-events: none;
  z-index: 90;
}
[data-tip]:hover::after { display: block; animation: tip-in 120ms var(--ease); }
@keyframes tip-in { from { opacity: 0; } }

/* Flip below when there is no room above: anything in the topbar, plus an
   opt-in for other elements pinned near the top of the viewport. */
.topbar [data-tip]::after,
[data-tip][data-tip-pos="bottom"]::after {
  bottom: auto;
  top: calc(100% + 7px);
}
/* Open to the left, centred on the trigger: for a control at the end of a table row, where a
   bubble above would sit under the sticky header and one to the side would leave the scrolling
   .table-wrap and be clipped. */
[data-tip][data-tip-pos="left"]::after {
  bottom: auto; left: auto; top: 50%; right: calc(100% + 7px); translate: 0 -50%;
}
/* Inside a table a cell clips its content and the wrap scrolls, so the bubble above would be cut
   off. tables.js draws those tooltips as a .tip-pop on <body> instead, in the same look. A long
   one wraps within the viewport (less tables.js's 7px gap each side) rather than running off a
   phone's screen. */
.table-wrap [data-tip]::after { content: none; }
.tip-pop {
  position: fixed; z-index: 90; pointer-events: none;
  max-width: min(320px, calc(100vw - var(--s4))); overflow-wrap: break-word;
  padding: 4px var(--s2); border-radius: 5px;
  background: var(--surface); color: var(--ink); border: 1px solid var(--line); box-shadow: var(--shadow-sm);
  font-size: var(--text-xs); font-weight: 500; animation: tip-in 120ms var(--ease);
}
/* Keep a tooltip inside the frame when its trigger sits at the right edge. */
[data-tip][data-tip-align="right"]::after { left: auto; right: 0; translate: 0 0; }
[data-tip][data-tip-align="left"]::after  { left: 0; right: auto; translate: 0 0; }

/* ---- panels docked over a canvas ----
   A map, a flow editor or a chart pane wants its controls over the content
   rather than beside it: the content keeps the full width and the panel is
   dismissible. The parent needs `position: relative`. Set the width you want;
   280px is only a starting point. */
.dock {
  position: absolute; z-index: 6; top: var(--s3); bottom: var(--s3);
  width: 280px; display: flex; flex-direction: column; cursor: default;
}
.dock-l { left: var(--s3); }
.dock-r { right: var(--s3); }
.dock > .card {
  display: flex; flex-direction: column; max-height: 100%; overflow: hidden;
  background: color-mix(in srgb, var(--surface) 93%, transparent);
  backdrop-filter: blur(10px); box-shadow: var(--shadow-lg);
}
.dock > .card > .card-body { overflow: auto; min-height: 0; }

/* the same chrome as a cluster rather than a panel: zoom controls, a collapsed
   dock's tab, a line of status. Corner is one of .tl .tr .bl .br */
.dock-bar {
  position: absolute; z-index: 7; cursor: default;
  display: flex; align-items: center; gap: var(--s1); padding: 3px;
  border-radius: var(--radius-full);
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  border: 1px solid var(--line); box-shadow: var(--shadow-sm);
  backdrop-filter: blur(8px);
  font-size: var(--text-xs); color: var(--ink-2); white-space: nowrap;
}
.dock-bar.tl { left: var(--s3); top: var(--s3); }
.dock-bar.tr { right: var(--s3); top: var(--s3); }
.dock-bar.bl { left: var(--s3); bottom: var(--s3); }
.dock-bar.br { right: var(--s3); bottom: var(--s3); }
/* icon buttons sit flush; words need their own breathing room */
.dock-bar .icon-btn { width: 26px; height: 26px; }
/* a bare icon in the bar (the collapsed dock's tab) has no .btn/.icon-btn to
   size it, and an unsized svg takes the 300x150 replaced default and squeezes
   the label to one word per line */
.dock-bar > svg { width: 15px; height: 15px; flex: none; }
.dock-bar > span { padding: 0 var(--s2); }

/* ---------- 15. STATES ---------- */
.empty { display: grid; place-items: center; text-align: center; padding: var(--s12) var(--s6); gap: var(--s4); }
.empty-art {
  width: 52px; height: 52px; border-radius: var(--radius-lg);
  display: grid; place-items: center; background: var(--bg-sunken); color: var(--faint);
  border: 1px solid var(--line-soft);
}
.empty-art svg { width: 22px; height: 22px; }
.empty h3 { font-family: var(--font-display); font-weight: var(--display-weight);
  font-size: var(--text-lg); letter-spacing: var(--display-tracking); }
.empty p { color: var(--muted); max-width: 40ch; font-size: var(--text-sm); }

.skeleton {
  border-radius: var(--radius-sm);
  background: linear-gradient(90deg, var(--bg-sunken) 25%, var(--line-soft) 50%, var(--bg-sunken) 75%);
  background-size: 200% 100%; animation: shimmer 1.4s linear infinite;
}
@keyframes shimmer { to { background-position: -200% 0; } }

.callout {
  display: flex; gap: var(--s3); padding: var(--s4);
  border-radius: var(--radius); border: 1px solid var(--line);
  background: var(--surface-2); font-size: var(--text-sm);
}
.callout svg { width: 17px; height: 17px; flex-shrink: 0; margin-top: 1px; }
.callout b { display: block; margin-bottom: 2px; }
.callout.info { background: var(--info-soft); border-color: transparent; color: var(--info); }
.callout.warn { background: var(--warn-soft); border-color: transparent; color: var(--warn); }
.callout.bad  { background: var(--bad-soft);  border-color: transparent; color: var(--bad); }
.callout.ok   { background: var(--ok-soft);   border-color: transparent; color: var(--ok); }
.callout .body { color: var(--ink-2); }
.callout.info .body, .callout.warn .body, .callout.bad .body, .callout.ok .body { color: inherit; }

/* ---------- 16. CHARTS ---------- */
.chart { width: 100%; overflow: visible; }
.chart .grid-line { stroke: var(--line-soft); stroke-width: 1; }
.chart .axis-text { fill: var(--faint); font-family: var(--font-mono); font-size: var(--text-2xs); }
/* .chart-axis: HTML labels under a bar chart drawn with preserveAspectRatio="none", which
   stretches the bars to the card's width but would distort SVG text. One cell per bar group;
   an empty span keeps a slot. .center centres each label under its slot. */
.chart-axis {
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr);
  margin-top: var(--s1); font-family: var(--font-mono); font-size: var(--text-2xs); color: var(--muted);
}
.chart-axis > span { white-space: nowrap; }
.chart-axis.center > span { display: flex; justify-content: center; }
@media (max-width: 860px) { .chart-axis > .alt { visibility: hidden; } }
.chart .series-line { fill: none; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.chart .bar { transition: opacity 120ms var(--ease); }
.chart .bar:hover { opacity: .75; }
.chart .hover-line { stroke: var(--faint); stroke-width: 1; stroke-dasharray: 3 3; }
.legend { display: flex; flex-wrap: wrap; gap: var(--s4); font-size: var(--text-xs); color: var(--muted); }
.legend i { width: 8px; height: 8px; border-radius: 2px; display: inline-block; margin-right: 6px; }

.chart-tip {
  position: fixed; z-index: 90; pointer-events: none;
  background: var(--ink); color: var(--bg);
  padding: var(--s2) var(--s3); border-radius: var(--radius-sm);
  font-size: var(--text-xs); box-shadow: var(--shadow-lg);
  opacity: 0; transition: opacity 100ms var(--ease);
  white-space: nowrap;
}
.chart-tip.on { opacity: 1; }
.chart-tip .k { color: color-mix(in srgb, var(--bg) 60%, var(--ink)); }
.chart-tip .v { font-family: var(--font-mono); }

/* draw-in animation */
.animate-draw { stroke-dasharray: var(--len); stroke-dashoffset: var(--len);
  animation: draw 900ms var(--ease) forwards; }
@keyframes draw { to { stroke-dashoffset: 0; } }
.animate-rise { transform-origin: bottom; animation: rise 500ms var(--ease) backwards; }
@keyframes rise { from { transform: scaleY(0); opacity: 0; } }
.animate-fade { animation: fade 400ms var(--ease) backwards; }
@keyframes fade { from { opacity: 0; transform: translateY(6px); } }

/* View entry: one short fade, no movement, no stagger.
   Opacity only and deliberately brief, so an app that re-renders the view
   (loading skeleton then data, a filter, a poll) restarts a fade the eye
   barely registers instead of a half-finished slide. Nothing shifts position,
   so a height change between renders never reads as a jolt. */
.view > * { animation: view-in var(--dur-view) var(--ease) backwards; }
@keyframes view-in { from { opacity: 0; } }

/* ---------- 17. RESPONSIVE ---------- */
.mobile-only { display: none; }

@media (max-width: 1180px) {
  .g4 { grid-template-columns: repeat(2, minmax(0,1fr)); }
  .g-2-1, .g-1-2, .g3 { grid-template-columns: 1fr; }
  .span2 { grid-column: auto; }
  .board { grid-template-columns: repeat(2, minmax(240px,1fr)); }
}
@media (max-width: 860px) {
  /* the closed drawer is hidden, not just moved, so its links leave the Tab order */
  .sidebar { transform: translateX(-100%); visibility: hidden;
    transition: transform var(--dur) var(--ease), visibility 0s linear var(--dur); }
  .app.nav-open .sidebar { transform: none; visibility: visible; box-shadow: var(--shadow-lg); transition-delay: 0s; }
  .app .main, .app.collapsed .main { margin-left: 0; }
  .mobile-only { display: inline-flex; }
  .desktop-only { display: none !important; }
  .view { padding: var(--s6) var(--s4) var(--s10); }
  .topbar { padding: 0 var(--s4); }
  .searchbtn { min-width: 0; width: 32px; padding: 0; justify-content: center; }
  .searchbtn span, .searchbtn .kbd { display: none; }
  .g2 { grid-template-columns: 1fr; }
  .page-title { font-size: var(--text-2xl); }
  .form-row { grid-template-columns: minmax(0, 1fr); gap: var(--s3); padding: var(--s5) 0; }
  .board { grid-template-columns: 1fr; }
  /* on a phone the header scrolls away with the page: a stuck header would cover a third of
     a short screen, and the columns there are narrow enough to read at a glance */
  .tbl thead th { transform: none; }
  .toasts { left: var(--s4); right: var(--s4); bottom: var(--s4); }
  .toast { max-width: none; }
}

/* .no-motion freezes entry animations — used for screenshots and visual diffs */
.no-motion, .no-motion *, .no-motion *::before, .no-motion *::after {
  animation: none !important; transition: none !important;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

/* ============================================================
   18. BUILDER / OPERATIONS PRIMITIVES
   Added to cover the element types in the ticketbot console:
   rule builders, condition rows, log streams, diffs, secrets,
   auth screens and master→detail navigation.
   ============================================================ */

/* ---- detail navigation ---- */
.back-row { display: flex; align-items: center; gap: var(--s2); margin-bottom: var(--s4); }
.back-link {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--text-sm); font-weight: 500; color: var(--muted);
  padding: 4px var(--s2) 4px var(--s1); border-radius: var(--radius-sm);
  transition: color var(--dur) var(--ease), background var(--dur) var(--ease);
}
.back-link:hover { color: var(--ink); background: var(--bg-sunken); }
.back-link svg { width: 14px; height: 14px; }

.meta-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); overflow: hidden;
}
/* the hairlines are each cell's right and bottom shadow. The grid clips the ones that land
   on its own border, and the cells have no background to paint over a neighbour's, so a
   last row that is not full just ends instead of leaving a bare strip or an empty cell. */
.meta-grid > div { padding: var(--s3) var(--s4); box-shadow: 1px 0 0 var(--line), 0 1px 0 var(--line); }
/* no divider after the last cell: in a short last row it would outline an empty cell */
.meta-grid > div:last-child { box-shadow: 0 1px 0 var(--line); }
.meta-grid .eyebrow { margin-bottom: 4px; }
.meta-grid .val { font-size: var(--text-base); font-weight: 500; }

/* ---- unsaved-state indicator ---- */
.dirty-dot {
  width: 7px; height: 7px; border-radius: 99px; background: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft); flex-shrink: 0;
}

/* ---- full-width system banner ---- */
.banner {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s3) var(--s5); border-radius: var(--radius);
  background: var(--info-soft); color: var(--info);
  font-size: var(--text-sm); border: 1px solid transparent;
}
/* a banner announces what follows; it never sits flush against it */
.banner:not(:last-child) { margin-bottom: var(--s3); }
.banner svg { width: 16px; height: 16px; flex-shrink: 0; }
.banner.warn { background: var(--warn-soft); color: var(--warn); }
.banner.bad  { background: var(--bad-soft);  color: var(--bad); }
.banner .btn { margin-left: auto; }
.banner-sticky { position: sticky; top: calc(var(--topbar-h) + var(--s3)); z-index: 20; box-shadow: var(--shadow); }

/* ---- a pointer drag in progress ----
   Put .is-dragging on the shell for the length of the gesture. Without it the
   drag starts a text selection that runs through everything the pointer passes
   over, and every control it crosses shows its own cursor. `cursor` is set once
   on the shell and inherited, so the modifier changes it everywhere at once;
   the !important is what beats each control's own cursor, and is scoped to a
   state that only exists mid-gesture. Docked chrome stops taking pointer events
   so the drag passes over it instead of ending on it. */
.is-dragging, .is-dragging * { user-select: none; -webkit-user-select: none; }
.is-dragging { cursor: grabbing; }
.is-dragging * { cursor: inherit !important; }
.is-dragging.drag-link { cursor: crosshair; }
.is-dragging.drag-copy { cursor: copy; }
.is-dragging.drag-box { cursor: crosshair; }
.is-dragging.drag-resize { cursor: col-resize; }
.is-dragging .dock, .is-dragging .dock-bar { pointer-events: none; }

/* ---- condition builder ---- */
.cond {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface-2); overflow: hidden;
}
.cond-tabs {
  display: flex; align-items: center; gap: var(--s1); padding: var(--s2) var(--s3);
  border-bottom: 1px solid var(--line-soft);
  /* in a 330px dock the mode tabs plus the match switch overflow; the switch drops to a second line */
  flex-wrap: wrap; row-gap: var(--s2);
}
.cond-tabs button {
  padding: 3px var(--s3); border-radius: var(--radius-sm);
  font-size: var(--text-xs); font-weight: 500; color: var(--muted);
}
.cond-tabs button:hover { color: var(--ink); }
.cond-tabs button.on { background: var(--surface); color: var(--ink); box-shadow: var(--shadow-sm); }
.cond-tabs .grow { flex: 1; }

.cond-rows { padding: var(--s3); display: flex; flex-direction: column; gap: var(--s2); }
.cond-row { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.cond-row .select, .cond-row .input { width: auto; height: 30px; font-size: var(--text-sm); }
/* The value slot. A select is as wide as its longest option and a flex item will not shrink
   below its content, so a long member or status name pushed the row past the card's edge.
   min-width: 0 lets the control shrink to the slot and clip its text instead. */
.cond-value { min-width: 200px; }
.cond-value > *, .cond-value .chips > * { min-width: 0; }
.cond-value .select { max-width: 100%; }
/* Field and operator keep a usable width on a wide row; the stacked layout below lets them
   shrink like everything else. Set here, not inline, so the container query can override. */
.cond-field { min-width: 180px; }
.cond-op { min-width: 130px; }
/* A chip's label wraps rather than running out of the row (a long company or contact name);
   the remove button stays on the first line. Shared with the condition hover card. */
.cond-value .chip, .cond-pop-row .chip { max-width: 100%; white-space: normal; overflow-wrap: anywhere; align-items: flex-start; }
.cond-value .chip-x, .cond-pop-row .chip-x { flex: none; }
.cond-join {
  width: var(--cond-join-w); flex-shrink: 0; height: 30px; padding: 0 6px;
  font-size: var(--text-xs); font-weight: 600; text-transform: uppercase;
  letter-spacing: .06em; color: var(--accent-on-soft);
  background: var(--accent-soft); border: 1px solid var(--accent-line);
  border-radius: var(--radius-sm); text-align: center;
}
/* as a <select> the browser centres the text; as a static word it is a plain
   flex item, and the line box would sit at the top of the fixed height. */
.cond-join:not(select) { display: grid; place-items: center; line-height: 1; }
.cond-row > .cond-join-spacer { width: var(--cond-join-w); flex-shrink: 0; }

.cond-foot {
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  padding: var(--s3); border-top: 1px solid var(--line-soft); background: var(--surface);
}
.cond-result { font-size: var(--text-xs); font-family: var(--font-mono); }
.cond-result.ok  { color: var(--ok); }
.cond-result.err { color: var(--bad); }

/* A condition builder is as often in a side panel as in a full-width card, and
   a row of field/operator/value does not fit in 330px. Below that the row
   stacks and the join moves to a line of its own. The container decides, not
   the viewport, so the same markup works in both places.
   width:100% is not cosmetic: container-type stops the box sizing itself from
   its contents, so in any shrink-to-fit parent (a flex row, a float) it would
   otherwise collapse to nothing. One join width, defined once — two literals
   that had to match is what let the select clip "and" to "a". */
.cond { container-type: inline-size; width: 100%; --cond-join-w: 68px; }
@container (max-width: 430px) {
  /* one control per line, in source order, and the row gets an edge so the
     stack still reads as one condition rather than five loose controls */
  .cond-row {
    display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s2);
    padding: var(--s2); border: 1px solid var(--line);
    border-radius: var(--radius-sm); background: var(--surface);
  }
  .cond-row > *, .cond-row > .cond-field, .cond-row > .cond-op { min-width: 0; }
  .cond-row > .select, .cond-row > .input { width: 100%; }
  .cond-row > .cond-join { justify-self: start; }
  .cond-row > .cond-join-spacer { display: none; }
  .cond-row > .icon-btn { justify-self: end; }
  .cond-foot { flex-direction: column; align-items: stretch; }
}

/* ---- value chips + typeahead ---- */
.chips { display: flex; align-items: center; gap: var(--s1); flex-wrap: wrap; }
.chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 4px 2px var(--s2); border-radius: var(--radius-sm);
  background: var(--bg-sunken); border: 1px solid var(--line);
  font-size: var(--text-xs); white-space: nowrap;
}
.chip-x {
  display: grid; place-items: center; width: 17px; height: 17px;
  border-radius: 4px; color: var(--muted); line-height: 1;
}
/* Compact controls stay visually small but take a 24px pointer target.
   Mark them .hit-expand so the audit knows the expansion is deliberate. */
.hit-expand { position: relative; }
.hit-expand::after {
  content: ""; position: absolute; left: 50%; top: 50%; translate: -50% -50%;
  width: max(100%, 24px); height: max(100%, 24px);
}
.chip-x svg { width: 10px; height: 10px; stroke-width: 2.6; }
.chip-x:hover { background: var(--bad-soft); color: var(--bad); }
.chip-add {
  padding: 2px var(--s2); border-radius: var(--radius-sm);
  border: 1px dashed var(--line); color: var(--muted); font-size: var(--text-xs);
}
.chip-add:hover { border-color: var(--accent); color: var(--accent); }

.typeahead { position: relative; }
/* the input fills the box, so a box that shrinks on a phone takes its input with it */
.typeahead .input { width: 100%; }
.typeahead-pop {
  position: absolute; z-index: 25; top: calc(100% + 4px); left: 0; min-width: 240px;
  padding: var(--s1); background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow-lg);
}
.typeahead-pop button {
  display: flex; align-items: center; gap: var(--s2); width: 100%;
  padding: 5px var(--s2); border-radius: var(--radius-sm);
  font-size: var(--text-sm); color: var(--ink-2); text-align: left;
}
.typeahead-pop button:hover, .typeahead-pop button.cursor { background: var(--accent-soft); color: var(--accent-on-soft); }
.typeahead-pop .cell-sub { margin-left: auto; }

/* ---- code / preview blocks ---- */
.code {
  font-family: var(--font-mono); font-size: var(--text-xs); line-height: 1.6;
  padding: var(--s3); border-radius: var(--radius-sm);
  background: var(--bg-sunken); border: 1px solid var(--line-soft);
  color: var(--ink-2); overflow-x: auto; white-space: pre; margin: 0;
}
.code.inline { display: inline; padding: 1px 5px; white-space: normal; }
.code .tok-field { color: var(--info); }
.code .tok-op    { color: var(--muted); }
.code .tok-val   { color: var(--ok); }
.code .tok-join  { color: color-mix(in srgb, var(--accent) 70%, var(--ink)); font-weight: 500; }

/* ---- log stream ---- */
.filter-bar {
  display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
  padding: var(--s3) var(--s4); border-bottom: 1px solid var(--line-soft);
}
.filter-bar .select, .filter-bar .input { height: 30px; font-size: var(--text-sm); width: auto; }

.log-list {
  font-family: var(--font-mono); font-size: var(--text-xs); line-height: 1.55;
  max-height: 560px; overflow: auto;
}
.log-row {
  display: grid; grid-template-columns: 72px 54px 1fr; gap: var(--s3);
  padding: 4px var(--s4); border-bottom: 1px solid var(--line-soft);
  border-left: 2px solid transparent;
}
.log-row:hover { background: var(--surface-2); }
.log-time { color: var(--muted); }
.log-level { font-weight: 500; color: var(--muted); letter-spacing: .04em; }
.log-msg { color: var(--ink-2); word-break: break-word; }
.log-row.error { border-left-color: var(--bad);  background: color-mix(in srgb, var(--bad) 7%, var(--surface)); }
.log-row.error .log-level { color: var(--bad); }
.log-row.warn  { border-left-color: var(--warn); }
.log-row.warn .log-level { color: var(--warn); }
.log-row.debug .log-msg, .log-row.debug .log-time { color: var(--faint); }
.log-attr { color: var(--muted); margin-left: var(--s2); }
.log-attr b { color: var(--ink-2); font-weight: 400; }

/* ---- diff table ---- */
.diff-table { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.diff-table th {
  text-align: left; font-size: var(--text-2xs); font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase; color: var(--faint);
  padding: var(--s2) var(--s3); border-bottom: 1px solid var(--line);
}
.diff-table td { padding: var(--s2) var(--s3); border-bottom: 1px solid var(--line-soft); vertical-align: top; }
.diff-table .field { font-weight: 500; white-space: nowrap; }
.diff-old, .diff-new {
  font-family: var(--font-mono); font-size: var(--text-xs);
  padding: 1px 5px; border-radius: 4px;
}
.diff-old { background: var(--bad-soft); color: var(--bad); }
.diff-new { background: var(--ok-soft);  color: var(--ok); }

/* ---- quoted note / message preview ---- */
.note {
  border-left: 2px solid var(--line); padding: var(--s2) 0 var(--s2) var(--s3);
  margin-top: var(--s2);
}
.note-author { font-size: var(--text-xs); font-weight: 600; color: var(--muted); margin-bottom: 2px; }
.note-text { font-size: var(--text-sm); color: var(--ink-2); white-space: pre-wrap; }

/* ---- typed event cards on a timeline ---- */
.events { position: relative; padding-left: var(--s6); }
.events::before {
  content: ""; position: absolute; left: 7px; top: 8px; bottom: 8px;
  width: 1px; background: var(--line);
}
.event { position: relative; padding-bottom: var(--s3); }
.event::before {
  content: ""; position: absolute; left: calc(-1 * var(--s6) + 3px); top: 13px;
  width: 9px; height: 9px; border-radius: 99px;
  background: var(--surface); border: 1.5px solid var(--faint);
}
.event.ok::before     { border-color: var(--ok);   background: var(--ok); }
.event.bad::before    { border-color: var(--bad);  background: var(--bad); }
.event.warn::before   { border-color: var(--warn); background: var(--warn); }
.event.accent::before { border-color: var(--accent); background: var(--accent); }
.event-card {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface); padding: var(--s3) var(--s4);
}
.event.bad .event-card { border-color: color-mix(in srgb, var(--bad) 35%, var(--line)); background: color-mix(in srgb, var(--bad-soft) 40%, var(--surface)); }
.event-head { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.event-head .title { font-size: var(--text-sm); font-weight: 600; }
.event-head .src {
  font-family: var(--font-mono); font-size: var(--text-2xs);
  padding: 1px 5px; border-radius: 4px; background: var(--bg-sunken); color: var(--muted);
}
.event-head .time { margin-left: auto; font-family: var(--font-mono); font-size: var(--text-2xs); color: var(--faint); }
.event-body { margin-top: var(--s2); font-size: var(--text-sm); color: var(--ink-2); }
.event-detail { font-size: var(--text-sm); color: var(--muted); }
/* an icon inside a detail line sits inline at text size; the global svg rule would let it grow to the card */
.event-detail svg { display: inline-block; width: 14px; height: 14px; vertical-align: -3px; stroke-width: 1.8; }
.event-error { font-family: var(--font-mono); font-size: var(--text-xs); color: var(--bad); margin-top: var(--s2); }

/* ---- secrets, keys, recovery codes ---- */
.secret {
  display: flex; align-items: center; gap: var(--s2);
  padding: var(--s3); border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg-sunken); font-family: var(--font-mono); font-size: var(--text-sm);
  word-break: break-all;
}
.secret.reveal { border-style: dashed; border-color: var(--accent-line); background: var(--accent-soft); color: var(--accent-on-soft); }
.recovery-codes {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: var(--s2);
}
.recovery-codes span {
  font-family: var(--font-mono); font-size: var(--text-sm); text-align: center;
  padding: var(--s2); border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface-2);
}
.qr {
  width: 132px; height: 132px; flex-shrink: 0; padding: var(--s2);
  border: 1px solid var(--line); border-radius: var(--radius); background: #fff;
}

/* ---- auth screens ---- */
.auth {
  min-height: 100vh; display: grid; place-items: center;
  padding: var(--s6); background: var(--bg);
}
.auth-card {
  width: 100%; max-width: 372px;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius-lg); box-shadow: var(--shadow);
  padding: var(--s8) var(--s6);
  display: flex; flex-direction: column; gap: var(--s5);
}
.auth-brand { display: flex; align-items: center; gap: var(--s3); }
.auth-brand .brand-mark { width: var(--s8); height: var(--s8); }
.auth-card h1 {
  font-family: var(--font-display); font-weight: var(--display-weight);
  letter-spacing: var(--display-tracking); font-size: var(--text-xl);
}
.auth-card h1 + p { color: var(--muted); font-size: var(--text-sm); margin-top: var(--s1); }
.auth-foot { text-align: center; font-size: var(--text-sm); color: var(--muted); }
.otp-input {
  text-align: center; font-family: var(--font-mono); font-size: var(--text-xl);
  letter-spacing: .5em; height: 48px; text-indent: .5em;
}

.pwd-reqs { display: flex; flex-direction: column; gap: 3px; margin-top: var(--s1); }
.pwd-req { display: flex; align-items: center; gap: 6px; font-size: var(--text-xs); color: var(--muted); }
.pwd-req svg { width: 12px; height: 12px; stroke-width: 2.6; color: var(--faint); }
.pwd-req.ok { color: var(--ok); }
.pwd-req.ok svg { color: var(--ok); }

@media (max-width: 860px) {
  .log-row { grid-template-columns: 62px 48px 1fr; gap: var(--s2); padding-inline: var(--s3); }
  .rule-head { flex-wrap: wrap; }
}

/* auth screens take over the whole frame */
.app.auth-mode .sidebar, .app.auth-mode .topbar { display: none; }
.app.auth-mode .main { margin-left: 0; }
.app.auth-mode .view { padding: 0; max-width: none; }
/* the parent crumb ships its own link + separator; display:contents puts them
   in the row itself so they take the same gap as the rest of the trail */
.crumbs #crumb-parent { display: contents; }
.crumbs #crumb-parent a { color: var(--muted); }
.crumbs #crumb-parent a:hover { color: var(--ink); }
/* a narrow topbar keeps only the page name, so every page still says where you are */
@media (max-width: 860px) {
  .crumbs { min-width: 0; }
  .crumbs #crumb-parent { display: none; }
  .crumbs .here { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
}

/* ---------- 19. MENU MARKS ---------- */
.menu button .check-mark { margin-left: auto; color: var(--accent); font-size: 11px; }
/* a checkbox item's tick sits where an icon would, an empty box of the same size when unticked */
.menu .menu-check { width: 15px; height: 15px; flex: none; display: grid; place-items: center; color: var(--ink); }
.menu .menu-check svg { width: 14px; height: 14px; }

/* ---- flow canvas ----
   A free-positioned node graph: a pannable, zoomable .plane inside a .canvas,
   wires drawn in one oversized SVG behind the cards, ports as real buttons on
   top. Card heights are fixed (88px) and the script
   places ports from those constants: a card that sized itself from its content
   would put every wire endpoint off by the difference. Overlays over the
   canvas are .dock / .dock-bar; .is-dragging on the shell covers a gesture. */

/* tone carriers: each node kind sets one pair the card and its icon chip use */
.t-start { --nc: var(--trigger); --nc-soft: color-mix(in srgb, var(--trigger) 13%, var(--surface)); }
.t-logic { --nc: var(--accent); --nc-soft: var(--accent-soft); }
.t-notify{ --nc: var(--info);   --nc-soft: var(--info-soft); }
.t-write { --nc: var(--warn);   --nc-soft: var(--warn-soft); }
.t-stop  { --nc: var(--bad);    --nc-soft: var(--bad-soft); }

.canvas {
  position: relative; width: 100%; height: 620px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background-color: var(--bg-sunken);
  overflow: hidden; touch-action: none; cursor: grab;
}
.canvas.panning { cursor: grabbing; }
.plane { position: absolute; top: 0; left: 0; width: 1px; height: 1px; transform-origin: 0 0; }
.wires { position: absolute; top: -4000px; left: -4000px; width: 8000px; height: 8000px; pointer-events: none; overflow: visible; }
.wire { fill: none; stroke: color-mix(in srgb, var(--muted) 52%, transparent); stroke-width: 2; }
.wire.hot { stroke: var(--accent); }
.wire.pick { stroke: var(--accent); stroke-width: 3; }
.wire.temp { stroke: var(--accent); stroke-dasharray: 5 5; }
.wire.ran { stroke: var(--ok); stroke-width: 3; }
.wire.dim { opacity: .3; }
.wirehit { fill: none; stroke: transparent; stroke-width: 16; pointer-events: stroke; cursor: pointer; }

.node {
  position: absolute; top: 0; left: 0; width: 248px; height: 88px;
  display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-sm); touch-action: none; cursor: grab;
  transition: box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease), opacity var(--dur) var(--ease);
}
/* the entry point reads as solid rather than tinted: a filled chip and a card
   edge carrying its hue. Both give way to .sel / .hit / .now below. */
.node.t-start { border-color: color-mix(in srgb, var(--trigger) 32%, var(--line)); }
.node.t-start .node-ico { background: var(--nc); color: var(--accent-ink); }
.node.sel { border-color: var(--nc); box-shadow: 0 0 0 3px var(--nc-soft), var(--shadow); }
/* shift-drag selection box, drawn in plane coordinates among the cards */
/* --accent-soft is mixed against the surface and so opaque; the box must show the cards under it */
.marquee { position: absolute; border: 1px dashed var(--accent); background: color-mix(in srgb, var(--accent) 12%, transparent); border-radius: var(--radius-sm); pointer-events: none; }
.node.off { opacity: .55; }
.node.dim { opacity: .34; }
.node.hit { border-color: var(--ok); box-shadow: 0 0 0 2px var(--ok-soft); }
.node.now { border-color: var(--ok); box-shadow: 0 0 0 4px var(--ok-soft), var(--shadow); }
.node.err { border-color: var(--bad); box-shadow: 0 0 0 3px var(--bad-soft); }
.node-top { display: flex; align-items: center; gap: var(--s2); padding: var(--s2) var(--s3) 0; }
.node-ico { width: 24px; height: 24px; flex: none; display: grid; place-items: center; border-radius: 6px; background: var(--nc-soft); color: var(--nc); }
.node-ico svg { width: 14px; height: 14px; }
.node-kind { font-size: var(--text-2xs); font-weight: 600; letter-spacing: .1em; text-transform: uppercase; color: var(--nc); }
.node-body { flex: 1; min-height: 0; padding: 3px var(--s3) var(--s3); }
.node-title { font-size: var(--text-sm); font-weight: 500; color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.node-sub { font-size: var(--text-xs); color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* A trigger or if card shows its condition as a count; the rows themselves are in .cond-pop,
   so a card never truncates condition text. 20px tall to fit the card's top row; the markup
   adds .hit-expand for a 24px target. */
.node-cbadge {
  flex: none; display: inline-flex; align-items: center; gap: var(--s1);
  height: 20px; padding: 0 var(--s2); border: 1px solid transparent; border-radius: var(--radius-full);
  background: var(--bg-sunken); color: var(--ink-2);
  font: 500 var(--text-2xs) var(--font-sans); cursor: help;
}
.node-cbadge svg { width: 11px; height: 11px; }
.node-cbadge:hover, .node-cbadge:focus-visible, .node-cbadge[aria-describedby] {
  background: var(--accent-soft); border-color: var(--accent-line); color: var(--accent-on-soft);
}
/* The popover is fixed to the viewport (placed by cvCondPopShow), so it reads at any zoom. */
.cond-pop {
  position: fixed; z-index: 80; width: 320px; max-height: min(420px, calc(100vh - var(--s6)));
  overflow: auto; padding: var(--s3) var(--s4) var(--s1);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow-lg); pointer-events: none; animation: tip-in 120ms var(--ease);
}
.cond-pop[hidden] { display: none; }
.cond-pop-head {
  padding-bottom: var(--s2); font-size: var(--text-2xs); font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase; color: var(--muted);
}
.cond-pop-row {
  display: flex; flex-direction: column; gap: var(--s1); padding: var(--s2) 0;
  border-top: 1px solid var(--line-soft); font-size: var(--text-xs); color: var(--ink-2);
  overflow-wrap: anywhere;
}
.cond-pop-row.flag { flex-direction: row; align-items: center; gap: var(--s2); }
.cond-pop-row.flag svg { width: 12px; height: 12px; flex: none; color: var(--ok); }
.cond-pop-row.flag.no svg { color: var(--muted); }
.cond-pop-field { font-weight: 500; color: var(--ink); }
.cond-pop-op { color: var(--muted); }
.cond-pop-row .chip.more { color: var(--muted); }
.cond-pop-code {
  padding: var(--s2) 0 var(--s3); border-top: 1px solid var(--line-soft);
  font-family: var(--font-mono); font-size: var(--text-2xs); color: var(--ink-2);
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.step-no {
  position: absolute; left: -11px; top: -11px; width: 22px; height: 22px; border-radius: 50%;
  display: grid; place-items: center; background: var(--ok); color: var(--surface);
  font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: 600;
}

/* ports are real buttons: 24px hit target, 11px dot */
.port {
  position: absolute; width: 24px; height: 24px; padding: 0; border: 0; background: none;
  transform: translate(-50%, -50%); display: grid; place-items: center; cursor: crosshair;
}
.port::before {
  content: ''; width: 11px; height: 11px; border-radius: 50%;
  background: var(--surface); border: 2px solid color-mix(in srgb, var(--muted) 52%, transparent);
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.port.wired::before { background: var(--accent); border-color: var(--accent); }
.port:hover::before { border-color: var(--accent); transform: scale(1.25); }
.port.inp { pointer-events: none; }
/* a wired input lifts its wire's end, so it takes the pointer like an output */
.port.inp.grab { pointer-events: auto; }
.port.tgt::before { background: var(--accent); border-color: var(--accent); transform: scale(1.4); }

.wlabel {
  position: absolute; transform: translate(-50%, -50%); white-space: nowrap; pointer-events: none;
  font-size: var(--text-2xs); font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
  padding: 1px 7px; border-radius: var(--radius-full);
  background: var(--surface); border: 1px solid var(--line); color: var(--muted);
}
/* a horizontal flow's captions start just right of the port, on the wire leaving it */
.wlabel.h { transform: translate(0, -50%); }
.wlabel.yes { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 36%, var(--surface)); }
.wirex {
  position: absolute; transform: translate(-50%, -50%);
  width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center;
  background: var(--surface); border: 1px solid var(--bad); color: var(--bad); box-shadow: var(--shadow-sm);
}
.wirex svg { width: 12px; height: 12px; stroke-width: 2; }
.wirex:hover { background: var(--bad); color: var(--bad-ink); }

.ghost {
  position: absolute; top: 0; left: 0; width: 248px; height: 88px;
  border: 1.5px dashed var(--accent); border-radius: var(--radius); background: var(--accent-soft);
  pointer-events: none; display: grid; place-items: center; font-size: var(--text-xs); color: var(--accent-on-soft);
}

/* the step list's draggable entries */
.pal-list { display: flex; flex-direction: column; gap: 3px; }
.pal {
  display: flex; align-items: center; gap: var(--s2); width: 100%; text-align: left;
  padding: 5px var(--s2); border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); font-size: var(--text-sm); color: var(--ink-2);
  cursor: grab; touch-action: none;
}
.pal:hover { border-color: var(--accent-line); background: var(--accent-soft); color: var(--accent-on-soft); }
.pal .swat { width: 8px; height: 8px; border-radius: 2px; background: var(--nc); flex: none; }

/* ---- trigger tabs ----
   .cv-frame stacks a strip of folder tabs on the canvas's top edge: one .cv-tab
   per trigger in firing order, the open one (.on) filled with the canvas colour so it joins it.
   The tabs scroll sideways in .cv-tabs-scroll; .cv-tabs-tools (the open
   trigger's switch and actions) stays put at the right. */
.cv-frame { display: flex; flex-direction: column; min-height: 0; }
.cv-tabs { display: flex; align-items: flex-end; gap: var(--s3); min-height: 49px; margin-bottom: -1px; position: relative; z-index: 1; }
.cv-tabs-scroll {
  flex: 1; min-width: 0; display: flex; align-items: flex-end; gap: 2px;
  padding: 0 var(--s3); overflow-x: auto; scrollbar-width: none;
}
.cv-tabs-scroll::-webkit-scrollbar { display: none; }
.cv-tab {
  flex: none; display: flex; align-items: center; gap: var(--s2); height: 48px;
  padding: 0 var(--s4); border: 1px solid transparent; border-bottom: 0;
  border-radius: var(--radius) var(--radius) 0 0; color: var(--ink-2); text-align: left; white-space: nowrap;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.cv-tab:hover { background: var(--surface-2); color: var(--ink); }
.cv-tab.on { background: var(--bg-sunken); border-color: var(--line); color: var(--ink); box-shadow: inset 0 2px 0 var(--trigger); }
.cv-tab > svg { width: 14px; height: 14px; flex: none; }
.cv-tab-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--trigger); flex: none; }
.cv-tab.off .cv-tab-dot { background: transparent; border: 2px solid var(--faint); }
/* a trigger's place in the firing order, shown once a board has two or more */
.cv-tab-no {
  min-width: 20px; height: 20px; padding: 0 var(--s1); flex: none; display: grid; place-items: center;
  border-radius: var(--radius-sm); background: var(--surface-2); color: var(--ink-2);
  font-family: var(--font-mono); font-size: var(--text-2xs);
}
.cv-tab-text { display: flex; flex-direction: column; line-height: 1.25; }
.cv-tab-name { font-size: var(--text-sm); font-weight: 600; }
.cv-tab-sub { font-size: var(--text-2xs); color: var(--muted); }
.cv-tab-new { flex: none; margin: 0 0 var(--s2) var(--s2); }
.cv-tabs-tools { flex: none; display: flex; align-items: center; gap: var(--s1); padding: 0 var(--s1) var(--s2) 0; }
.cv-tabs-tools .switch { margin-right: var(--s2); font-size: var(--text-sm); color: var(--ink-2); }

/* a step more than one trigger reaches carries this chip over its top edge; it opens the other tab */
.node-also {
  position: absolute; right: var(--s3); top: -11px; height: 22px; padding: 0 var(--s2);
  border: 1px solid var(--accent-line); border-radius: var(--radius-full); background: var(--surface-2);
  color: var(--accent-on-soft); font-size: var(--text-2xs); font-weight: 600; white-space: nowrap; cursor: pointer;
}
.node-also:hover { background: var(--accent-soft); }

/* zoom cluster and status line are .dock-bar corners; .pct is the zoom readout */
.dock-bar .pct { min-width: 46px; text-align: center; font-family: var(--font-mono); font-size: var(--text-xs); color: var(--muted); }

@media (prefers-reduced-motion: no-preference) {
  .wire.ran { stroke-dasharray: 7 6; animation: wire-dash 700ms linear infinite; }
  @keyframes wire-dash { to { stroke-dashoffset: -26; } }
}
