/* ===========================================================================
   warden — in Decipher's design language, shared by every tool of the
   ecosystem (decipher/frontend/docs/design-guidelines.md; the tokens and
   components are Sanctum's port of it): a light-first app with a dark twin, one navy brand
   hue (222), Inter for text and Montserrat for headings, generous rounding
   (12px controls, 24px page surface and dialogs) and depth from soft shadows
   plus the "blick" inset rim rather than from borders.
   App chrome: sidebar (brand + nav) · header (search, theme, account) · the
   page drawn on one large card (Decipher's PageSurface).
   =========================================================================== */
@font-face { font-family: "Inter"; font-style: normal; font-weight: 300 700; font-display: swap;
  src: url("../fonts/inter-latin.woff2") format("woff2"); }
@font-face { font-family: "Montserrat"; font-style: normal; font-weight: 500 700; font-display: swap;
  src: url("../fonts/montserrat-latin.woff2") format("woff2"); }

/* THEME — every colour token states its light and its dark value side by side
   (`light-dark()`), so the two themes cannot drift apart the way two copies of
   a token block do. Which one applies is `color-scheme`: the OS preference by
   default, or the viewer's own choice, which theme.js writes to
   <html data-theme> before the first paint. The values are Decipher's own
   (app/globals.css): --bg/--surface are its background/card, --surface-2 its
   translucent --input fill, --accent its --primary. */
:root { color-scheme: light dark; }
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark; }
:root {
  --bg:        light-dark(hsl(210 15% 97%), hsl(220 6% 0%));   /* page floor, sidebar, header */
  --surface:   light-dark(#fff, hsl(220 0% 9%));                /* the page card, cards, modal */
  /* Translucent on purpose (Decipher's --input): it darkens whatever it sits
     on by a fixed step, so one value separates from the floor, a card and a
     modal alike. The navy overlay keeps the brand's tint in the greys. */
  --surface-2: light-dark(hsl(220 40% 25% / 6%), hsl(220 40% 84% / 12%));  /* fields, wells, chips */
  --surface-hi:light-dark(hsl(220 40% 25% / 5%), hsl(220 40% 84% / 7%));   /* row / control hover */
  --border:    light-dark(hsl(220 13% 90%), hsl(220 5% 18%));
  --border-hi: light-dark(hsl(220 13% 84%), hsl(220 5% 26%));
  --text:      light-dark(hsl(222 28% 11%), hsl(0 0% 95%));
  --muted:     light-dark(hsl(220 10% 44%), hsl(220 5% 58%));
  --faint:     light-dark(hsl(220 8% 62%), hsl(220 5% 42%));
  --accent:    light-dark(hsl(222 28% 28%), hsl(222 38% 57%));  /* Decipher navy (--primary) */
  --accent-2:  light-dark(hsl(222 30% 22%), hsl(222 45% 64%));
  --accent-sf: light-dark(hsl(222 40% 28% / 9%), hsl(222 50% 60% / 16%));
  --on-accent: light-dark(hsl(210 20% 98%), #fff);
  --ring:      light-dark(hsl(222 47% 28%), hsl(222 57% 61%));
  --info:      light-dark(hsl(222 55% 86%), hsl(222 25% 18%));
  --info-fg:   light-dark(hsl(222 45% 26%), hsl(222 40% 88%));
  --success:   light-dark(hsl(142 72% 29%), hsl(142 69% 58%));
  --danger:    light-dark(hsl(0 70% 45%), hsl(0 70% 62%));
  --danger-sf: light-dark(hsl(0 70% 45% / 8%), hsl(0 70% 55% / 14%));
  /* One categorical scale, each hue a light/dark pair from Decipher's status
     palette (the --family-status strokes). Item types, project colours and file
     categories all draw from it, so a tint is always a colour the brand owns. */
  --hue-green:  light-dark(hsl(166 76% 25%), hsl(161 47% 58%));
  --hue-blue:   light-dark(hsl(210 74% 37%), hsl(208 71% 72%));
  --hue-amber:  light-dark(hsl(34 84% 33%),  hsl(35 86% 54%));
  --hue-orange: light-dark(hsl(22 78% 42%),  hsl(22 75% 62%));
  --hue-gold:   light-dark(hsl(38 85% 36%),  hsl(43 90% 60%));
  --hue-purple: light-dark(hsl(270 55% 42%), hsl(270 55% 72%));
  --hue-pink:   light-dark(hsl(330 65% 45%), hsl(330 70% 74%));
  --hue-rose:   light-dark(hsl(350 70% 42%), hsl(350 70% 72%));
  --pw:   var(--hue-green);    /* passwords */
  --note: var(--hue-blue);     /* notes */
  --file: var(--hue-amber);    /* files */
  --proj: var(--hue-purple);   /* projects */
  --kb:   var(--hue-rose);     /* knowledge */
  --star: light-dark(hsl(38 92% 48%), hsl(43 96% 58%));   /* pinned */
  /* The toast is inverted chrome (Decipher's pill): navy-black in light, near
     white in dark. */
  --inverse:    light-dark(hsl(222 28% 11% / 92%), hsl(0 0% 95% / 94%));
  --on-inverse: light-dark(#fff, hsl(222 28% 11%));
  --overlay:    light-dark(hsl(222 28% 11% / 32%), hsl(0 0% 0% / 62%));
  /* The blick: a 1px specular rim along a lifted edge (Decipher's `blick` /
     `blick-sm` utilities). It is what marks the active nav item and raised
     controls, instead of a border. */
  /* The outline button (Decipher's `outline`): its fill, inner ring and hover.
     Every control drawn in that recipe reads these three, not its own copy. */
  --btn-bg:     light-dark(#fff, #000);
  --btn-ring:   inset 0 0 0 1px light-dark(rgb(229 229 229 / .8), var(--border));
  --btn-hover:  light-dark(hsl(219 12% 95%), hsl(220 6% 12%));
  --blick:      light-dark(hsl(0 0% 100%), hsl(0 0% 16%));
  --blick-shadow: light-dark(rgb(0 0 0 / .1), rgb(0 0 0 / .5));

  --sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --heading: "Montserrat", var(--sans);
  --mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;

  --r-sm: 8px;    /* menu items, chips, small controls (rounded-lg) */
  --r:    12px;   /* every field, button and card (rounded-xl) */
  --r-lg: 24px;   /* the page surface and dialogs (rounded-3xl) */

  --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / .05);
  --shadow:    0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);
  --shadow-xl: 0 20px 25px -5px light-dark(hsl(222 28% 11% / 6%), rgb(0 0 0 / .5)),
               0 8px 10px -6px light-dark(hsl(222 28% 11% / 5%), rgb(0 0 0 / .4));
  --shadow-lg: 0 25px 50px -12px light-dark(hsl(222 28% 11% / 25%), rgb(0 0 0 / .7));
  --inset-field: inset 0 1px 1px 0 rgb(0 0 0 / .04);
  --blick-sm: 0 1px 3px 0 var(--blick-shadow), 0 1px 2px -1px var(--blick-shadow),
              inset 0 1px 0 0 color-mix(in srgb, var(--blick) 50%, transparent),
              inset 0 0 1px 0 color-mix(in srgb, var(--blick) 50%, transparent),
              inset 1px 0 0 0 color-mix(in srgb, var(--blick) 50%, transparent),
              inset -1px 0 0 0 color-mix(in srgb, var(--blick) 50%, transparent),
              inset 0 -1px 0 0 color-mix(in srgb, var(--blick) 20%, transparent);

  --sidebar-w: 240px;
  --sidebar-w-c: 60px;   /* collapsed to its icons */
  --topbar-h:  64px;
  --ctrl-h: 36px;

  /* The content rail: wide enough that a normal laptop screen is filled edge to
     edge, capped only so ultrawide monitors don't stretch list rows past
     reading length. --surface-gap is the margin the page card keeps from the
     window on the right and at the bottom (Decipher's `sm:pr-8 sm:pb-6`). */
  --content-max: 1720px;
  --content-pad: 1.5rem;
  --surface-gap: 2rem;
}
* { box-sizing: border-box; }
/* SCROLLBARS — everything scrolls, nothing draws a bar for it.
   A browser-painted scrollbar is the same foreign artefact a native checkbox is
   (see the design notes): it ignores the palette, it is a different width on
   every platform, and on a card it reads as a seam down the edge. Wheel,
   trackpad, keyboard, drag and scroll-anchoring all keep working — what is given
   up is the bar as a hint that a region scrolls, which is why the regions that
   do (the modal's two columns) are cut off at a visible edge rather than fading
   out. This also retires `scrollbar-gutter: stable`: with no bar there is no
   width for a modal's `overflow: hidden` to reclaim, so nothing can jolt. */
* { scrollbar-width: none; }
*::-webkit-scrollbar { display: none; }
html { -webkit-text-size-adjust: 100%; font-size: 14.5px; }   /* Decipher's base size */
body {
  margin: 0; color: var(--text); font-family: var(--sans);
  font-size: 13.5px; line-height: 1.5; -webkit-font-smoothing: antialiased;
  background: var(--bg); min-height: 100vh;
}
/* Montserrat on every heading, as Decipher sets it on h1–h6. */
h1, h2, h3, h4, h5, h6 { font-family: var(--heading); }
a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-2); text-decoration: underline; text-underline-offset: 3px; }

/* ICONS — Fluent System Icons, each drawn twice (shell/icons.py): the regular
   outline at rest, the filled one while a control is hovered, focused or
   active, the swap Decipher makes on every clickable thing. An icon that is
   not inside a control never swaps. */
.ico { display: block; flex: none; fill: currentColor; }
/* The page's one copy of every icon (icon_sprite in base.html), referenced by
   <use>. Out of the layout rather than display:none, which some engines treat
   as unrenderable for what references it. */
.icon-sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
.ico-f { display: none; }
/* A control that is itself the active one (a nav link, a tab, a scope) — not
   anything inside an active container: a previewed row is .is-active too, and
   its pin must not read as pinned. */
:is(a, button):is(:hover, :focus-visible) .ico-r, :is(a, button).is-active .ico-r, .on .ico-r { display: none; }
:is(a, button):is(:hover, :focus-visible) .ico-f, :is(a, button).is-active .ico-f, .on .ico-f { display: inline; }

/* ===========================================================================
   APP CHROME — grid: [sidebar | top bar] / [sidebar | main], one window tall.
   The sidebar and the top bar sit on the page floor; the page itself is one
   large card (.surface) floating on it, as Decipher draws a PageSurface — and
   like Decipher's, the card stays put while what is on it scrolls (.content),
   so the top bar and the card's edges are always where the eye left them.
   =========================================================================== */
.app {
  display: grid;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  grid-template-rows: var(--topbar-h) minmax(0, 1fr);
  grid-template-areas: "sidebar topbar" "sidebar main";
  height: 100vh; height: 100dvh;
  transition: grid-template-columns .15s ease-out;
}
/* Decipher's icon rail (⌘B): the width the nav gives up goes to the page. */
.app.is-collapsed { --sidebar-w: var(--sidebar-w-c); }

/* ------------------------------------------------------------- sidebar --- */
.sidebar { grid-area: sidebar; min-height: 0; overflow-y: auto;
  display: flex; flex-direction: column; gap: .4rem; padding: 0 .6rem 1rem;
  background: var(--bg); }
.side-head { display: flex; align-items: center; justify-content: space-between; gap: .5rem;
  flex: none; height: var(--topbar-h); padding: 0 .1rem 0 .65rem; }
.brand { display: flex; align-items: center; min-width: 0; color: var(--text); }
.brand:hover { text-decoration: none; color: var(--text); }
/* The round control that folds the sidebar to its icons — Decipher's, with the
   blick rim every raised control wears. */
.side-toggle { display: grid; place-items: center; flex: none; width: 28px; height: 28px; padding: 0;
  border: 0; border-radius: 50%; cursor: pointer; color: var(--muted); background: transparent;
  transition: color .12s, background .12s, box-shadow .12s; }
.side-toggle .ico { width: 16px; height: 16px; }
.side-toggle:hover { color: var(--text); background: var(--surface); box-shadow: var(--blick-sm); }

/* The lock-up: the mark in the brand navy, the wordmark in ink, the product
   name where Decipher's own app writes "IP". */
.logo { display: inline-flex; align-items: center; gap: .5rem; line-height: 1; }
.logo-mark { width: 20px; height: 20px; flex: none; color: var(--accent); }
.logo-word { height: 13px; width: auto; flex: none; color: var(--text); }
.logo-product { font-family: var(--heading); font-weight: 700; font-size: 14px;
  letter-spacing: -.01em; color: var(--accent); margin-left: -.15rem; }

.nav { display: flex; flex-direction: column; gap: 2px; }
/* A group: its micro-label is the control that folds it (Decipher's "MY WORK"
   with the chevron at the far edge), remembered per group in a cookie. */
.nav-sec { display: flex; flex-direction: column; gap: 2px; }
.nav-group { display: flex; align-items: center; justify-content: space-between; gap: .5rem;
  width: 100%; margin: .75rem 0 .1rem; padding: .3rem .65rem; border: 0; background: none; cursor: pointer;
  border-radius: var(--r-sm); font-family: var(--sans); font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .1em; color: var(--text); text-align: left; }
.nav-group:hover { background: var(--surface-hi); }
.nav-chev { width: 14px; height: 14px; color: var(--muted); transform: rotate(90deg);
  transition: transform .15s ease-out; }
.nav-sec.is-closed .nav-chev { transform: none; }
.nav-sec-body { display: flex; flex-direction: column; gap: 2px; }
.nav-sec.is-closed .nav-sec-body { display: none; }

/* A nav item is a 36px row, rounded like every control; the active one is
   lifted onto the card colour with the blick rim (Decipher's
   `bg-card/90 blick-sm`), its icon filled. */
.nav-link { position: relative; display: flex; align-items: center; gap: .65rem;
  height: var(--ctrl-h); padding: 0 .65rem; border-radius: var(--r); color: var(--text);
  font-size: 13.5px; transition: background .15s ease-out, box-shadow .15s ease-out; }
.nav-link:is(:hover, .is-active) { background: color-mix(in srgb, var(--surface) 90%, transparent);
  box-shadow: var(--blick-sm); color: var(--text); text-decoration: none; }
.nav-link.is-active { font-weight: 500; }
.nav-ico { width: 20px; height: 20px; color: var(--text); opacity: .6; transition: opacity .15s; }
.nav-link:hover .nav-ico, .nav-link.is-active .nav-ico { opacity: 1; }
.nav-link.is-active .nav-ico { color: var(--kc, var(--accent)); }

.nl-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.side-foot { margin-top: auto; display: flex; flex-direction: column; gap: .7rem; padding: .5rem .65rem 0; }
.chip { display: inline-flex; align-items: center; gap: .4rem; font-size: 11.5px;
  color: var(--muted); align-self: flex-start; }
.chip .ico { width: 16px; height: 16px; color: var(--success); }
a.chip:hover { color: var(--text); text-decoration: none; }

/* Collapsed: icons only, every group open (a rail has no room to fold
   anything), each item's name a tooltip beside it. The rail is short enough
   not to scroll, which is what lets a tooltip leave it. A phone has its own
   tab bar, so the rail is a desktop state only. */
@media (min-width: 768px) {
  .is-collapsed .sidebar { overflow: visible; padding: 0 .45rem 1rem; }
  .is-collapsed .side-head { justify-content: center; padding: 0; }
  .is-collapsed .brand, .is-collapsed .nav-group,
  .is-collapsed .chip > span { display: none; }
  .is-collapsed .nav-sec-body { display: flex; }
  .is-collapsed .nav-sec { margin-top: .45rem; padding-top: .45rem; border-top: 1px solid var(--border); }
  .is-collapsed .nav-link { justify-content: center; padding: 0; }
  .is-collapsed .side-foot { align-items: center; padding: .5rem 0 0; }
  .is-collapsed .nav-link .nl-label { position: absolute; left: calc(100% + 10px); top: 50%; z-index: 50;
    flex: none; transform: translateY(-50%); padding: .3rem .6rem; border-radius: var(--r-sm);
    background: var(--inverse); color: var(--on-inverse); font-size: 12px; font-weight: 500;
    box-shadow: var(--shadow); opacity: 0; pointer-events: none; transition: opacity .12s; }
  .is-collapsed .nav-link:is(:hover, :focus-visible) .nl-label { opacity: 1; }
}

/* ---------------------------------------------------------------- top bar - */
/* The page's own head at the left (Decipher's PageHeaderSlot), search in the
   middle, the viewer's own controls at the right. Three columns, so the
   search stays centred over the page card whatever width either end takes. */
.topbar { grid-area: topbar; position: relative; z-index: 30;
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 360px) minmax(0, 1fr);
  align-items: center; gap: 1rem;
  padding: 0 var(--surface-gap) 0 var(--content-pad); background: var(--bg); }
.top-brand { display: none; }
.top-head { grid-column: 1; display: flex; align-items: center; gap: .4rem; min-width: 0; }
.top-title { margin: 0; min-width: 0; font-size: 16.5px; font-weight: 600; letter-spacing: -.01em;
  line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Back is a fixed parent page, drawn as Decipher's ghost chevron. */
.top-back { display: grid; place-items: center; flex: none; width: 28px; height: 28px;
  margin-left: -.35rem; border-radius: var(--r-sm); color: var(--muted); transition: color .12s, background .12s; }
.top-back .ico { width: 18px; height: 18px; }
.top-back:hover { color: var(--text); background: var(--surface-hi); text-decoration: none; }
.top-search { grid-column: 2; position: relative; }
.top-search-ico { position: absolute; left: .75rem; top: 50%; transform: translateY(-50%);
  width: 16px; height: 16px; color: var(--faint); pointer-events: none; }
.top-search input { height: 34px; min-height: 0; padding: 0 3rem 0 2.2rem; font-size: 13px; cursor: pointer; }
.top-search input:focus ~ .top-search-ico { color: var(--accent); }
.top-search-kbd { font-family: var(--sans); font-size: 11px; font-weight: 600; color: var(--text);
  background: var(--surface); border-radius: 6px; padding: .05rem .4rem; line-height: 1.5;
  box-shadow: var(--shadow); pointer-events: none; white-space: nowrap; }
.top-search-kbd { position: absolute; right: .45rem; top: 50%; transform: translateY(-50%); }
.top-actions { grid-column: 3; justify-self: end; display: flex; align-items: center; gap: .35rem; }
/* One icon control for the chrome's buttons (the bell, a dialog's close). */
.icon-btn { position: relative; display: inline-flex; align-items: center; justify-content: center; flex: none;
  width: var(--ctrl-h); height: var(--ctrl-h); padding: 0; border: 0; background: none; cursor: pointer;
  color: var(--muted); border-radius: var(--r); transition: color .12s, background .12s; }
.icon-btn .ico { width: 18px; height: 18px; }
.icon-btn:hover, .icon-btn[aria-expanded="true"] { color: var(--text); background: var(--surface-hi); text-decoration: none; }

/* The viewer: their name and their initial, the trigger of the account menu. */
.top-user { display: inline-flex; align-items: center; gap: .6rem; height: var(--ctrl-h);
  padding: 0 .3rem 0 .7rem; margin-left: .2rem; border: 0; background: none; cursor: pointer;
  border-radius: var(--r); font-family: var(--sans); font-size: 13.5px; font-weight: 500; color: var(--text);
  white-space: nowrap; transition: background .12s; }
.top-user:hover, .top-user[aria-expanded="true"] { background: var(--surface-hi); }
.avatar { width: 28px; height: 28px; border-radius: 50%; flex: none; display: grid; place-items: center;
  font-size: 12px; font-weight: 600; color: var(--muted);
  background: var(--surface); box-shadow: var(--blick-sm), inset 0 0 0 1px var(--border); }

/* ------------------------------------------------------------------ menus - */
/* A dropdown the chrome opens (the account menu, notifications): a card under
   its trigger, right-aligned to it. app.js opens one at a time and closes it
   on Escape or a click elsewhere. */
.menu { position: relative; }
.menu-pop { position: absolute; right: 0; top: calc(100% + 6px); z-index: 60; min-width: 250px;
  display: flex; flex-direction: column; padding: .35rem; background: var(--surface);
  border-radius: var(--r); box-shadow: var(--shadow-lg), inset 0 0 0 1px var(--border);
  animation: menu-in .14s ease-out both; }
.menu-pop[hidden] { display: none; }
@keyframes menu-in { from { opacity: 0; transform: translateY(-4px); } }
.menu-item { display: flex; align-items: center; gap: .6rem; min-height: 34px; padding: .35rem .6rem;
  border-radius: var(--r-sm); color: var(--text); font-size: 13.5px; }
.menu-item > span { flex: 1; min-width: 0; }
.menu-item .ico { width: 16px; height: 16px; flex: none; opacity: .7; }
.menu-item:hover, .menu-item:focus-visible, .menu-item.is-active { background: var(--surface-hi); color: var(--text);
  text-decoration: none; }
.menu-item:hover .ico { opacity: 1; }
.menu-badge { flex: none; min-width: 20px; padding: 0 .4rem; text-align: center; border-radius: 20px;
  font-size: 11px; font-weight: 600; font-variant-numeric: tabular-nums;
  color: var(--on-accent); background: var(--danger); }
.menu-badge:empty { display: none; }
.menu-sep { height: 1px; margin: .3rem -.35rem; background: var(--border); }
.menu-who { display: flex; flex-direction: column; padding: .45rem .6rem .35rem; }
.menu-who-name { font-weight: 600; font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; }
.menu-who-role { font-size: 12px; color: var(--muted); }
.menu-label { margin: 0; padding: .45rem .6rem .3rem; font-family: var(--heading); font-size: 11px;
  font-weight: 700; text-transform: uppercase; letter-spacing: .04em; }
.menu-empty { margin: 0; padding: .4rem .6rem .6rem; font-size: 13px; color: var(--muted); }
.menu-theme { display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: .2rem .25rem .2rem .6rem; font-size: 13.5px; }
.menu-theme .seg-group { height: 30px; padding: 2px; }
.menu-theme .seg { padding: 0 .5rem; }
.menu-theme .seg .ico { width: 15px; height: 15px; }
:root:not([data-theme]) [data-theme-set="system"],
:root[data-theme="light"] [data-theme-set="light"],
:root[data-theme="dark"] [data-theme-set="dark"] { color: var(--text); background: var(--surface); box-shadow: var(--blick-sm); }

/* ------------------------------------------------------------- the page - */
.main { grid-area: main; display: flex; min-width: 0; min-height: 0;
  padding: 0 var(--surface-gap) 1.5rem 0; }
/* The page card (Decipher's PageSurface): white, 24px corners, a long soft
   shadow, the window's full height whatever is on it. */
.surface { flex: 1; display: flex; min-width: 0; min-height: 0; width: 100%;
  max-width: var(--content-max); margin: 0 auto; overflow: hidden;
  background: var(--surface); border-radius: var(--r-lg); box-shadow: var(--shadow-xl); }
/* What scrolls on it. With no scrollbar anywhere (see SCROLLBARS), the edge
   that has more beyond it fades out instead — Decipher's ScrollArea fade, set
   per edge by app.js so a page that fits shows a hard, finished edge. */
@property --fade-t { syntax: "<length>"; inherits: false; initial-value: 0px; }
@property --fade-b { syntax: "<length>"; inherits: false; initial-value: 0px; }
.content { flex: 1; min-width: 0; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  padding: 1.6rem var(--content-pad) 2rem;
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 var(--fade-t), #000 calc(100% - var(--fade-b)), transparent);
  mask-image: linear-gradient(to bottom, transparent, #000 var(--fade-t), #000 calc(100% - var(--fade-b)), transparent);
  transition: --fade-t .2s, --fade-b .2s; }
.content[data-fade~="top"] { --fade-t: 2rem; }
.content[data-fade~="bottom"] { --fade-b: 2rem; }

/* ===========================================================================
   HEADS
   =========================================================================== */
.page-title { font-size: 20px; font-weight: 600; margin: 0 0 .25rem; letter-spacing: -.01em; line-height: 1.3; }
.page-sub { color: var(--muted); font-size: 13.5px; margin: 0 0 1.6rem; }
.page-head { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 1.4rem; }
.page-head .page-sub { margin: 0; }
.section-head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; margin: 2rem 0 .8rem; }
/* Decipher's card section title ("MY PORTFOLIO"): small, bold, uppercase — one
   micro-label for section heads and card heads. */
.section-title, .ecard-t { font-family: var(--heading); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em; color: var(--text); }
.section-title { margin: 0; }

/* ===========================================================================
   DASHBOARD — where to pick up: a quiet line of counts, a line of security
   status, then one Continue card (tabs over a list) beside the projects.
   Colour is spent on one thing at a time: the rows here are drawn neutral and
   their actions wait for the pointer, so the only loud thing on the page is a
   security problem when there is one.
   =========================================================================== */
.dash { container-type: inline-size; }

 .btn-caret { width: 14px; height: 14px; margin: 0 -.15rem 0 .1rem; opacity: .8; }

.dash-sum { display: flex; flex-wrap: wrap; align-items: baseline; gap: .2rem .55rem;
  margin: 0 0 .9rem; font-size: 13.5px; color: var(--muted); }
.dash-sum a { color: var(--muted); }
.dash-sum a:hover { color: var(--text); text-decoration: none; }
.dash-sum b { font-weight: 600; color: var(--text); font-variant-numeric: tabular-nums; }
.dash-dot { color: var(--faint); }
.dash-when { margin-left: auto; font-size: 12px; color: var(--faint); }

.dash-health { display: flex; align-items: center; gap: .65rem; margin-bottom: 1.1rem;
  padding: .6rem .9rem; border-radius: var(--r); font-size: 13px; color: var(--text);
  background: var(--surface-2); transition: background .12s; }
.dash-health:hover { text-decoration: none; }
.dash-health-ico { flex: none; width: 18px; height: 18px; }
.dash-health-t { font-weight: 600; }
.dash-health-s { color: var(--muted); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-health.ok .dash-health-ico { color: var(--success); }
.dash-health.ok .dash-health-s { margin-left: auto; color: var(--faint); font-size: 12px; }
.dash-health.ok:hover { background: var(--surface-hi); }
.dash-health.bad { background: var(--danger-sf); }
.dash-health.bad .dash-health-ico { color: var(--danger); }
.dash-health.bad:hover { background: color-mix(in srgb, var(--danger) 18%, transparent); }

.dash-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(260px, 340px); gap: 1rem; align-items: start; }
@container (max-width: 760px) { .dash-grid { grid-template-columns: minmax(0, 1fr); } }

@container (max-width: 560px) { .dash-health.bad .dash-health-s { display: none; } }


.actions { display: flex; gap: .55rem; flex-wrap: wrap; }

/* ===========================================================================
   CARDS — Decipher's card, the furniture of the dashboard's widgets.
   =========================================================================== */

/* A Decipher card: white on the white page card, set apart by a hairline and
   its 12px corners rather than by a shadow. */
.ecard { display: flex; flex-direction: column; min-width: 0; overflow: hidden;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); }
/* Head: the badge and name of what the card holds, its total, and whatever
   acts on it — the dashboard's quiet create. The name is the link to the full
   list, so the card needs no second "View all" until there is more than it
   shows (.ecard-more); a card whose title is a plain label gets no hover,
   hence the hover on `a` only. */
.ecard-head { display: flex; align-items: center; gap: .6rem; padding: .7rem .7rem .7rem .9rem;
  border-bottom: 1px solid var(--border); }
/* Title and count are one label and stay together; whatever acts on the card
   goes to the far edge. The count carries the push (margin-right: auto), not
   the title, or a wide card would strand its own total next to the buttons. */
.ecard-t { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ecard-head .col-count { margin-right: auto; }

a.ecard-t:hover { color: var(--kc, var(--accent)); text-decoration: none; }

/* Rows: the flat list row, one notch denser — the card is the panel, so the
   rows inside it carry no border or background of their own. --ctrl-h scopes
   the shrink to every button the row draws (copy, pin) in one place. */
.ecard-rows { --ctrl-h: 30px; }
.ecard-rows .item { padding: .55rem .65rem; gap: .6rem; }
.ecard-rows .item .ic { width: 30px; height: 30px; }

.ecard-rows .item .body .t { font-size: 13.5px; }
.ecard-rows .item .body .s { font-size: 12.5px; }
   /* a column this narrow spends its width on the name */

/* The line in place of rows: on the dashboard the whole line is the create
   link, and only the link form lights up on hover.

   A line of text, not a flex row: it keeps its own word spacing, and the lone
   icon aligns itself instead. */
.ecard-empty { display: block; margin: 0; padding: 1.3rem .8rem;
  font-size: 13px; color: var(--muted); text-align: center;
  transition: color .12s, background .12s; }
.ecard-empty .ico { display: inline-block; width: 15px; height: 15px; vertical-align: -3px; margin-right: .3rem; }
.ecard-empty a { color: var(--muted); }
.ecard-empty a:hover { color: var(--kc, var(--accent)); }

.ecard-more { display: flex; align-items: center; justify-content: space-between; gap: .5rem;
  padding: .6rem .9rem; border-top: 1px solid var(--border);
  font-size: 12.5px; font-weight: 500; color: var(--muted); transition: color .12s, background .12s; }
.ecard-more .ico { width: 15px; height: 15px; }
.ecard-more:hover { color: var(--text); background: var(--surface-hi); text-decoration: none; }

.col-count { font-size: 11.5px; font-weight: 500; color: var(--muted); background: var(--surface-2);
  border-radius: 20px; min-width: 22px; text-align: center; padding: 0 .45rem; font-variant-numeric: tabular-nums; }

.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r);
  padding: 1rem 1.1rem; transition: border-color .12s; }
.card:hover { border-color: var(--border-hi); }

.badge { display: inline-block; font-size: 11px; font-weight: 600; padding: .15rem .55rem; border-radius: var(--r-sm);
  color: var(--kc, var(--accent));
  background: color-mix(in srgb, var(--kc, var(--accent)) 12%, transparent); }

/* Decipher's tab list: a recessed track, the selected segment lifted onto the
   card colour with the blick rim. */
.seg-group { display: inline-flex; flex: none; gap: 2px; padding: 3px; height: var(--ctrl-h);
  background: var(--surface-2); border-radius: var(--r); }
.seg { display: inline-flex; align-items: center; border: 0; background: none; cursor: pointer;
  color: var(--muted); font-family: var(--sans); font-size: 13px; font-weight: 500; padding: 0 .8rem; line-height: 1;
  border-radius: var(--r-sm); transition: color .12s, background .12s, box-shadow .12s; }
.seg:hover { color: var(--text); }
.seg.is-active { color: var(--text); background: var(--surface); box-shadow: var(--blick-sm); }

.list { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); overflow: hidden; }
/* One height for every row in a flat list, whether or not the type has a
   subtitle — a mixed list (search, shared with me) otherwise steps up and down
   row by row. Card grids set their own height from their content. */
.list .item { min-height: 58px; }

.item { position: relative; display: flex; align-items: center; gap: .85rem; padding: .75rem 1rem;
  border-top: 1px solid var(--border); cursor: pointer; transition: background .1s; }
.item[hidden] { display: none; }  /* [hidden] alone loses to display:flex above */
.item:first-child { border-top: 0; }
.item:hover { background: var(--surface-hi); }
/* icon badge shared by list rows (.ic), section and card headings (.col-ico)
   and the confirm dialog (.confirm-ico): a rounded, tinted square holding the
   kind glyph. Each site sets only its size above; the tint recipe lives here
   once. */
.item .ic, .dt-name .ic, .confirm-ico, .rec-ic, .hiw-feat-ico { flex: none; display: grid; place-items: center;
  border-radius: var(--r-sm); color: var(--kc, var(--accent));
  background: color-mix(in srgb, var(--kc, var(--accent)) 11%, transparent); }
.item .ic { width: 34px; height: 34px; }

.item .body { min-width: 0; flex: 1; }
/* Title line: the name, plus whatever is flagged about it (a sharing tag). The
   clipping is on the name alone — the same split as .share-name/.share-who in
   the roster — so a long title cannot truncate its own tag. */
.item .body .t { display: flex; align-items: center; gap: .45rem;
  font-weight: 600; font-size: 13.5px; min-width: 0; }
.item .body .t a { min-width: 0; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.item .body .t a:hover { color: var(--accent); text-decoration: none; }
.item .body .s { color: var(--muted); font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* An item row has two zones: the data (icon + title/subtitle) and the
   functional actions — data left, actions right, at every width the app draws
   a row at. Inside a card the row is denser (.ecard-rows) and its buttons
   shrink with --ctrl-h, but it stays one line: two zones, never a stack. */
.item-main { display: flex; align-items: center; gap: .85rem; flex: 1; min-width: 0; }

/* ===========================================================================
   DATA VIEW — a list page as Decipher draws one: an overview card, a toolbar,
   the table and its pager, and a preview beside the table once a row is
   opened.
   =========================================================================== */
.dv { display: flex; flex-direction: column; gap: 1.1rem; }
.dv-note { margin: 0; font-size: 12.5px; color: var(--muted); }

/* --- overview (Decipher's widget row) ---------------------------------- */
/* One card, its figures on one line; a figure that does not fit is dropped
   rather than wrapped (Decipher: "a widget never grows taller because it ran
   out of width"). */
.widget { container: widget / inline-size; padding: 1.25rem 1.6rem 1.4rem;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); }
.widget-t { margin: 0 0 .9rem; font-family: var(--heading); font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em; }
.widget-metrics { display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); gap: 1.5rem; }
.metric-l { margin: 0; font-size: 12px; color: var(--muted); }
.metric-v { margin: .1rem 0 0; font-family: var(--heading); font-size: 26px; font-weight: 500;
  letter-spacing: -.03em; line-height: 1.15; font-variant-numeric: tabular-nums; }
.metric.tone-warn .metric-v { color: var(--file); }
.metric.tone-bad .metric-v { color: var(--danger); }
@container widget (max-width: 620px) { .metric:nth-child(n+4) { display: none; } }
@container widget (max-width: 420px) { .metric:nth-child(n+3) { display: none; } }
/* How a mixed list divides between types: one bar in the types' colours, and
   its legend kept to one line. */
.mixbar { display: flex; gap: 3px; height: 8px; margin-top: 1.1rem; }
.mixbar span { flex-basis: 0; min-width: 6px; border-radius: 4px; background: var(--kc); }

/* --- toolbar ------------------------------------------------------------- */
.dv-tools { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.dv-search { position: relative; flex: 0 1 18rem; min-width: 12rem; }
.dv-search-ico { position: absolute; left: .7rem; top: 50%; transform: translateY(-50%);
  width: 15px; height: 15px; color: var(--faint); pointer-events: none; }
.dv-search input { height: var(--ctrl-h); min-height: 0; padding: 0 .8rem 0 2.1rem; font-size: 13px; }

/* A filter is a button naming what it narrows by (Decipher's "⊕ Status"):
   outline at rest, its count once something in it is ticked. */
.fbtn { display: inline-flex; align-items: center; gap: .4rem; height: 32px; padding: 0 .75rem;
  border: 0; border-radius: var(--r); cursor: pointer; background: var(--btn-bg); color: var(--text);
  box-shadow: var(--shadow-sm), var(--btn-ring); font-family: var(--sans); font-size: 12.5px; font-weight: 500;
  white-space: nowrap; transition: background .12s, box-shadow .12s; }
.fbtn .ico { width: 15px; height: 15px; opacity: .7; }
.fbtn:hover, .fbtn[aria-expanded="true"] { background: var(--btn-hover); }
.fbtn.is-on { box-shadow: var(--shadow-sm), inset 0 0 0 1px var(--accent); }
.fbtn.is-on .ico { color: var(--accent); opacity: 1; }
.fbtn-n { min-width: 18px; padding: 0 .35rem; border-radius: 6px; text-align: center; font-size: 11px;
  color: var(--on-accent); background: var(--accent); font-variant-numeric: tabular-nums; }
.fbtn-n[hidden] { display: none; }
.fbtn.ghost { background: none; box-shadow: none; color: var(--muted); }
.fbtn.ghost:hover { color: var(--text); background: var(--surface-hi); }
.fbtn[hidden] { display: none; }
.menu-check { display: flex; align-items: center; gap: .6rem; min-height: 34px; padding: .3rem .6rem;
  border-radius: var(--r-sm); cursor: pointer; font-size: 13.5px; }
.menu-check:hover { background: var(--surface-hi); }
.menu-check > span:first-of-type { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu-check-n { font-size: 12px; color: var(--faint); font-variant-numeric: tabular-nums; }
/* A menu entry may be a button (a form's submit, a copy) as well as a link. */
button.menu-item { width: 100%; border: 0; background: none; cursor: pointer; font-family: var(--sans); text-align: left; }
.menu-item.danger, .menu-item.danger:hover { color: var(--danger); }
.menu-pop form { margin: 0; }
/* A popover in the top layer (app.js → placeFloat): the browser's popover box
   (centred, bordered, Canvas-coloured) undone, placed by left/top alone. Its
   own width wherever it sits: a fixed box shrinks to the room left of its last
   position, so an armed delete (wider) would be placed by its old width. */
.menu-pop.float { position: fixed; inset: auto; margin: 0; border: 0; color: var(--text);
  min-width: 200px; width: max-content; max-width: calc(100vw - 16px);
  max-height: min(60vh, 420px); overflow-y: auto; }

/* --- the table ----------------------------------------------------------- */
.dv-split { display: grid; grid-template-columns: minmax(0, 1fr); align-items: start; }
/* Severity · Finding · Project · Target · Last seen. A page that already
   narrows to one project or server drops that column (.dt.no-project …). */
.dt { container: dt / inline-size;
  --c-sev: 6.8rem; --c-name: minmax(14rem, 3fr); --c-project: minmax(6rem, .9fr); --c-target: minmax(7rem, 1.2fr);
  --c-when: 7.5rem; }
.dt[hidden] { display: none; }
.dt-head, .dt-row { display: grid; align-items: center; column-gap: .25rem;
  --dt-cols: var(--c-sev) var(--c-name) var(--c-project) var(--c-target) var(--c-when);
  grid-template-columns: var(--dt-cols); }
.dt.no-project { --c-project: ; }
.dt-head { height: 40px; border-bottom: 1px solid var(--border); font-size: 12.5px; font-weight: 500; color: var(--muted); }
.dt-cell { min-width: 0; padding: 0 .5rem; }
.dt-cell.end { text-align: right; justify-self: stretch; }
.dt-sort { display: inline-flex; align-items: center; gap: .3rem; padding: .25rem .35rem; margin-left: -.35rem;
  border: 0; border-radius: var(--r-sm); background: none; cursor: pointer;
  font-family: var(--sans); font-size: 12.5px; font-weight: 500; color: var(--muted); white-space: nowrap; }
.dt-cell.end .dt-sort { margin: 0 -.35rem 0 0; }
.dt-sort .ico { width: 13px; height: 13px; opacity: .45; }
.dt-sort:hover { color: var(--text); background: var(--surface-hi); }
.dt-sort.is-asc, .dt-sort.is-desc { color: var(--text); }
.dt-sort.is-asc .ico, .dt-sort.is-desc .ico { opacity: 1; color: var(--accent); }
.dt-sort.is-desc .ico { transform: scaleY(-1); }

/* Rows: a hairline under each, rounded ends on hover — Decipher's row
   states, with the previewed row in the accent tint and a checked one grey. */
.dt-row { position: relative; min-height: 54px; border-bottom: 1px solid var(--border); cursor: pointer;
  border-radius: var(--r-sm); transition: background .1s; }
.dt-row[hidden] { display: none; }
.dt-row:hover { background: var(--surface-hi); }
.dt-row.is-selected { background: var(--surface-2); }
.dt-row.is-active { background: var(--accent-sf); }
.dt-name { display: flex; align-items: center; gap: .75rem; padding-top: .45rem; padding-bottom: .45rem; }
.dt-name .ic { width: 32px; height: 32px; }
.dt-name .ic svg { width: 17px; height: 17px; }
.dt-name .body { flex: 1; min-width: 0; }
.dt-name .t { display: flex; align-items: center; gap: .45rem; min-width: 0; font-weight: 600; font-size: 13.5px; }
.dt-name .t a { min-width: 0; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dt-name .t a:hover { color: var(--accent); text-decoration: underline; }
.dt-name .s { color: var(--muted); font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dt-row:hover .tag { border-color: var(--border-hi); color: var(--text); }

.dt-row .dt-when { color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.dt-none { color: var(--faint); }

.dt-row .tag { max-width: 100%; height: 24px; }

/* Narrower than the columns need (a phone, or the table beside a preview):
   the page's own columns go, name and date stay. Asked of the table's own
   width — a container query, as Decipher's widgets do — because the preview
   narrows it at any window size. */
@container dt (max-width: 720px) {
  /* on the rows, not .dt: a container query cannot style its own container */
  .dt-head, .dt-row { --dt-cols: var(--c-sev) minmax(0, 1fr) var(--c-when); }
  .dt [data-col] { display: none; }
}
@container dt (max-width: 460px) {
  /* hidden cells leave the grid's flow, so the template counts only those left */
  .dt-head, .dt-row { --dt-cols: var(--c-sev) minmax(0, 1fr); }
  .dt .dt-when { display: none; }
}

/* --- pager --------------------------------------------------------------- */
/* Decipher's table footer: a muted band, rows per page on the left, where you
   are and the four steps on the right. */
.dv-pager { display: flex; align-items: center; gap: 1rem; margin-top: 1rem; padding: .55rem .6rem .55rem .9rem;
  background: var(--surface-2); border-radius: var(--r); font-size: 12.5px; }
.dv-pager[hidden] { display: none; }
.dv-size { display: inline-flex; align-items: center; gap: .55rem; color: var(--muted); }
.dv-size select { width: auto; height: 30px; min-height: 0; padding: 0 1.9rem 0 .7rem; font-size: 12.5px;
  background-color: var(--surface); box-shadow: var(--shadow-sm); }
.dv-page { margin-left: auto; font-weight: 500; font-variant-numeric: tabular-nums; }
.dv-page-btns { display: flex; gap: 2px; }
.dv-page-btns .icon-btn { width: 30px; height: 30px; display: inline-flex; gap: 0; }
.dv-page-btns .icon-btn .ico { width: 15px; height: 15px; }
.dv-page-btns .icon-btn .ico + .ico { margin-left: -9px; }
.dv-page-btns .icon-btn:disabled { opacity: .35; pointer-events: none; }

/* --- empty state (Decipher's InteractiveEmptyState) ---------------------- */
.empty-state { display: flex; flex-direction: column; align-items: center; text-align: center;
  padding: 3rem 1.5rem 3.2rem; border: 1px dashed var(--border-hi); border-radius: var(--r); }
.es-icons { display: flex; align-items: flex-end; margin-bottom: 1.2rem; }
.es-ico { display: grid; place-items: center; width: 44px; height: 44px; border-radius: var(--r);
  background: var(--surface); color: var(--muted); box-shadow: var(--blick-sm), inset 0 0 0 1px var(--border); }
.es-ico svg { width: 22px; height: 22px; }
.es-ico:nth-child(1) { transform: rotate(-8deg) translate(6px, 4px); }
.es-ico:nth-child(2) { z-index: 1; width: 50px; height: 50px; color: var(--kc, var(--accent)); }
.es-ico:nth-child(3) { transform: rotate(8deg) translate(-6px, 4px); }
.es-title { margin: 0; font-family: var(--heading); font-size: 16px; font-weight: 600; }
.es-text { margin: .35rem 0 0; max-width: 42ch; font-size: 13.5px; color: var(--muted); }
.empty-state .btn { margin-top: 1.2rem; }
[data-empty-filter][hidden] { display: none; }

/* ===========================================================================
   BUTTONS
   =========================================================================== */
/* Decipher's buttons. The plain .btn is its `outline` variant — a white button
   with a hairline ring drawn inside the box (black in dark) — and .primary its
   solid navy `default`. Icons: 16px at 70% ink, a hair outside the label. */
.btn { display: inline-flex; align-items: center; justify-content: center; gap: .45rem;
  background: var(--btn-bg); border: 0; color: var(--text);
  box-shadow: var(--shadow-sm), var(--btn-ring);
  border-radius: var(--r); height: var(--ctrl-h); padding: 0 1.1rem;
  font-family: var(--sans); font-size: 13.5px; font-weight: 500; cursor: pointer; text-decoration: none; white-space: nowrap;
  transition: background .12s, color .12s, box-shadow .12s; }
.btn .ico { width: 16px; height: 16px; opacity: .7; margin-left: -.2rem; }
.btn:hover { background: var(--btn-hover); color: var(--text); text-decoration: none; }
.btn:disabled { opacity: .5; cursor: not-allowed; pointer-events: none; }
.btn.primary { background: var(--accent); color: var(--on-accent); box-shadow: var(--shadow); }
.btn.primary .ico { opacity: .85; }
.btn.primary:hover { background: color-mix(in srgb, var(--accent) 90%, transparent); color: var(--on-accent); }
.btn.danger { color: var(--danger); }
.btn.danger:hover { background: var(--danger-sf); color: var(--danger); }

/* ===========================================================================
   DETAIL
   =========================================================================== */
/* Decipher's record page: a header — the type's icon tile, the title over a
   line of facts, the actions at the right — then underlined tabs over the
   panels. */
.rec-head { display: flex; align-items: flex-start; gap: 1rem; margin-bottom: .6rem; }
.rec-ic { width: 44px; height: 44px; border-radius: var(--r); }

.rec-title { flex: 1; min-width: 0; }
.rec-title .page-title { margin: 0 0 .45rem; overflow-wrap: anywhere; }
/* The facts about the record as one row of tags — its type, who it is shared
   with or by, its project — then when it last changed, in the quiet register. */
.rec-meta { display: flex; flex-wrap: wrap; align-items: center; gap: .45rem; }
.rec-meta .badge { height: 26px; display: inline-flex; align-items: center; }
.rec-when { font-size: 12px; color: var(--faint); margin-left: .2rem; white-space: nowrap; }
.rec-actions { flex: none; display: flex; align-items: center; gap: .4rem; }

/* On a phone the title needs the width more than the type needs a tile: the
   badge under it already says what it is. */
@media (max-width: 767px) { .rec-ic { display: none; } .rec-head { gap: .6rem; } }

/* A record's tab strip (the tab itself is the search dialog's .scope recipe):
   a little taller, a link rather than a button, and a count in its label. */
.utabs { display: flex; gap: 1.6rem; margin: 0 0 1.5rem; border-bottom: 1px solid var(--border);
  overflow-x: auto; }
.utab { display: inline-flex; align-items: center; gap: .45rem; height: 44px; font-size: 13.5px; white-space: nowrap; }
.utab.is-active::after { animation: utab-in .2s ease-out both; }
@keyframes utab-in { from { transform: scaleX(.4); opacity: 0; } }

.utab:focus-visible { outline: none; box-shadow: 0 2px 0 0 var(--focus, var(--accent)); }

/* The form footer: Decipher's dialog footer, Cancel then the primary action,
   both at the right edge. */
form .actions { display: flex; align-items: center; justify-content: flex-end; gap: .6rem; flex-wrap: wrap;
  margin-top: 1.5rem; padding-top: 1.25rem; border-top: 1px solid var(--border); }
.panel { background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); padding: 1.4rem 1.5rem; }
.form-panel { max-width: 560px; }
.empty { color: var(--muted); font-size: 13.5px; background: var(--surface); border: 1px dashed var(--border-hi);
  border-radius: var(--r); padding: 1.6rem; text-align: center; }
/* Baseline-aligned, so a label sits on the line of its value whether the value
   is plain text or a field with buttons in it. */
.kv { display: grid; grid-template-columns: 8rem 1fr; gap: .9rem 1.3rem; font-size: 13.5px; margin: 0;
  align-items: baseline; }
.kv dt { color: var(--muted); font-size: 13px; font-weight: 500; }
.kv dd { margin: 0; word-break: break-word; }

.note { margin: 0; padding: .6rem 2.4rem .6rem .75rem; background: var(--surface-2);
  border: 0; border-radius: var(--r); font-family: var(--mono);
  font-size: 13px; line-height: 1.5; color: var(--text); white-space: pre-wrap; word-break: break-word; }

/* ===========================================================================
   FORMS
   =========================================================================== */
form p { margin: 0 0 1.15rem; }
/* Decipher's form label: sentence case, medium weight, in ink, a few pixels
   in from the field's rounded edge. */
.form-panel label, .auth-card label { display: block; font-size: 13px; font-weight: 500;
  color: var(--text); margin: 0 0 var(--label-gap, .45rem) var(--label-inset, .7rem); }
.form-panel .helptext { display: block; color: var(--muted); font-size: 12px; margin: .35rem 0 0 .7rem; }
/* Text-like fields. Kept as a bare element selector on purpose: every field in
   the app narrows it with one class (.filter, .code-cell, .top-search input),
   so raising the specificity here — even with a :not() — would quietly outrank
   all of them. Checkboxes and radios opt out below instead, where an attribute
   selector wins on its own. */
/* Decipher's field: no border, the translucent --input fill, a faint inset
   shadow, 12px corners; focus draws the ring *inside* the box. */
input, textarea, select { width: 100%; min-height: var(--ctrl-h); background: var(--surface-2); color: var(--text); border: 0;
  border-radius: var(--r); padding: .55rem .8rem; font-family: var(--sans); font-size: 13.5px;
  box-shadow: var(--inset-field); transition: box-shadow .12s; }
input::placeholder, textarea::placeholder { color: color-mix(in srgb, var(--muted) 55%, transparent); }
/* Every filter in the app is a `type=search`, for the semantics and the Escape
   key it brings. What it also brings is a clear button WebKit paints itself —
   the same foreign artefact as a native checkbox or a scrollbar, in a blue that
   belongs to no palette here. Dropped in one place, like those. Nothing is lost
   with it: the button is WebKit's alone (no other engine draws one), so no
   filter in the app could have depended on it to be clearable. */
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; appearance: none; }
input:focus, textarea:focus, select:focus { outline: none; box-shadow: inset 0 0 0 1px var(--ring); }
/* A select is a text-like field plus a chevron of our own: the native arrow is
   drawn by the OS and ignores the palette, so it is the one control that would
   still ignore the theme on a form. */
select { appearance: none; -webkit-appearance: none; padding-right: 2rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2373798A' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right .55rem center; background-size: 15px; }

/* ---------------------------------------------------------------------------
   CHECKBOX + RADIO — one drawn control for the whole app (the attach picker,
   the share modal's people list and access level, "select all"). `appearance:
   none` is what lets the palette apply at all: the native control paints itself
   from the OS theme and ignores colour, so it reads as a foreign box on the
   card. Selection is always the accent, never the row's --kc, so a checked box
   means the same thing on a purple project row as on a green password one.

   This is also where they shed the text-field rule above: a radio left at
   `width: 100%` eats its row and pushes its own label onto a second line.
   --------------------------------------------------------------------------- */
input[type=checkbox], input[type=radio] {
  appearance: none; -webkit-appearance: none; flex: none; box-shadow: none;
  width: 17px; height: 17px; min-height: 0; margin: 0; padding: 0; cursor: pointer;
  display: inline-grid; place-content: center; vertical-align: -3px;
  background: var(--surface); border: 1px solid var(--border-hi); box-shadow: var(--shadow-sm);
  border-radius: 5px; transition: background .12s, border-color .12s; }
input[type=radio] { border-radius: 50%; }
input[type=checkbox]:hover, input[type=radio]:hover { border-color: var(--accent); }
input[type=checkbox]::after { content: ""; width: 9px; height: 5px; margin-top: -2px;
  border: 2px solid var(--on-accent); border-top: 0; border-right: 0;
  transform: rotate(-45deg) scale(.4); opacity: 0; transition: transform .12s, opacity .12s; }
input[type=radio]::after { content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: var(--on-accent); transform: scale(.4); opacity: 0; transition: transform .12s, opacity .12s; }
input[type=checkbox]:checked, input[type=radio]:checked {
  background: var(--accent); border-color: var(--accent); }
input[type=checkbox]:checked::after { transform: rotate(-45deg) scale(1); opacity: 1; }
/* Some of a page's rows ticked: a bar, not a tick. */
input[type=checkbox]:indeterminate { background: var(--accent); border-color: var(--accent); }
input[type=checkbox]:indeterminate::after { width: 8px; height: 0; margin: 0; border-width: 0 0 2px;
  transform: none; opacity: 1; }
input[type=radio]:checked::after { transform: scale(1); opacity: 1; }
input[type=checkbox]:disabled, input[type=radio]:disabled { opacity: .4; cursor: not-allowed; }
textarea { min-height: 8rem; resize: vertical; }
[data-flash="copied"] { color: var(--success); }
*:focus-visible { outline: 2px solid var(--ring); outline-offset: 2px; }
/* A field draws its own ring inside the box, so it needs no outline too. */
input:focus-visible, textarea:focus-visible, select:focus-visible { outline: none; }

/* ===========================================================================
   AUTH
   =========================================================================== */
/* Decipher's sign-in page (as its gitg tool draws it): a cover — the logo, a
   headline with the brand word in navy, one line under it and an isometric
   drawing — beside the card that holds the form. One column below 900px,
   where the drawing shrinks to a strip above the card. */
.auth-shell { min-height: 100vh; display: grid; place-items: center; padding: 2rem 1rem;
  background: var(--bg); }
.auth-inner { width: 100%; max-width: 68rem; display: grid; align-items: center;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 26rem); gap: clamp(2.5rem, 6vw, 6rem); }
.auth-cover { min-width: 0; }
.auth-top { display: inline-flex; color: var(--text); }
.auth-top:hover { color: var(--text); text-decoration: none; }
.auth-top .logo-word { color: var(--muted); }
.auth-hero { margin: 2.25rem 0 0; font-size: clamp(2rem, 4vw, 3.1rem); line-height: 1.05; font-weight: 600;
  letter-spacing: -.03em; text-wrap: balance; }
.auth-hero span { color: var(--accent); }
.auth-lede { margin: 1rem 0 0; max-width: 40ch; font-size: 1.05rem; line-height: 1.5; color: var(--muted); }
.auth-more { display: inline-flex; align-items: center; gap: .25rem; margin-top: 1rem; font-weight: 500; }
.auth-more .ico { width: 16px; height: 16px; }

/* The drawing (shell/_warden_drawing.svg, scripts/gen_drawing.py): every block
   three faces, lit from the top-left; the lit ones are the brand navy, a few
   more half-lit around them. */
.iso { display: block; width: 100%; max-width: 34rem; height: auto; margin-top: 2.5rem; overflow: visible;
  --box-top:   light-dark(#fff, hsl(220 5% 20%));
  --box-left:  light-dark(hsl(215 12% 94%), hsl(220 0% 9%));
  --box-right: light-dark(hsl(219 12% 90%), hsl(220 6% 13%));
  --box-edge:  light-dark(hsl(220 13% 86%), hsl(220 5% 22%)); }
.iso polygon { stroke: var(--box-edge); stroke-width: .6; stroke-linejoin: round; }
.iso .t { fill: var(--box-top); }
.iso .l { fill: var(--box-left); }
.iso .r { fill: var(--box-right); }
.iso .hi polygon { stroke: var(--accent); }
.iso .hi .t { fill: var(--accent); }
.iso .hi .l { fill: color-mix(in srgb, var(--accent) 70%, var(--bg)); }
.iso .hi .r { fill: color-mix(in srgb, var(--accent) 85%, var(--bg)); }
.iso .mid polygon { stroke: color-mix(in srgb, var(--accent) 55%, var(--box-edge)); }
.iso .mid .t { fill: color-mix(in srgb, var(--accent) 18%, var(--box-top)); }
/* Warden's own parts: the shield on the keep in navy with its tick in the
   face's tone, the lit windows, the servers' slots and lights, and
   the scan lines from the keep's top, dashed and drifting toward the servers. */
.iso .shield { fill: var(--accent); stroke: var(--accent); }
.iso .shield-mark { fill: var(--box-top); stroke: none; }
.iso .win { fill: color-mix(in srgb, var(--accent) 85%, var(--bg)); stroke: var(--accent); }
.iso .slot { fill: color-mix(in srgb, var(--box-edge) 60%, var(--box-right)); stroke: none; }
.iso .led { fill: var(--box-top); stroke: none; }
.iso .led-lit { fill: var(--accent); stroke: none; }
.iso .beam { stroke: var(--accent); stroke-width: .8; stroke-dasharray: 2 2.5; stroke-linecap: round; opacity: .45; }
@media (prefers-reduced-motion: no-preference) {
  .iso .beam { animation: iso-scan 1.6s linear infinite; }
}
@keyframes iso-scan { to { stroke-dashoffset: -9; } }

/* The card's controls are one size up (48px), set once for every field and
   button inside it. */
.auth-card { --ctrl-h: 3rem; min-width: 0; background: var(--surface); border-radius: 1rem;
  box-shadow: var(--shadow-lg); padding: 1.5rem 2rem 2rem; }
.auth-card .panel { border: 0; background: none; padding: 0; }
.auth-card form.panel { display: flex; flex-direction: column; gap: 1.5rem; }
.auth-card form.panel > p { margin: 0; }
.auth-head { margin-bottom: 1.75rem; }
.auth-title { margin: 0 0 .75rem; font-size: 1.25rem; line-height: 1; font-weight: 600; letter-spacing: -.025em; }
.auth-subtitle { margin: 0; font-size: 13px; }
/* A field: label over a 3rem filled control, the icon at its leading edge. */
.auth-field { display: flex; flex-direction: column; gap: .5rem; min-width: 0; }
.auth-field label { --label-gap: 0; line-height: 1; }
.auth-control { position: relative; display: flex; align-items: center; }
.auth-control .ico { position: absolute; left: .8rem; width: 16px; height: 16px; color: var(--muted);
  opacity: .7; pointer-events: none; }
.auth-control input { padding: 0 1rem 0 2.5rem; }
.auth-control input:-webkit-autofill { -webkit-box-shadow: inset 0 0 0 1000px light-dark(hsl(216 14% 93%), hsl(220 8% 16%));
  -webkit-text-fill-color: var(--text); }
/* The submit is centred and generous, not stretched across the card. */
.auth-submit { display: flex; width: max-content; max-width: 100%; margin: .25rem auto 0;
  padding: 0 2.5rem; border-radius: 1rem; }
.auth-submit .ico { opacity: .6; }
.auth-note { margin: 0; }
.auth-foot { color: var(--muted); text-align: center; margin: 1.5rem 0 0; font-size: 13px; }
.auth-foot-tight { text-align: center; margin: .5rem 0 0; font-size: 12.5px; }
@media (max-width: 899px) {
  .auth-shell { place-items: start center; }
  .auth-inner { grid-template-columns: minmax(0, 1fr); max-width: 28rem; gap: 2rem; }
  .auth-hero { margin-top: 1.5rem; }
  .iso { margin-top: 1.5rem; max-height: 9rem; }
}
@media (max-width: 639px) {
  .auth-card { padding: 1.25rem 1.25rem 1.5rem; }
  .auth-submit { width: 100%; justify-content: center; }
}
/* HOME + HOW IT WORKS (findings/home.html for a visitor at `/`,
   findings/how.html for a member) — Sanctum's front door in Warden's words.
   One loud thing, the hero's rings (what Warden watches, from the code out to
   the servers, the shield at the core, the baseline the gate around it);
   everything below is quiet: what it checks, a numbered journey, the promises,
   the way in. The same body (findings/_hiw.html) sits on the auth floor and in
   the shell's .content, so widths are container queries on .hiw. */
.hiw-public { width: 100%; max-width: 68rem; align-self: start; }
.hiw-top { display: flex; align-items: center; justify-content: space-between; gap: 1rem; margin-bottom: 1rem; }
.hiw-btns { display: flex; flex-wrap: wrap; gap: .6rem; }
.hiw-hero-copy .hiw-btns { margin-top: 1.75rem; }
.hiw-btns + .hiw-badges { margin-top: 1.25rem; }
.hiw { container-type: inline-size; max-width: 68rem; margin: 0 auto; padding-bottom: 1.5rem; }

.hiw-hero { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(0, .8fr); align-items: center;
  gap: clamp(2rem, 5cqi, 4.5rem); padding: clamp(1.5rem, 4cqi, 3rem) 0 clamp(2.5rem, 5cqi, 4rem); }
.hiw-title { margin: 0; font-size: clamp(2.2rem, 5cqi, 3.3rem); line-height: 1.04; font-weight: 600;
  letter-spacing: -.035em; text-wrap: balance; }
.hiw-title span { color: var(--accent); }
.hiw-lede { margin: 1.25rem 0 0; max-width: 32rem; font-size: 1.1rem; line-height: 1.6; color: var(--muted);
  text-wrap: pretty; }
.hiw-badges { margin-top: 1.75rem; display: flex; flex-wrap: wrap; gap: .5rem; }

/* The rings: tint deepens toward the core, which is the accent itself. */
.rings { display: block; width: 100%; max-width: 26rem; height: auto; margin: 0 auto; overflow: visible; }
.rings * { transform-box: view-box; transform-origin: center; }
.rings-orbit { fill: none; stroke: var(--border-hi); stroke-width: 1.25; stroke-dasharray: 2 7; stroke-linecap: round; }
.rings-dot { fill: var(--accent); stroke: var(--surface); stroke-width: 3; }
.rings-1 { fill: color-mix(in srgb, var(--accent) 7%, var(--surface)); stroke: color-mix(in srgb, var(--accent) 12%, var(--border)); }
.rings-2 { fill: color-mix(in srgb, var(--accent) 14%, var(--surface)); stroke: color-mix(in srgb, var(--accent) 22%, var(--border)); }
.rings-3 { fill: color-mix(in srgb, var(--accent) 24%, var(--surface)); stroke: color-mix(in srgb, var(--accent) 36%, var(--border)); }
.rings-core { fill: var(--accent); }
.rings-halo { fill: color-mix(in srgb, var(--accent) 22%, transparent); }
.rings-mark { color: var(--on-accent); fill: var(--on-accent); }
.rings-pill rect { fill: var(--surface); stroke: var(--border); }
.rings-pill text { font-family: var(--sans); font-size: 10.5px; font-weight: 600; fill: var(--text);
  text-anchor: middle; dominant-baseline: central; }
.rings-pill.is-gate rect { fill: var(--accent-sf); stroke: none; }
.rings-pill.is-gate text { fill: var(--accent); }
@media (prefers-reduced-motion: no-preference) {
  .rings-shield { animation: rings-in .6s cubic-bezier(.2,.8,.2,1) both; }
  .rings-1, .rings-2, .rings-3 { animation: rings-in .7s var(--d) cubic-bezier(.2,.8,.2,1) both; }
  .rings-3 { --d: .12s; } .rings-2 { --d: .24s; } .rings-1 { --d: .36s; }
  .rings-orbit, .rings-pill { animation: rings-fade .6s .6s ease-out both; }
  .rings-spin { animation: rings-fade .6s .8s ease-out both, rings-spin 8s .8s cubic-bezier(.45,0,.25,1) both; }
}
@keyframes rings-in { from { opacity: 0; transform: scale(.82); } }
@keyframes rings-fade { from { opacity: 0; } }
@keyframes rings-spin { to { transform: rotate(360deg); } }

/* A section: a heading, then its body. Space, not rules, divides them. */
.hiw-sec { margin-top: clamp(3rem, 7cqi, 4.5rem); }
.hiw h3, .hiw-cta h3 { margin: 0 0 1.5rem; font-size: clamp(1.25rem, 2.6cqi, 1.6rem);
  font-weight: 600; letter-spacing: -.025em; text-wrap: balance; }

/* What it checks: one tile per family, each in its own hue (--kc). */
.hiw-features { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1rem; }
.hiw-feat { min-width: 0; padding: 1.25rem 1.4rem 1.4rem; border-radius: var(--r-lg); background: var(--surface);
  box-shadow: var(--btn-ring), var(--shadow-sm); }
.hiw-feat-ico { width: 2.6rem; height: 2.6rem; border-radius: var(--r); }
.hiw-feat-ico .ico { width: 20px; height: 20px; }
.hiw-feat h4 { margin: 1.1rem 0 .35rem; font-size: 15px; font-weight: 600; letter-spacing: -.01em; }
.hiw-feat p { margin: 0; color: var(--muted); line-height: 1.6; text-wrap: pretty; }

/* The journey: five numbered steps joined by a line that darkens toward the
   end; a column below 860px. */
.hiw-chain { list-style: none; margin: 0; padding: 0; display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 1.25rem; }
.hiw-node { position: relative; min-width: 0; }
.hiw-node b { display: block; margin-top: 1rem; font-family: var(--heading); font-size: 14px; font-weight: 600;
  letter-spacing: -.01em; }
.hiw-node > span:last-child { display: block; margin-top: .3rem; font-size: 13px; line-height: 1.5;
  color: var(--muted); text-wrap: pretty; }
.hiw-node:not(:last-child)::after { content: ""; position: absolute; top: 1.75rem; left: 4.25rem;
  right: -.5rem; height: 2px; border-radius: 1px;
  background: linear-gradient(var(--chain-dir, 90deg), var(--border), color-mix(in srgb, var(--accent) 45%, var(--border))); }
.hiw-ico { position: relative; display: grid; place-items: center; width: 3.5rem; height: 3.5rem;
  border-radius: var(--r); background: var(--surface); color: var(--accent); box-shadow: var(--blick-sm); }
.hiw-ico .ico { width: 22px; height: 22px; }
.hiw-step { position: absolute; top: -.45rem; right: -.45rem; display: grid; place-items: center;
  width: 1.3rem; height: 1.3rem; border-radius: 50%; background: var(--surface); box-shadow: var(--btn-ring);
  font-size: 10.5px; font-weight: 700; color: var(--muted); }
.hiw-node.is-end .hiw-ico { background: var(--accent); color: var(--on-accent); }

/* The promises, and the one limit. */
.hiw-facts { padding: clamp(1.5rem, 3.5cqi, 2.25rem);
  border-radius: var(--r-lg); background: var(--surface); box-shadow: var(--btn-ring), var(--shadow-xl); }
.hiw-facts ul { list-style: none; margin: 0; padding: 0; display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem 2rem; }
.hiw-facts li, .hiw-tradeoff { display: flex; align-items: flex-start; gap: .65rem; line-height: 1.55;
  color: var(--muted); }
.hiw-facts b { color: var(--text); font-weight: 600; }
.hiw-facts .ico { flex: none; width: 18px; height: 18px; margin-top: .1rem; color: var(--success); }
.hiw-tradeoff { margin: 1.5rem 0 0; padding: 1rem 1.1rem; border-radius: var(--r);
  background: color-mix(in srgb, var(--star) 10%, transparent); }
.hiw-facts .hiw-tradeoff .ico { color: var(--star); }

/* The way in (the home page): its one accent surface. */
.hiw-cta { margin-top: 2rem; display: flex; align-items: center; justify-content: space-between; gap: 1.5rem;
  flex-wrap: wrap; padding: clamp(1.5rem, 3.5cqi, 2.25rem); border-radius: var(--r-lg);
  background: var(--accent); color: var(--on-accent); box-shadow: var(--shadow-xl); }
.hiw-cta h3 { margin: 0; }
.hiw-cta p { margin: .4rem 0 0; max-width: 34rem; color: color-mix(in srgb, var(--on-accent) 75%, transparent); }
.hiw-cta .btn { background: var(--on-accent); color: var(--accent); box-shadow: none; }
.hiw-cta .btn:hover { background: color-mix(in srgb, var(--on-accent) 88%, transparent); color: var(--accent); }

.hiw-specs { display: flex; flex-wrap: wrap; justify-content: center; gap: .4rem; margin-top: 2rem; }

@container (max-width: 860px) {
  .hiw-chain { grid-template-columns: minmax(0, 1fr); gap: 1.4rem; }
  .hiw-node { display: grid; grid-template-columns: 3.5rem minmax(0, 1fr); column-gap: 1rem; }
  .hiw-node .hiw-ico { grid-row: span 2; }
  .hiw-node b { margin: .35rem 0 0; }
  .hiw-node:not(:last-child)::after { top: 3.5rem; bottom: -1.4rem; left: 1.7rem; right: auto; width: 2px;
    height: auto; --chain-dir: 180deg; }
  .hiw-features { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@container (max-width: 720px) {
  .hiw-hero { grid-template-columns: minmax(0, 1fr); }
  .rings { max-width: 20rem; }
  .hiw-facts ul, .hiw-features { grid-template-columns: minmax(0, 1fr); }
}
/* Decipher's toast is a pill: inverted chrome (navy-black on light, near white
   on dark), centred at the top of the window, one line that grows to its
   message and wraps rather than truncates. The tone lives on its icon alone,
   drawn as a mask so a toast built by app.js needs no markup for it. */
.toasts { position: fixed; top: var(--topbar-h); left: 50%; transform: translateX(-50%); z-index: 120;
  display: flex; flex-direction: column; align-items: center; gap: .5rem; width: min(92vw, 30rem);
  pointer-events: none; }
.toast { display: flex; align-items: flex-start; gap: .55rem; width: max-content; max-width: 100%;
  background: var(--inverse); color: var(--on-inverse); padding: .6rem 1rem .6rem .8rem;
  border-radius: 1.375rem; font-size: 13.5px; font-weight: 500; line-height: 1.45;
  box-shadow: var(--shadow-lg); backdrop-filter: blur(8px); pointer-events: auto;
  animation: toast-in .2s cubic-bezier(.2,.8,.2,1) both; }
.toast::before { content: ""; flex: none; width: 16px; height: 16px; margin-top: 1px;
  background: var(--toast-tone); -webkit-mask: var(--toast-ico) center / contain no-repeat;
  mask: var(--toast-ico) center / contain no-repeat; }
/* The two tones' glyphs: Fluent's checkmark-circle and warning (filled). */
.toast { --toast-tone: light-dark(hsl(152 76% 60%), hsl(158 64% 32%));
  --toast-ico: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M10 2a8 8 0 1 1 0 16 8 8 0 0 1 0-16m3.36 5.65a.5.5 0 0 0-.64-.06l-.07.06L9 11.3 7.35 9.65l-.07-.06a.5.5 0 0 0-.7.7l.07.07 2 2 .07.06c.17.11.4.11.56 0l.07-.06 4-4 .07-.08a.5.5 0 0 0-.06-.63'/%3E%3C/svg%3E"); }
.toast.error { --toast-tone: light-dark(hsl(0 91% 71%), hsl(0 72% 51%));
  --toast-ico: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M7.37 3.56a3 3 0 0 1 5.26 0l5.5 10A3 3 0 0 1 15.5 18h-11a3 3 0 0 1-2.63-4.44zM10 12.75a.75.75 0 1 0 0 1.5.75.75 0 0 0 0-1.5m0-6.25a.5.5 0 0 0-.5.5v4a.5.5 0 0 0 1 0V7a.5.5 0 0 0-.5-.5'/%3E%3C/svg%3E"); }
.toast.hide { opacity: 0; transform: translateY(-6px); transition: opacity .35s, transform .35s; }
@keyframes toast-in { from { opacity: 0; transform: translateY(-8px) scale(.98); } }
@media (prefers-reduced-motion: reduce) { .toast { animation: none; } }
/* Decipher's underlined tab: the label, and a 3px rounded bar riding the hairline under the current one. */
.scope, .utab { position: relative; flex: none; height: 42px; padding: 0; border: 0; background: none; cursor: pointer;
  font-family: var(--sans); font-size: 13px; font-weight: 500; color: var(--muted); transition: color .12s; }
.scope:hover, .utab:hover { color: var(--text); text-decoration: none; }
.scope.is-active, .utab.is-active { color: var(--text); }
.scope.is-active::after, .utab.is-active::after { content: ""; position: absolute; left: 0; right: 0; bottom: -1px;
  height: 3px; border-radius: 3px; background: var(--accent); }
.tag { display: inline-flex; align-items: center; gap: .4rem; max-width: 100%;
  height: 26px; padding: 0 .6rem; font-size: 12px; font-weight: 500; color: var(--muted);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-sm);
  transition: color .12s, border-color .12s; }
.tag.sm { flex: none; max-width: 12rem; height: 19px; gap: .3rem; padding: 0 .4rem;
  font-size: 11px; font-weight: 600; border-radius: 6px; }
.tag svg { flex: none; width: 14px; height: 14px; color: var(--kc, var(--accent)); }
.tag.sm svg { width: 11px; height: 11px; color: inherit; }
.tag span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
a.tag:hover { text-decoration: none; color: var(--text);
  border-color: color-mix(in srgb, var(--kc, var(--accent)) 55%, var(--border-hi)); }

/* ===========================================================================
   RESPONSIVE
   The desktop grid (sidebar + header) holds all the way down to tablets — an
   iPad in portrait keeps the sidebar, which is the best use of the width. Below
   768px the chrome switches to a mobile app shell: a compact header (the mark,
   search, the viewer's controls) over a fixed bottom tab bar, with the page
   card running edge to edge between them. Safe-area insets keep it clear of
   the notch and the home bar.
   =========================================================================== */

/* Up to a tablet the avatar stands in for the viewer's name in the top bar. */
@media (max-width: 1024px) { .top-user-name { display: none; } .top-user { padding: 0 .3rem; } }

/* ---- tablet: narrow the rail so content keeps its room ------------------ */
@media (min-width: 768px) and (max-width: 1024px) {
  :root { --sidebar-w: 212px; --content-pad: 1.3rem; --surface-gap: 1.2rem; }
  .logo-product { display: none; }
}

/* ---- mobile: top header + fixed bottom tab bar -------------------------- */
@media (max-width: 767px) {
  :root {
    --m-top-h: 56px;
    --m-bottom-h: 60px;
    --m-top-total: calc(var(--m-top-h) + env(safe-area-inset-top));  /* header + notch */
    --content-pad: 1rem;
  }
  body { font-size: 14px; }

  /* Header, then the page; the sidebar is lifted out of the grid entirely and
     fixed to the bottom edge as a tab bar (see .sidebar below). */
  /* On a phone the page scrolls as a page again: the window is the frame,
     and a card scrolling inside it would fight the browser's own bars. */
  .app, .app.is-collapsed { --sidebar-w: 0px; grid-template-columns: minmax(0, 1fr);
    grid-template-rows: var(--m-top-total) minmax(0, 1fr);
    grid-template-areas: "topbar" "main"; height: auto; min-height: 100vh; }

  .topbar { position: sticky; top: 0; display: flex; gap: .5rem; height: 100%;
    padding: env(safe-area-inset-top) .6rem 0 .9rem; }
  .top-brand { display: flex; flex: none; color: var(--text); }
  .top-brand .logo-mark { width: 22px; height: 22px; }
  .top-head { flex: 1; }
  .top-title { font-size: 15px; }
  .top-back { margin-left: 0; }
  /* Search folds to its icon: the dialog it opens is the full-width field. */
  .top-search { flex: none; width: var(--ctrl-h); }
  .top-search input { padding: 0; color: transparent; }
  .top-search input::placeholder { color: transparent; }
  .top-search-ico { left: 50%; transform: translate(-50%, -50%); }
  .top-search-kbd { display: none; }
  .top-actions { gap: 0; }
  .top-user { margin-left: .2rem; }

  /* Bottom tab bar: the brand, the group labels, Recents and the footer chip
     stay behind — the top bar carries the mark, and a tab bar is links only. */
  .nav-sec, .nav-sec-body, .nav-sec.is-closed .nav-sec-body { display: contents; }
  .side-head, .nav-group, .side-foot { display: none; }
  .sidebar { position: fixed; inset: auto 0 0 0; top: auto; z-index: 40;
    height: auto; flex-direction: row; gap: 0; overflow: visible;
    padding: 4px 4px calc(4px + env(safe-area-inset-bottom));
    background: color-mix(in srgb, var(--bg) 92%, transparent); backdrop-filter: blur(10px);
    border-top: 1px solid var(--border); }
  /* Ten destinations do not fit a phone's width at a tappable size, so the bar
     scrolls sideways rather than squeezing every label to an ellipsis. */
  .nav { flex: 1; flex-direction: row; gap: 2px; overflow-x: auto; overscroll-behavior-x: contain; }
  .nav-link { flex: 1 0 64px; min-width: 64px; flex-direction: column; justify-content: center;
    align-items: center; gap: 3px; height: auto; padding: 6px 3px; min-height: 50px;
    font-size: 10px; font-weight: 600; }
  .nav-ico { width: 22px; height: 22px; }
  .nl-label { flex: none; max-width: 100%; }

  /* The page card runs edge to edge: at this width a margin around it is room
     taken from the content. It keeps its top corners, so it still reads as the
     surface the header sits above. */
  .main { display: block; padding: 0; }
  .surface { display: block; overflow: visible; min-height: calc(100vh - var(--m-top-total));
    border-radius: var(--r-lg) var(--r-lg) 0 0; }
  .content { overflow: visible; -webkit-mask-image: none; mask-image: none;
    padding: 1.2rem var(--content-pad) calc(var(--m-bottom-h) + 1.4rem + env(safe-area-inset-bottom)); }

  .page-title { font-size: 18px; }
  .page-sub { font-size: 13px; margin-bottom: 1.2rem; }

  
  
  
  .dash-when { flex-basis: 100%; margin-left: 0; }

  .section-head { margin: 1.5rem 0 .7rem; }

  /* Denser list rows */
  .item { padding: .65rem .8rem; gap: .7rem; }
  .item .ic { width: 32px; height: 32px; }

  /* Detail: label-over-value reads better than a cramped two-column grid */
  .kv { grid-template-columns: 1fr; gap: .3rem; }
  .kv dt { padding-top: .7rem; }
  .kv dt:first-of-type { padding-top: 0; }
  
  .panel { padding: 1.1rem 1rem; }
  .modal-head { padding-left: 1.25rem; }
  .modal-body { padding: .4rem 1.25rem 1.6rem; }

  

  /* Keep toasts clear of the header */
  .toasts { top: calc(var(--m-top-total) + .4rem); }

  /* 16px form text stops iOS from zooming the page on focus */
  input, textarea, select, .top-search input { font-size: 16px; }

}

/* ---- very narrow phones (iPhone SE, 320px): reclaim every pixel --------- */
@media (max-width: 360px) {
  /* labels would ellipsize at this width — go icon-only; the glyphs read fine */
  .nl-label { display: none; }
  .nav-ico { width: 24px; height: 24px; }
  .nav-link { padding: 8px 2px; }
}


/* ===========================================================================
   WARDEN — severities, states and health. Colour carries severity and
   nothing else: one hue per level from the brand's status palette, so a red
   thing on any page is a critical finding and an orange one a high.
   =========================================================================== */
:root {
  --sev-critical: var(--danger);
  --sev-high:     var(--hue-orange);
  --sev-medium:   var(--hue-amber);
  --sev-low:      var(--hue-blue);
  --sev-info:     var(--muted);
}
.critical { --kc: var(--sev-critical); }
.high     { --kc: var(--sev-high); }
.medium   { --kc: var(--sev-medium); }
.low      { --kc: var(--sev-low); }
.info     { --kc: var(--sev-info); }
.ok       { --kc: var(--success); }

/* A severity pill: the level's colour on its own tint. */
.sev { display: inline-flex; align-items: center; gap: .35rem; height: 22px; padding: 0 .55rem;
  border-radius: 6px; font-size: 11.5px; font-weight: 600; white-space: nowrap; color: var(--kc);
  background: color-mix(in srgb, var(--kc) 12%, transparent); }
.sev::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; }
.sev.lg { height: 26px; font-size: 12px; }
/* The finding's tile in a row: the shield in the level's colour. */
.sev-tile { --kc: var(--sev-info); }

/* What the baseline says about a finding. Known findings carry no badge. */
.state { display: inline-flex; align-items: center; height: 19px; padding: 0 .4rem; border-radius: 6px;
  font-size: 10.5px; font-weight: 700; letter-spacing: .03em; text-transform: uppercase; white-space: nowrap;
  color: var(--st); background: color-mix(in srgb, var(--st) 13%, transparent); }
.state.new       { --st: var(--accent); }
.state.escalated { --st: var(--danger); }
.state.fixed     { --st: var(--success); }
.state.resolved  { --st: var(--success); }
.state.ignored   { --st: var(--muted); }
.state.known     { --st: var(--muted); }
.dt-row.is-gone .t a { color: var(--muted); text-decoration: line-through; text-decoration-color: var(--faint); }

/* Health: a dot (sidebar, rows) or a dot and its word (cards). */
.hdot { flex: none; width: 8px; height: 8px; border-radius: 50%; background: var(--kc, var(--success)); }
.nav-link .hdot { margin-left: auto; }
.nav-link .hdot.ok { opacity: .55; }
.health { display: inline-flex; align-items: center; gap: .4rem; font-size: 12px; font-weight: 600; color: var(--kc); }
.health::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: currentColor; }

/* Counts by severity, in a row (project / server / asset rows). Zero is drawn
   faint so the eye goes to what is there. */
.sevcounts { display: inline-flex; align-items: center; gap: .3rem; flex-wrap: wrap; }
.sevcount { min-width: 26px; height: 22px; padding: 0 .45rem; display: inline-flex; align-items: center;
  justify-content: center; border-radius: 6px; font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--kc); background: color-mix(in srgb, var(--kc) 12%, transparent); }
.sevcount.zero { color: var(--faint); background: var(--surface-2); font-weight: 500; }
a.sevcount:hover { text-decoration: none; color: var(--kc); background: color-mix(in srgb, var(--kc) 20%, transparent); }

.metric.sev-critical .metric-v { color: var(--sev-critical); }
.metric.sev-high .metric-v { color: var(--sev-high); }
.metric.sev-medium .metric-v { color: var(--sev-medium); }
.metric.tone-new .metric-v { color: var(--accent); }
.metric.tone-fixed .metric-v { color: var(--success); }
.metric a { color: inherit; }
.metric a:hover { text-decoration: none; }
.metric a:hover .metric-l { color: var(--text); }
.metric .metric-v.zero { color: var(--faint); }

/* Page head: what the page is, and what acts on all of it. */
.head { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; flex-wrap: wrap; margin-bottom: 1.1rem; }
.head-t { min-width: 0; }
.head-t .page-title { display: flex; align-items: center; gap: .6rem; }
.head-t .page-sub { margin: 0; }
.head-actions { display: flex; align-items: center; gap: .45rem; flex-wrap: wrap; }
.head-actions form { margin: 0; }
.btn.sm { height: 32px; padding: 0 .8rem; font-size: 12.5px; }

/* Cards in a grid: the dashboard's widgets and an asset's tiles. */
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr)); gap: 1rem; align-items: start; }
.cards.two { grid-template-columns: repeat(auto-fill, minmax(min(100%, 440px), 1fr)); }
.ecard-head .ico.lead { width: 16px; height: 16px; color: var(--muted); }
.ecard-head .count { margin-right: auto; font-size: 11.5px; font-weight: 500; color: var(--muted); background: var(--surface-2);
  border-radius: 20px; min-width: 22px; text-align: center; padding: 0 .45rem; font-variant-numeric: tabular-nums; }
.ecard-head .more { margin-left: auto; font-size: 12.5px; font-weight: 500; color: var(--muted); display: inline-flex; align-items: center; gap: .15rem; }
.ecard-head .more .ico { width: 14px; height: 14px; }
.ecard-head .more:hover { color: var(--text); text-decoration: none; }

/* A row in a card: a tile, the name over one line about it, whatever counts at the right. */
.row { display: flex; align-items: center; gap: .75rem; padding: .6rem .9rem; border-top: 1px solid var(--border);
  color: var(--text); transition: background .1s; }
.row:first-child { border-top: 0; }
a.row:hover { background: var(--surface-hi); text-decoration: none; color: var(--text); }
.row .ic { flex: none; display: grid; place-items: center; width: 30px; height: 30px; border-radius: var(--r-sm);
  color: var(--kc, var(--accent)); background: color-mix(in srgb, var(--kc, var(--accent)) 11%, transparent); }
.row .ic .ico { width: 16px; height: 16px; }
.row .body { flex: 1; min-width: 0; }
.row .t { display: flex; align-items: center; gap: .45rem; font-weight: 600; font-size: 13.5px; min-width: 0; }
.row .t > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row .s { color: var(--muted); font-size: 12.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row .end { flex: none; display: flex; align-items: center; gap: .5rem; font-size: 12px; color: var(--faint); }
.rows-empty { margin: 0; padding: 1.3rem .9rem; font-size: 13px; color: var(--muted); text-align: center; }

/* An asset tile (server, site, repo, domain) on a project or server page. */
.asset { display: flex; flex-direction: column; gap: .7rem; padding: 1rem 1.1rem; color: var(--text);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r); transition: border-color .12s; }
a.asset:hover { border-color: var(--border-hi); text-decoration: none; color: var(--text); }
.asset-top { display: flex; align-items: center; gap: .7rem; }
.asset-top .ic { flex: none; display: grid; place-items: center; width: 32px; height: 32px; border-radius: var(--r-sm);
  color: var(--accent); background: var(--accent-sf); }
.asset-top .ic .ico { width: 17px; height: 17px; }
.asset-name { font-weight: 600; font-size: 14px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.asset-kind { font-size: 11.5px; color: var(--muted); }
.asset-lines { margin: 0; padding: 0; list-style: none; font-size: 12.5px; color: var(--muted); }
.asset-lines li { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Filters: a GET form — the search field, a button per facet opening a menu of checkboxes. */
.dv-tools form { display: contents; }
.menu-pop.left { left: 0; right: auto; }
.menu-pop .menu-check input { flex: none; }
.menu-pop .menu-apply { display: flex; justify-content: flex-end; padding: .4rem .25rem .1rem; border-top: 1px solid var(--border); margin-top: .3rem; }
.fbtn select { height: 32px; min-height: 0; width: auto; padding: 0 1.9rem 0 .75rem; font-size: 12.5px; }
select.fsel { width: auto; height: 32px; min-height: 0; padding: 0 1.9rem 0 .75rem; font-size: 12.5px;
  background-color: var(--btn-bg); box-shadow: var(--shadow-sm), var(--btn-ring); font-weight: 500; }
.dv-count { margin-left: auto; font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* The finding page. */
.rec-ic.sev-tile { --kc: var(--sev-info); }
.rec-ic .ico { width: 22px; height: 22px; }
.rec-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(260px, 340px); gap: 1.25rem; align-items: start; }
@media (max-width: 1100px) { .rec-grid { grid-template-columns: minmax(0, 1fr); } }
.panel + .panel, .rec-side > * + * { margin-top: 1rem; }
.panel-t { margin: 0 0 1rem; font-family: var(--heading); font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; }
.fix { border-color: color-mix(in srgb, var(--success) 35%, var(--border)); background: color-mix(in srgb, var(--success) 5%, var(--surface)); }
.fix .panel-t { color: var(--success); display: flex; align-items: center; gap: .4rem; }
.fix .panel-t .ico { width: 16px; height: 16px; }
.fix p { margin: 0; font-size: 13.5px; line-height: 1.6; }
.mono { font-family: var(--mono); font-size: 12.5px; word-break: break-all; }
.ignored-note { padding: .8rem 1rem; border-radius: var(--r); background: var(--surface-2); font-size: 13px; }
.ignored-note b { font-weight: 600; }
.side-form textarea { min-height: 5.5rem; margin-bottom: .6rem; }
.side-form .btn { width: 100%; }
.side-form p { margin: 0 0 .6rem; font-size: 12.5px; color: var(--muted); }

/* Scans. */
.progress { height: 8px; border-radius: 4px; background: var(--surface-2); overflow: hidden; }
.progress > span { display: block; height: 100%; width: var(--p, 0%); border-radius: 4px; background: var(--accent);
  transition: width .4s ease-out; }
.scan-live { display: flex; flex-direction: column; gap: .6rem; }
.scan-live-t { display: flex; justify-content: space-between; gap: 1rem; font-size: 13px; }
.scan-live-t .mono { color: var(--muted); }
.status { display: inline-flex; align-items: center; gap: .35rem; font-size: 12.5px; font-weight: 600; }
.status::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.status.running { color: var(--accent); }
.status.running::before { animation: pulse 1.2s ease-in-out infinite; }
.status.done { color: var(--success); }
.status.failed { color: var(--danger); }
@keyframes pulse { 50% { opacity: .25; } }
.scan-form { display: flex; flex-direction: column; gap: 1rem; }
.scan-form .checks { display: flex; flex-wrap: wrap; gap: .4rem 1.1rem; }
.scan-form .checks label { display: inline-flex; align-items: center; gap: .45rem; font-size: 13px; margin: 0; cursor: pointer; }
.scan-form .row-f { display: flex; gap: .8rem; align-items: flex-end; flex-wrap: wrap; }
.scan-form .row-f > div { flex: 1 1 16rem; }
.scan-form label.l { display: block; font-size: 13px; font-weight: 500; margin: 0 0 .45rem .7rem; }
.table { width: 100%; border-collapse: collapse; font-size: 13px; }
.table th { text-align: left; font-weight: 500; font-size: 12.5px; color: var(--muted); height: 40px; padding: 0 .6rem;
  border-bottom: 1px solid var(--border); white-space: nowrap; }
.table td { padding: .65rem .6rem; border-bottom: 1px solid var(--border); vertical-align: middle; }
.table tr.link { cursor: pointer; }
.table tr.link:hover td { background: var(--surface-hi); }
.table .num { text-align: right; font-variant-numeric: tabular-nums; }
.table-wrap { overflow-x: auto; }

/* The running-scan chip in the top bar. */
.scan-chip { display: inline-flex; align-items: center; gap: .45rem; height: 30px; padding: 0 .7rem; border-radius: 20px;
  font-size: 12px; font-weight: 600; color: var(--accent); background: var(--accent-sf); white-space: nowrap; }
.scan-chip:hover { text-decoration: none; color: var(--accent-2); }
.scan-chip::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor;
  animation: pulse 1.2s ease-in-out infinite; }
@media (max-width: 767px) { .scan-chip span { display: none; } }

.errorlist { margin: 0; padding: .7rem 1rem; list-style: none; border-radius: var(--r); background: var(--danger-sf);
  color: var(--danger); font-size: 13px; }
.section-gap { margin-top: 2rem; }
.ignored-note + form { margin-top: .8rem; }
.row[data-href] { cursor: pointer; }
.row[data-href]:hover { background: var(--surface-hi); }
.row .t > a { min-width: 0; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row .t > a:hover { color: var(--accent); text-decoration: none; }
.row .t > .state { flex: none; }
