/* ============================================================
   shell.css — the app shell the portal editor and the admin
   console share: sidebar, brand, nav items, topbar, session bar,
   theme toggle and the main column's frame.

   docs/plans/primitive-layer-design-step-7-shell-2026-09-22.md.
   Linked before the surface's own sheet by the two documents that
   have a shell, and by nothing else. Not in primitives.css because
   its literals are not tokenised yet (design §2.4).

   Every rule is in `@layer primitives`, so an unlayered rule in
   portal.css or admin-shell.css overrides it without a
   specificity contest. It imports no token sheet: both documents
   load tokens.css and aliases.css already.
   ============================================================ */

/* The console links this sheet first, so it has to name the layer order
   itself: first mention fixes it, and reset must stay below primitives. */
@layer reset, primitives;

@layer primitives {
  /* The shell's 28px page gutter, named for its use (spacing programme
     2026-09-29, mapping rule 5): it sits in the scale's own 24→32 gap
     and repeats — the topbar's horizontal inset, the sidebar brand
     block's bottom margin, and the page gutter of both the portal editor
     (portal.css) and the console (admin-shell.css) read this one property. */
  :root {
    --shell-pad: calc(var(--space-6) + var(--space-1));
    /* The topbar's own sticky clearance, unified from admin's html{scroll-padding-top} and
       portal's --portal-scroll-offset (M7, spacing programme review 2026-09-29), and since
       2026-10-01 measured rather than assumed (WCAG 2.4.11, focus inventory D2). The bar is
       61px at 1280 but wraps to 107px (portal) and 136px (console, the session bar inside it)
       at 320, and a fixed 72px left 11 console stops entirely under it on Shift+Tab.
       lib/shell-topbar.js publishes the bar's real height as --shell-topbar-h on <html>, from
       a ResizeObserver, and a jump target clears it by --space-2. Until it has measured (or
       with no script) the fallback is --space-10: 64 + 8 is the fixed 72px this replaced. */
    --shell-scroll-offset: calc(var(--shell-topbar-h, var(--space-10)) + var(--space-2));
  }

  /* --- Frame ----------------------------------------------------------- */
  .shell {
    display: grid;
    grid-template-columns: 240px 1fr;
    min-height: 100vh;
  }

  /* --- Sidebar --------------------------------------------------------- */
  .sidebar {
    background: var(--sidebar-bg);
    color: var(--sidebar-text);
    padding: var(--inset-xl) var(--page-gutter-narrow);
    display: flex;
    flex-direction: column;
    gap: var(--space-half);
    position: sticky;
    top: 0;
    align-self: start;
    height: 100vh;
    overflow-y: auto;
  }
  [data-theme="dark"] .sidebar { background: var(--sidebar-bg-dark); }

  .sidebar .brand {
    display: grid;
    grid-template-columns: 24px minmax(0, 1fr);
    align-items: center;
    gap: var(--gap-close);
    margin-bottom: var(--shell-pad);
    padding: 0 var(--inset-sm);
    text-decoration: none;
    color: inherit;
  }
  .sidebar .brand:hover .brand-name-support { color: var(--sidebar-text-strong); }
  .sidebar .brand .pulse {
    width: 24px;
    height: 24px;
    background: var(--sidebar-text-strong);
    mask: url('/symbol.svg') center / contain no-repeat;
    opacity: var(--opacity-busy);
    flex: none;
  }
  .sidebar .brand-name {
    min-width: 0;
    overflow-wrap: anywhere;
    font-family: var(--font-display);
    font-variation-settings: 'SOFT' 30, 'opsz' 144, 'WONK' 0;
    font-size: var(--text-lg);
    font-weight: var(--fw-semi);
    letter-spacing: var(--tracking-tight);
    color: var(--sidebar-text-strong);
  }
  .sidebar .brand-name-support {
    font-weight: var(--fw-semi);
    color: var(--sidebar-text-strong);
    transition: color var(--dur-quick) var(--ease-standard);
  }
  .sidebar .brand-name-finder {
    font-style: italic;
    font-weight: var(--fw-regular);
    font-variation-settings: 'SOFT' 60, 'opsz' 144;
    color: var(--sidebar-accent);
    margin-left: var(--gap-tight);
  }
  .sidebar .brand-tag {
    grid-column: 2;
    justify-self: start;
    font-family: var(--type-overline-family);
    font-size: var(--type-overline-size);
    font-weight: var(--type-overline-weight);
    letter-spacing: var(--type-overline-tracking);
    text-transform: var(--type-overline-transform);
    color: var(--sidebar-accent);
    background: var(--sidebar-accent-tint);
    padding: var(--space-half) var(--inset-sm);
    border-radius: var(--shape-xs);
  }

  /* The one-line item (icon + label). The console's two-line item
     top-aligns in admin-shell.css (D5.2 row 3). */
  .nav-item {
    appearance: none;
    background: transparent;
    border: none;
    font: inherit;
    text-align: left;
    width: 100%;
    display: flex;
    align-items: center;
    gap: var(--gap-close);
    padding: var(--control-pad-sm) var(--control-pad-md);
    border-radius: var(--shape-sm);
    color: var(--sidebar-text);
    font-size: var(--type-ui-size);
    font-weight: var(--type-ui-weight);
    cursor: pointer;
    margin-bottom: var(--space-half);
    transition: background-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
  }
  .nav-item:hover {
    color: var(--sidebar-text-strong);
    background: var(--sidebar-hover);
  }
  /* The selected tab; after :hover so the fill holds under the pointer. */
  .nav-item[aria-selected="true"] {
    background: var(--sidebar-active);
    color: var(--sidebar-text-strong);
  }

  .sidebar-footer {
    margin-top: auto;
    padding-top: var(--inset-xl);
    display: flex;
    flex-direction: column;
    gap: var(--gap-close);
    border-top: var(--border-width) solid var(--sidebar-divider);
  }

  /* --- Topbar ---------------------------------------------------------- */
  /* min-height, not height: the bar wraps at 640 (D5.2 row 9). */
  .topbar {
    min-height: 56px;
    background: var(--surface);
    border-bottom: var(--border-width) solid var(--border);
    display: flex;
    align-items: center;
    padding: var(--inset-sm) var(--shell-pad);
    justify-content: space-between;
    gap: var(--gap-related);
    position: sticky;
    top: 0;
    z-index: var(--z-sticky);
  }
  .topbar-title {
    font-size: var(--type-ui-size);
    color: var(--text-muted);
    font-weight: var(--type-ui-weight);
    letter-spacing: var(--type-ui-tracking);
  }
  /* Wraps at every width: at 320 the console's pill and toggle measured
     past 288px on one line. */
  .topbar-actions {
    display: flex;
    min-width: 0;
    flex-wrap: wrap;
    justify-content: flex-end;
    align-items: center;
    gap: var(--gap-related);
  }

  /* --target-min square: the toggle was 38px in the portal and 34px in
     the console (D5.2 row 8). */
  .theme-toggle {
    appearance: none;
    width: var(--target-min);
    height: var(--target-min);
    border: var(--border-width) solid var(--border);
    border-radius: var(--button-radius);
    background: var(--surface);
    color: var(--text-dim);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: color var(--dur-base) var(--ease-standard), background-color var(--dur-base) var(--ease-standard), transform var(--dur-base) var(--ease-standard);
    flex: none;
  }
  .theme-toggle:hover { color: var(--primary); background: var(--surface-2); }
  [data-theme="light"] .theme-toggle .icon-moon { display: block; }
  [data-theme="light"] .theme-toggle .icon-sun { display: none; }
  [data-theme="dark"] .theme-toggle .icon-moon { display: none; }
  [data-theme="dark"] .theme-toggle .icon-sun { display: block; }

  .session-bar {
    display: inline-flex;
    align-items: center;
    gap: var(--gap-close);
    padding: var(--inset-xs) var(--inset-sm) var(--inset-xs) var(--inset-md);
    border: var(--border-width) solid var(--border);
    border-radius: var(--radius-pill);
    background: var(--surface);
    font-size: var(--type-caption-size);
    color: var(--text-dim);
    transition: background-color var(--dur-base) var(--ease-standard), border-color var(--dur-base) var(--ease-standard), color var(--dur-base) var(--ease-standard);
  }
  .session-bar .session-dot {
    width: 7px;
    height: 7px;
    border-radius: var(--radius-circle);
    background: var(--success);
    flex: none;
    transition: background-color var(--dur-base) var(--ease-standard);
  }
  .session-bar .session-text {
    white-space: nowrap;
    font-weight: var(--fw-medium);
    display: inline-flex;
    align-items: center;
    gap: var(--gap-tight);
  }
  .session-bar.warn {
    background: var(--warn-tint);
    border-color: transparent;
    color: var(--warn-ink);
  }
  .session-bar.warn .session-dot { background: var(--warn); }
  .session-bar.danger {
    background: var(--danger-tint);
    border-color: transparent;
    color: var(--danger-ink);
  }
  .session-bar.danger .session-dot { background: var(--danger); }

  .signout-btn {
    appearance: none;
    background: transparent;
    border: none;
    font: inherit;
    font-size: var(--type-badge-size);
    font-weight: var(--type-badge-weight);
    color: currentColor;
    cursor: pointer;
    padding: var(--space-1) var(--control-pad-md);
    border-radius: var(--radius-pill);
    letter-spacing: var(--type-badge-tracking);
    opacity: 0.75;
    transition: opacity var(--dur-quick) var(--ease-standard), background var(--dur-quick) var(--ease-standard);
  }
  /* A 6% wash of the current navy; it was #0B3C5D, retired 2026-09-18
     (D5.2 row 12). */
  .signout-btn:hover {
    opacity: 1;
    background: color-mix(in srgb, var(--primary) 6%, transparent);
  }
  [data-theme="dark"] .signout-btn:hover { background: var(--sidebar-active); }

  /* --- Main column ----------------------------------------------------- */
  /* The column's frame. What sits inside it — .admin-main, the page head —
     is section [A] below. */
  .admin-body {
    display: flex;
    flex-direction: column;
    min-width: 0;
  }

  /* Below 860 the sidebar becomes one scrolling row, and below 640 the
     topbar wraps: both are in "The two viewport steps", at the end. */

  /* Forced colours paint a fill as Canvas, so the session dot vanished on
     both surfaces. System colour only; the state is in the bar's text. */
  @media (forced-colors: active) {
    .session-bar .session-dot,
    .session-bar.warn .session-dot,
    .session-bar.danger .session-dot { background: CanvasText; forced-color-adjust: none; }
  }

  /* ====================================================================
     The shared shell classes (intent-layer programme, W4, 2026-09-29).
     Seeded here, adopted by wave 3: each section below is one wave-3
     task's, and that task edits its own section only. Rulings:
     .superpowers/sdd/intent-layer-programme-2026-09-29/shell-inventory.md.
     Tokens and roles only. A surface's divergence goes through the
     section's hooks, set on the host, never a restated property: an
     unlayered copy beats this layer outright, so wave 3 deletes each
     surface copy in the commit that adopts the class.
     ==================================================================== */

  /* === [A] Page frame: main column and page head ======================= */
  /* Hooks: --shell-main-end (the column's bottom padding) and
     --shell-main-max (its cap). The console keeps the 48px default and
     caps at 1200px (1280px on Attenuation); the portal ramps its foot from
     64 to 96px and has no cap. Each sets them on its own .admin-main rule. */
  .admin-main {
    width: 100%;
    padding: var(--shell-pad) var(--shell-pad) var(--shell-main-end, var(--section-space));
    max-width: var(--shell-main-max, none);
  }
  .admin-h1 {
    font-family: var(--type-title-family);
    font-variation-settings: var(--type-title-axes);
    font-size: var(--type-title-size);
    font-weight: var(--type-title-weight);
    letter-spacing: var(--type-title-tracking);
    margin: 0 0 var(--gap-tight);
    color: var(--text);
    line-height: var(--type-title-leading);
    text-wrap: balance;
  }
  /* One head rhythm (shell-inventory.md, Rulings): eyebrow, h1 (.admin-h1),
     then the lede 4px under it (.admin-h1's own margin), and 24px from the
     head to the content. */
  .page-head { margin: 0 0 var(--gap-group-lg); }
  .page-head > :last-child { margin-bottom: 0; }
  /* Page headings use the selected skin's shared type roles on both portals. */
  .page-head .eyebrow {
    display: block;
    margin: 0 0 var(--gap-tight);
    font-family: var(--type-eyebrow-family);
    letter-spacing: var(--type-eyebrow-tracking);
  }
  .page-lede {
    margin: 0;
    color: var(--text-dim);
    font-size: var(--type-body-sm-size);
    line-height: var(--type-body-sm-leading);
    max-width: var(--measure);
  }
  /* The head with its own figures beside it (the workspace's stats). */
  .page-head--split {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--gap-group-lg);
  }
  /* The phone gutter and head size: "The two viewport steps", at the end. */

  /* === [B] Cards: body, head and rail =================================== */
  /* Hooks: --shell-card-pad, --shell-card-radius and --shell-card-shadow,
     for the outliers that keep their values (account-card radius-md,
     outlet-card padding 20, att-editor radius-xl). The defaults sit on the
     card itself, so the bespoke class beside it (unlayered) retunes one by
     setting it, and the raw-values guard reads each value where it is set.
     Write the bespoke class first in markup (`class="trust-group
     shell-card"`) so the audit probe names the element by it
     (tests/audit/_names.js). */
  /* The shared card box lives in design-system/primitives.css. */
  /* The card head: a display-set title and a muted line under it. Hooks,
     each defaulted on the element so the bespoke class beside it retunes
     one: --shell-card-title-size, -axes, -tracking, -leading and -gap (the
     space under it); --shell-card-sub-leading, -measure and -color. The
     portal's h2 titles need portal.css to restate the three a base.css
     `h2` would take (see `.portal-shell .shell-card-title` there). */
  .shell-card-title {
    --shell-card-title-size: var(--text-lg);
    --shell-card-title-axes: 'SOFT' 30, 'opsz' 96;
    --shell-card-title-tracking: var(--tracking-snug);
    --shell-card-title-leading: var(--leading-heading);
    --shell-card-title-gap: var(--gap-tight);
    margin: 0 0 var(--shell-card-title-gap);
    font-family: var(--type-card-title-family);
    font-variation-settings: var(--shell-card-title-axes);
    font-size: var(--shell-card-title-size);
    font-weight: var(--type-card-title-weight);
    letter-spacing: var(--shell-card-title-tracking);
    line-height: var(--shell-card-title-leading);
    color: var(--text);
  }
  .shell-card-sub {
    --shell-card-sub-leading: var(--leading-body);
    --shell-card-sub-measure: 64ch;
    --shell-card-sub-color: var(--text-muted);
    margin: 0;
    font-size: var(--type-body-sm-size);
    line-height: var(--shell-card-sub-leading);
    color: var(--shell-card-sub-color);
    max-width: var(--shell-card-sub-measure);
  }
  /* The leading rail. Hooks: --rail-w and --rail-color, set on the host:
     a state or tier rule recolours the rail by setting --rail-color, not
     with a ::before rule of its own. */
  .shell-card--rail { position: relative; overflow: hidden; }
  .shell-card--rail::before {
    content: '';
    position: absolute;
    inset: 0 auto 0 0;
    width: var(--rail-w, 4px);
    background: var(--rail-color, color-mix(in srgb, var(--accent) 60%, var(--surface)));
    border-top-left-radius: inherit;
    border-bottom-left-radius: inherit;
    transition: background var(--dur-base) var(--ease-standard);
  }

  /* === [C] Empty states ================================================= */
  /* Compact and dashed by default, one treatment for the whole family
     (the portal's accepting and trust empties; the console's verify list,
     pipeline list and lifecycle timeline). --hero is the workspace and
     lifecycle pair (icon, a line, a sub-line). */
  .shell-empty {
    padding: var(--inset-2xl);
    text-align: center;
    color: var(--text-muted);
    font-size: var(--type-body-sm-size);
    border: var(--border-width) dashed var(--border);
    border-radius: var(--shape-md);
  }
  .shell-empty--hero {
    background: var(--surface);
    border-color: var(--border-strong);
    border-radius: var(--shape-lg);
    padding: var(--space-10) var(--inset-3xl);
    color: var(--text-dim);
    font-size: var(--type-body-size);
  }
  .shell-empty-icon {
    display: flex;
    justify-content: center;
    margin-bottom: var(--gap-related);
    line-height: 0;
    color: var(--accent);
  }
  /* One measure for both heroes' sub-line: the workspace's was 44ch, the
     lifecycle's 50ch. */
  .shell-empty-sub {
    margin: var(--gap-tight) auto 0;
    max-width: var(--measure-narrow);
    font-size: var(--type-body-sm-size);
    color: var(--text-muted);
  }

  /* === [D1] Panel switching ============================================= */
  /* One entrance for both surfaces' panels: a 4px lift over 0.22s.

     The trigger is CSS alone. Both tab scripts show a panel by removing
     `hidden` (display: none), and CSS Animations restarts an element's
     animations when it leaves display: none. Measured 2026-09-29 in
     Chromium, Firefox and WebKit: a panel shown for the first time, and
     one shown again after it has already run, both start the animation
     at 0ms. The portal's old comment ("animations don't auto-restart when
     an element comes back from hidden") was wrong, and its forced-reflow
     `--enter` class only added a replay for a panel that was ALREADY
     showing, which is a tab re-selecting itself or a re-init after
     Revert. Neither is an entrance, so neither replays now.
     tests/boot/panel-entrance.spec.js holds all three engines to it.

     No fill mode, so no transform (and no containing block) outlives the
     animation. Reduced motion gets no entrance at all, rather than each
     surface's global 0.01ms collapse. */
  @media (prefers-reduced-motion: no-preference) {
    .shell [role="tabpanel"] { animation: shell-panel-in var(--dur-base) var(--ease-out); }
  }
  @keyframes shell-panel-in {
    from { opacity: 0; transform: translateY(4px); }
    to { opacity: 1; transform: translateY(0); }
  }

  /* === [D2] Sidebar: groups and focus =================================== */
  /* The focus ring's colour inside the sidebar. A custom property, not an
     outline-color: a layered outline-color loses to each surface's
     unlayered `:focus-visible { outline: 3px solid var(--focus) }`. */
  .sidebar { --surface-focus: var(--sidebar-accent); }
  /* One group element on both surfaces: div.nav-section > div.nav-section-label,
     inside the sidebar's tablist. The wrapper carries no role, so the nav
     still owns its tabs. The gap BETWEEN groups, --gap-group-md, is each
     surface's .sidebar-nav gap, not a margin here.
     The defaults are the portal's. The console sets every hook in
     admin-shell.css's :root:
       --shell-label-font, -weight, -tracking  the type: body, 700, .12em
                                               (the console: mono, 600, .22em)
       --shell-label-rule        a line under the label: none (the console's
                                 hairline)
       --shell-label-rule-gap    the text to that line: 0
       --shell-label-inset       the label's side margins: 0
       --shell-label-after       the label to its first item: --gap-tight
       --shell-section-gap       between a group's items: --space-half
       --shell-strip-section     the phone strip's group: contents
       --shell-strip-label       the phone strip's label: none */
  /* The census counts this as a stack base (shell.css's third, the raise W4
     accepted as temporary); it stays one until the markup adopts .stack. */
  .nav-section { display: flex; flex-direction: column; gap: var(--shell-section-gap, var(--space-half)); }
  .nav-section-label {
    font-family: var(--shell-label-font, var(--font-body));
    font-size: var(--type-overline-size);
    font-weight: var(--shell-label-weight, var(--fw-bold));
    letter-spacing: var(--shell-label-tracking, var(--tracking-caps-md));
    text-transform: var(--type-overline-transform);
    color: var(--sidebar-text);
    padding: 0 var(--inset-md) var(--shell-label-rule-gap, 0);
    margin: 0 var(--shell-label-inset, 0) var(--shell-label-after, var(--gap-tight));
    border-bottom: var(--shell-label-rule, none);
  }
  /* In the phone strip: "The two viewport steps", at the end. */

  /* === [E] Session and page globals ===================================== */
  /* The countdown in the session bar. Hook: --session-timer-ink, so each
     surface keeps its colour (the portal's is the default --text-muted, the
     console's --text). The bar's own warn/danger tint is above, beside
     .session-bar; both surfaces set it at the same thresholds, from
     lib/session-grade.js. */
  .session-bar .session-timer {
    font-family: var(--type-mono-family);
    font-size: var(--type-mono-size);
    font-weight: var(--fw-medium);
    letter-spacing: var(--type-mono-tracking);
    font-variant-numeric: var(--type-mono-numeric);
    color: var(--session-timer-ink, var(--text-muted));
  }
  /* The portal clears the digits while more than 30 minutes remain; an empty
     timer collapses, so the chip carries no gap around an invisible value. */
  .session-bar .session-timer:empty { display: none; }
  .session-bar.warn .session-timer { color: var(--warn-ink); }
  .session-bar.danger .session-timer { color: var(--danger-ink); }
  /* A jump target, and a control focused by Shift+Tab, clears the sticky
     topbar at its measured height (--shell-scroll-offset, above). */
  html { scroll-padding-top: var(--shell-scroll-offset); }

  /* === The two viewport steps ========================================== */
  /* One block per step, last in the sheet so that every rule in them comes
     after the base rule it overrides (the frame's, and sections [A] and
     [D2]'s). Wave 3 seeded a block of each in its own section; folding them
     here returns media_width_blocks to 2 (the intent-layer review's M3). */

  /* Below 860 the sidebar becomes one row that scrolls, with every item at
     --target-min, rather than a wrapped block (D5.2 row 7). The phone strip
     keeps each surface's own reading of the nav groups: the portal's
     dissolve into the one row and its labels go (flowing inline between the
     icons, PROFILE and OPERATIONS read as labels for the wrong ones); the
     console's stay runs of the row, each label divided off to its right. */
  @media (max-width: 860px) {
    .brand--compact { display: flex; width: var(--target-min); min-height: var(--target-min); margin: 0; flex: none; }
    .shell { grid-template-columns: 1fr; }
    .sidebar {
      position: static;
      height: auto;
      flex-direction: row;
      flex-wrap: nowrap;
      align-items: center;
      padding: var(--inset-sm) var(--space-3);
      gap: var(--space-half);
      overflow-x: auto;
      overflow-y: hidden;
      scroll-padding-inline: var(--space-3);
    }
    .sidebar .brand { margin-bottom: 0; margin-right: auto; flex: none; }
    .nav-item {
      width: auto;
      flex: none;
      margin-bottom: 0;
      padding: var(--control-pad-md);
      min-height: var(--target-min);
      white-space: nowrap;
    }
    .nav-section {
      display: var(--shell-strip-section, contents);
      flex-direction: row;
      flex-wrap: nowrap;
      gap: var(--gap-tight);
      align-items: center;
    }
    .nav-section-label {
      display: var(--shell-strip-label, none);
      border-bottom: none;
      padding: 0 var(--inset-sm) 0 var(--inset-xs);
      margin: 0;
      border-right: var(--border-width) solid var(--sidebar-divider);
    }
  }

  /* Below 640 the topbar wraps, and the page takes the phone gutter and a
     smaller head. */
  @media (max-width: 640px) {
    .topbar { padding: var(--inset-sm) var(--page-gutter-narrow); gap: var(--gap-close); flex-wrap: wrap; }
    .session-bar { min-width: 0; }
    .session-bar .session-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
    .signout-btn { white-space: nowrap; }
    .admin-main { padding: var(--space-5) var(--page-gutter-narrow) var(--shell-main-end, var(--section-space)); }
    .admin-h1 { font-size: var(--text-xl); }
  }
}
