/* PR-UNIFY-C6-FIX1 (2026-05-24): bumps SW autobust hash so tablets
 * holding the pre-C6 shop-floor.js drop it on next reload. No
 * visible change. Remove on the next substantive shell.css edit. */
/*
 * CabinetWorks Pro — Shell Layout
 * Structural rules for the persistent app chrome:
 *   #app-topbar     — 56px fixed top bar
 *   #app-sidebar    — 56px collapsed / 220px expanded left nav
 *   #app-workspace  — scrolling main content host for all #pg-* divs
 *   #app-statusbar  — 32px fixed status bar
 *   #app-bottom-nav — 56px bottom nav (portrait tablet only)
 *
 * RULES:
 *   - ZERO magic-number px values. Every dimension comes from a --sp-* or
 *     --z-* token defined in tokens.css.
 *   - ZERO inline styles. Shell chrome styled entirely here.
 *   - ZERO colour values. All colours via theme tokens (var(--bg) etc.)
 *     already defined in index.html's :root[data-theme] blocks.
 *   - This file does NOT touch #pg-* internal markup.
 *     Steps 4-6 of the blueprint own that migration.
 *   - Sidebar pin state driven by [data-sidebar-pinned] on <body>.
 *   - Active page driven by [data-page="hub|cab|door|orders|settings"] on <body>.
 */

/* ======================================================================
   TOPBAR
   ====================================================================== */
#app-topbar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--sp-10);         /* 56px */
  z-index: var(--z-topbar);     /* 30   */
  background: var(--sf);
  border-bottom: 1px solid var(--bdr);
  display: flex;
  align-items: center;
  padding: 0 var(--sp-6);       /* 24px side padding */
  gap: var(--sp-4);             /* 16px internal gap */
  /* No transition on topbar — it is always visible, never animates */
}

/* Hamburger menu toggle — first in the topbar for thumb reach.
   48px tap target per blueprint Section 5. Low visual weight — icon only. */
#app-topbar .topbar-menu-btn {
  flex-shrink: 0;
  min-width: var(--sp-9);       /* 48px */
  min-height: var(--sp-9);
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  color: var(--txt);
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: background var(--dur-chrome) var(--ease-out);
}

#app-topbar .topbar-menu-btn:hover,
#app-topbar .topbar-menu-btn:focus-visible {
  background: var(--pn-h, var(--pn));
  outline: none;
}

/* Legacy static-logo container — kept for any markup that still uses it. */
#app-topbar .topbar-logo {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ── Topbar nav cluster: Back + Home + page-title ─────────────────────────
   Layout: [hamburger] [← Back | Home | Page Title] [search center] [actions right]
   Back is hidden (hidden attr) when the nav stack is empty; Home is always
   visible. The cluster is a single <nav> so the two buttons are semantically
   grouped and read as one landmark. */
#app-topbar .topbar-nav-cluster {
  display: flex;
  align-items: center;
  gap: var(--sp-1);          /* 4px between Back and Home */
  flex-shrink: 0;
}

/* Shared base for both Back and Home buttons inside the cluster */
#app-topbar .topbar-back-btn,
#app-topbar .topbar-home-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);           /* 4px between icon and label */
  padding: 0 var(--sp-2);     /* 8px h-pad — comfortable without wasting width */
  min-height: 44px;           /* ≥44px tap target (WCAG 2.5.5) */
  min-width: 44px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm, 6px);
  color: var(--txt);
  font-family: var(--ff-ui);
  font-size: 13px;
  font-weight: var(--fw-semibold);
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur-chrome) var(--ease-out),
              border-color var(--dur-chrome) var(--ease-out),
              opacity var(--dur-chrome) var(--ease-out);
}

/* Back chevron: subtle ghost — it's secondary to Home */
#app-topbar .topbar-back-btn {
  color: var(--dim);
  font-weight: var(--fw-medium);
}
#app-topbar .topbar-back-btn:hover,
#app-topbar .topbar-back-btn:focus-visible {
  background: color-mix(in srgb, var(--txt) 6%, transparent);
  color: var(--txt);
  outline: none;
}
#app-topbar .topbar-back-btn:active {
  background: color-mix(in srgb, var(--txt) 10%, transparent);
}
/* Back is hidden when nav stack is empty */
#app-topbar .topbar-back-btn[hidden] { display: none; }

/* Home button: uses the accent stripe when active on the Hub */
#app-topbar .topbar-home-btn:hover,
#app-topbar .topbar-home-btn:focus-visible {
  background: color-mix(in srgb, var(--acc) 8%, transparent);
  border-color: color-mix(in srgb, var(--acc) 35%, var(--bdr));
  outline: none;
}
#app-topbar .topbar-home-btn:active {
  background: color-mix(in srgb, var(--acc) 14%, transparent);
}
#app-topbar .topbar-home-btn[aria-current="page"] {
  background: color-mix(in srgb, var(--acc) 10%, transparent);
  border-color: color-mix(in srgb, var(--acc) 50%, var(--bdr));
}
#app-topbar .topbar-home-btn:focus-visible,
#app-topbar .topbar-back-btn:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 2px;
}
#app-topbar .topbar-home-btn svg,
#app-topbar .topbar-back-btn svg { flex-shrink: 0; }

/* Page title — shown between Home and the search bar.
   Tells the operator which screen they're on at a glance. */
#app-topbar .topbar-page-title {
  font-family: var(--ff-ui);
  font-size: 13px;
  font-weight: var(--fw-semibold);
  color: var(--dim);
  padding: 0 var(--sp-1);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 160px;
  /* Not a tap target — no pointer events */
  pointer-events: none;
  user-select: none;
}
#app-topbar .topbar-page-title[hidden] { display: none; }

/* Narrow screens — hide text labels, keep icons at 44×44 */
@media (max-width: 767px) {
  #app-topbar .topbar-home-label,
  #app-topbar .topbar-back-label { display: none; }
  #app-topbar .topbar-back-btn,
  #app-topbar .topbar-home-btn {
    padding: 0 var(--sp-2);
    justify-content: center;
  }
  #app-topbar .topbar-page-title {
    max-width: 100px;
    font-size: 12px;
  }
}
@media (max-width: 480px) {
  #app-topbar .topbar-page-title { display: none; }
}

#app-topbar .topbar-search {
  flex: 1;
  max-width: 480px;
  margin: 0 auto;
}

#app-topbar .topbar-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-2);             /* 8px */
  margin-left: auto;
  flex-shrink: 0;
}

/* Each icon in the right cluster is a 56×56 tap target */
#app-topbar .topbar-actions > * {
  min-width: var(--sp-10);      /* 56px */
  min-height: var(--sp-10);     /* 56px */
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ======================================================================
   SIDEBAR — default HIDDEN off-screen; slides in as overlay
   ======================================================================
   User feedback 2026-04-22: "prefer if we can hide them, it looks too
   cluttered." Behaviour changed from persistent-chrome (previous default:
   56px always visible, pinned at >=1024px) to on-demand overlay. User
   opens via the hamburger button in the topbar. Sidebar slides in as a
   220px overlay (does NOT push workspace content), dismisses on: ESC,
   backdrop click, or nav-item click. No pin persistence — always opens
   fresh. */
#app-sidebar {
  position: fixed;
  top: var(--sp-10);            /* below topbar: 56px */
  left: 0;
  /* 2026-04-24 (slim-menu): shrink to content rather than stretch full
     viewport height. Avoids the empty-middle "void" UX flagged when
     the menu collapsed from 12 items to 4 (or 2 for non-admins). The
     bottom anchor is released; max-height keeps it from overflowing on
     short viewports — the inner scroll region still handles overflow. */
  bottom: auto;
  max-height: calc(100dvh - var(--sp-10));
  width: 220px;                 /* full width when visible */
  z-index: var(--z-sidebar);    /* 20 */
  background: var(--sf);
  border-right: 1px solid var(--bdr);
  border-bottom: 1px solid var(--bdr);
  overflow: hidden;             /* scroll lives on #app-sidebar-scroll */
  display: flex;
  flex-direction: column;
  transform: translateX(-100%); /* HIDDEN by default */
  transition: transform var(--dur-chrome) var(--ease-in-out);
  box-shadow: var(--shadow-modal);
}

/* If the docked layout is ever re-enabled per-page, restore the
   full-height sidebar so the workspace shift continues to look right. */
body[data-sidebar-docked="true"]:not([data-focus-mode="active"]) #app-sidebar {
  bottom: 0;
  max-height: none;
  border-bottom: none;
}

/* Inner scrollable region — Workspace + Library groups live here.
   flex: 1 so it consumes all available height between the close button
   and the Settings footer. min-height: 0 is required for flex children
   with overflow to actually scroll rather than grow. */
#app-sidebar-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-gutter: stable;
  scrollbar-width: thin;
  display: flex;
  flex-direction: column;
  /* PR-UI-CLEANUP (2026-06-30): the pinned .sidebar-footer (Settings) now sits
     below this scroll region and reserves the statusbar clearance, so the old
     44px bottom padding here is no longer needed (it just left a big gap above
     the footer). 8px keeps a little breathing room under the last nav group. */
  padding-bottom: 8px;
}

/* PR-UI-CLEANUP (2026-06-30): pinned Settings footer at the BOTTOM of the
   sidebar (operator: "settings should be at the bottom here not by the user").
   Sibling of #app-sidebar-scroll inside the flex-column #app-sidebar, so it
   stays put while the nav groups scroll above it. margin-top:auto pins it to
   the bottom if the sidebar is ever full-height (docked mode); the 36px bottom
   padding clears the 32px fixed #app-statusbar so "Settings" is never hidden. */
.sidebar-footer {
  flex: 0 0 auto;
  margin-top: auto;
  border-top: 1px solid var(--bdr);
  padding: 8px 8px 36px;
  background: var(--sf);
}
.sidebar-footer .sidebar-nav-item { width: 100%; }

#app-sidebar-scroll::-webkit-scrollbar {
  width: 6px;
}
#app-sidebar-scroll::-webkit-scrollbar-track {
  background: transparent;
}
#app-sidebar-scroll::-webkit-scrollbar-thumb {
  background: var(--bdr);
  border-radius: 3px;
}
#app-sidebar-scroll::-webkit-scrollbar-thumb:hover {
  background: var(--dim);
}

/* Open state — triggered by body[data-sidebar-open="true"] via the
   hamburger toggle. Slide-in, no push. */
body[data-sidebar-open="true"] #app-sidebar {
  transform: translateX(0);
}

/* ======================================================================
   NAV-DOCK (2026-04-24) — Pinned sidebar + focus-mode overrides
   ======================================================================
   Docked state (body[data-sidebar-docked="true"]): sidebar permanently
   visible on the left; workspace shifts right by sidebar width. Set on
   Hub + design list pages (Cabinet list, Doors list, Orders) + all
   Library sub-pages + Settings. Cleared on login.

   Focus-mode (body[data-focus-mode="active"]): overrides dock — sidebar
   hides so editors get full-screen real estate. Hamburger remains
   available for temporary overlay access during editing.

   Breakpoints (shop-tablet-optimized):
     >= 1024px — pin at 240px (desktop + landscape iPad Pro)
     768-1023  — pin at 200px (landscape 10.2" iPad, portrait iPad Pro)
     < 768     — never pinned; bottom nav handles primary navigation

   Hamburger visibility:
     pinned & not-focus & >= 768px → HIDDEN (nothing to toggle)
     focus-mode OR < 768px         → VISIBLE (overlay still usable)
*/

/* Smooth workspace reflow on dock / undock. Narrow-viewport layouts
   leave left:0 regardless so they don't animate. */
@media (min-width: 768px) {
  #app-workspace {
    transition: left var(--dur-chrome) var(--ease-in-out);
  }
}

/* Wide (>= 1024px): sidebar pinned at 240px */
@media (min-width: 1024px) {
  body[data-sidebar-docked="true"]:not([data-focus-mode="active"]) #app-sidebar {
    width: 240px;
    transform: translateX(0);
    box-shadow: none;
  }
  body[data-sidebar-docked="true"]:not([data-focus-mode="active"]) #app-workspace {
    left: 240px;
  }
  body[data-sidebar-docked="true"]:not([data-focus-mode="active"]) #app-topbar .topbar-menu-btn {
    display: none;
  }
  /* Backdrop + close-X button are overlay affordances; hide when pinned */
  body[data-sidebar-docked="true"]:not([data-focus-mode="active"]) #app-sidebar-backdrop,
  body[data-sidebar-docked="true"]:not([data-focus-mode="active"]) #app-sidebar .sidebar-close-btn {
    display: none;
  }
}

/* Tablet landscape (768-1023px): narrower pin at 200px */
@media (min-width: 768px) and (max-width: 1023px) {
  body[data-sidebar-docked="true"]:not([data-focus-mode="active"]) #app-sidebar {
    width: 200px;
    transform: translateX(0);
    box-shadow: none;
  }
  body[data-sidebar-docked="true"]:not([data-focus-mode="active"]) #app-workspace {
    left: 200px;
  }
  body[data-sidebar-docked="true"]:not([data-focus-mode="active"]) #app-topbar .topbar-menu-btn {
    display: none;
  }
  body[data-sidebar-docked="true"]:not([data-focus-mode="active"]) #app-sidebar-backdrop,
  body[data-sidebar-docked="true"]:not([data-focus-mode="active"]) #app-sidebar .sidebar-close-btn {
    display: none;
  }
}

/* Focus-mode affordance on hamburger — signals "sidebar dismissed but
   available" during active editing. Subtle left-edge accent rather
   than a notification dot (dots misread as alerts). */
body[data-focus-mode="active"] #app-topbar .topbar-menu-btn {
  box-shadow: inset 3px 0 0 var(--acc);
}

/* Respect OS reduce-motion. Sidebar + workspace snap instantly. */
@media (prefers-reduced-motion: reduce) {
  #app-sidebar,
  #app-workspace {
    transition: none !important;
  }
}

/* Backdrop — visible only when sidebar is open, dims workspace */
#app-sidebar-backdrop {
  position: fixed;
  inset: var(--sp-10) 0 0 0;    /* below topbar, covers workspace + statusbar */
  z-index: calc(var(--z-sidebar) - 1);
  /* PR-LIB-COLOR-TOKENS: theme-stable dark scrim token (always darkens,
     even in light themes — a brightening overlay would look like fog). */
  background: var(--scrim);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-chrome) var(--ease-in-out);
}

body[data-sidebar-open="true"] #app-sidebar-backdrop {
  opacity: 1;
  pointer-events: auto;
}

/* ======================================================================
   SIDEBAR — Grouped nav (2026-04-22 IA update)
   ======================================================================
   Sidebar content arranges into 3 groups:
     1. .sidebar-nav-group                    — "Workspace" (Hub, Cabinet,
                                                 Doors, Orders): 4 daily
                                                 workflow surfaces, full
                                                 visual weight.
     2. .sidebar-nav-group.sidebar-nav-secondary — "Library" (Projects,
                                                 Customers, Libraries,
                                                 Cut Lists): 4 supporting
                                                 surfaces, reduced weight
                                                 (lighter icon stroke,
                                                 slightly dimmer labels
                                                 at rest).
     3. .sidebar-nav-group.sidebar-settings-anchor — "Account" (Settings):
                                                 anchored at bottom via
                                                 margin-top:auto.
   Section labels are subtle: 11px, uppercase, letter-spaced, dim color.
   They orient without competing with the nav items they head up. */

#app-sidebar .sidebar-nav-group {
  display: flex;
  flex-direction: column;
  /* PR-NAV-FIT-1 (2026-05-24): tightened from 12px to 8px per group
     so all 14 nav items (Workspace + Design + Production + Library)
     fit without scrolling on 720-800px-tall laptop viewports. */
  padding: var(--sp-2) 0;
}

#app-sidebar .sidebar-nav-group + .sidebar-nav-group {
  border-top: 1px solid var(--bdr);
}

#app-sidebar .sidebar-settings-anchor {
  /* Settings lives outside the scroll wrapper now (2026-04-22) so it is
     ALWAYS visible regardless of viewport height. flex-shrink: 0 keeps it
     from collapsing; a strong top-border separates it from the scroll
     region so it reads as "pinned footer" not "final list item". */
  flex-shrink: 0;
  border-top: 1px solid var(--bdr);
  background: var(--sf);
}

/* Section label — small caps-ish, dim. Announces the group without
   shouting. 44px tall to maintain even vertical rhythm with nav items.
   Not a tap target (no interactive role). Reduced-motion safe (no
   animation). */
#app-sidebar .sidebar-section-label {
  font-family: var(--ff-ui);
  font-size: var(--fs-xs, 11px);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--dim);
  /* PR-NAV-FIT-1 (2026-05-24): tightened from 12/16/8/16 to 8/16/4/16
     to reclaim 32px across 4 group labels. Less vertical gap still
     reads as a label (it's the uppercase + dim color that does the
     "this is a section header" work, not the padding). */
  padding: var(--sp-2) var(--sp-4) var(--sp-1) var(--sp-4);
  user-select: none;
  line-height: 1;
}

/* Sidebar nav items — the list inside each group */
#app-sidebar .sidebar-nav {
  display: flex;
  flex-direction: column;
  padding: 0;                   /* group owns the outer padding now */
  list-style: none;
  margin: 0;
}

#app-sidebar .sidebar-nav-item {
  display: flex;
  align-items: center;
  gap: var(--sp-3);             /* 12px icon-to-label */
  /* PR-NAV-FIT-1 (2026-05-24): tightened from 56px to 44px so the
     full 14-item nav fits on a 720-800px laptop without scrolling.
     44px is the Apple HIG / Material minimum recommended tap target
     — still comfortable on iPad with gloved hands. */
  height: 44px;
  min-height: 44px;
  padding: 0 var(--sp-4);       /* 16px side padding */
  color: var(--dim);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  border: none;
  background: transparent;
  width: 100%;
  text-align: left;
  font-family: var(--ff-ui);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  text-decoration: none;
  /* PR-UI-6 (2026-04-27): + transform on hover for tactile feedback,
     master prompt §10.2 ("transform: translateX(1px)"). Reduced-motion
     users get instant snap because --dur-chrome is zeroed in the
     prefers-reduced-motion block of tokens.css. */
  transition: background var(--dur-chrome) var(--ease-out),
              color      var(--dur-chrome) var(--ease-out),
              transform  var(--dur-quick)  var(--spring-snappy);
}

#app-sidebar .sidebar-nav-item:hover,
#app-sidebar .sidebar-nav-item:focus-visible {
  background: var(--pn-h, var(--pn));
  color: var(--txt);
  outline: none;
  /* PR-UI-6: 1px right-shift on hover. Subtle Apple-Pro tactile cue;
     mirrors Final Cut Pro's sidebar feel. */
  transform: translateX(1px);
}

#app-sidebar .sidebar-nav-item[aria-current="page"],
#app-sidebar .sidebar-nav-item.active {
  color: var(--acc);
  background: var(--pn);
  border-left: 3px solid var(--acc);
  padding-left: calc(var(--sp-4) - 3px);  /* keep icon x-position stable */
}

/* Library group — reduced visual weight.
   Icons are rendered with stroke-width 1.75 in the markup (lighter than
   workspace group's 2.0). Items default to a slightly dimmer color and
   a slightly tighter 44px height — this distinguishes them as
   secondary without making them harder to tap. Hover + active state are
   identical to workspace items so the affordance is the same. */
#app-sidebar .sidebar-nav-secondary .sidebar-nav-item {
  /* PR-NAV-FIT-1 (2026-05-24): Library group rows tighten from 48px
     to 40px. Still distinguishable from the 44px primary rows (4px
     lighter weight reinforces the "secondary" hierarchy) and still
     tap-friendly on tablet (the icons are stroke-1.75 here vs 2.0
     primary so visual weight is already lower). */
  height: 40px;
  min-height: 40px;
  font-size: var(--fs-base);
  color: var(--dim);
}

#app-sidebar .sidebar-nav-secondary .sidebar-nav-item:hover,
#app-sidebar .sidebar-nav-secondary .sidebar-nav-item:focus-visible,
#app-sidebar .sidebar-nav-secondary .sidebar-nav-item[aria-current="page"] {
  color: var(--txt);
}

/* Active-state highlight for deep-linked hub sub-tabs. When body has
   data-page="hub" AND data-hub-tab matches a sub-id, the corresponding
   sidebar item gets the accent treatment. This keeps "Hub" unhighlighted
   when the user navigated to (say) Projects via sidebar. */
body[data-page="hub"][data-hub-tab="home"] #snav-hub,
body[data-page="hub"][data-hub-tab="proj"] #snav-projects,
body[data-page="hub"][data-hub-tab="cust"] #snav-customers,
body[data-page="hub"][data-hub-tab="lib"]  #snav-libraries,
body[data-page="hub"][data-hub-tab="std"]  #snav-standards {
  color: var(--acc);
  background: var(--pn);
  border-left: 3px solid var(--acc);
  padding-left: calc(var(--sp-4) - 3px);
}

/* When a Library sub-tab is active, dim the top-level Hub row so the
   user sees exactly one active item at a time. Visual hierarchy: only
   one "you are here" indicator. */
body[data-page="hub"][data-hub-tab="proj"] #snav-hub,
body[data-page="hub"][data-hub-tab="cust"] #snav-hub,
body[data-page="hub"][data-hub-tab="lib"]  #snav-hub,
body[data-page="hub"][data-hub-tab="std"]  #snav-hub,
body[data-page="hub"][data-hub-tab="cut"]  #snav-hub {
  color: var(--dim);
  background: transparent;
  border-left-color: transparent;
  padding-left: var(--sp-4);
}

#app-sidebar .sidebar-nav-item .nav-icon {
  flex-shrink: 0;
  width: var(--sp-6);           /* 24px */
  height: var(--sp-6);
  display: flex;
  align-items: center;
  justify-content: center;
}

#app-sidebar .sidebar-nav-item .nav-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* Sidebar is always full-width when visible now — label always shown */
  opacity: 1;
}

/* Legacy pin button kept as a Close button in the overlay header.
   User clicks × to dismiss the sidebar. Aligned right for thumb reach. */
#app-sidebar .sidebar-pin-btn {
  display: flex;
  align-items: center;
  justify-content: flex-end;    /* close X anchors right */
  height: var(--sp-9);          /* 48px */
  min-height: var(--sp-9);
  padding: 0 var(--sp-4);
  border: none;
  background: transparent;
  color: var(--dim);
  cursor: pointer;
  border-bottom: 1px solid var(--bdr);
  width: 100%;
  transition: color var(--dur-chrome) var(--ease-out);
}

#app-sidebar .sidebar-pin-btn:hover,
#app-sidebar .sidebar-pin-btn:focus-visible {
  color: var(--txt);
  outline: none;
}

/* ======================================================================
   WORKSPACE — main scrolling content host
   ======================================================================
   Sidebar is overlay, not push — workspace always extends to the left
   edge. Bottom offset is 0 when status bar is hidden (default), or 32px
   when status bar is shown. Workspace bottom tracks body[data-shell-status]. */
#app-workspace {
  position: fixed;
  top: var(--sp-10);            /* 56px — below topbar */
  left: 0;                      /* sidebar is overlay, not push */
  right: 0;
  bottom: 0;                    /* status bar hidden by default */
  overflow-y: auto;
  overflow-x: hidden;
  background: var(--bg);
}

/* Workspace shrinks by status-bar height only when status bar is visible */
body[data-shell-status]:not([data-shell-status=""]) #app-workspace,
body[data-offline] #app-workspace {
  bottom: 32px;
}

/* PR-FORGE-HUB-STATUSBAR (2026-06-13): always-on telemetry footer on
   desktop (>=768px). data-statusbar="on" is set permanently at load and
   reuses the SAME 32px layout math as the transient data-shell-status
   states. Tablet keeps the bar transient — the shop floor wants the
   vertical space. The strongest single "pro CAD/ERP, not a website" cue. */
@media (min-width: 768px) {
  body[data-statusbar="on"] #app-workspace { bottom: 32px; }
  body[data-statusbar="on"] #app-statusbar { display: flex; }
}

/* Portrait tablet (<768px): bottom nav visible — workspace must rise to clear it */
@media (max-width: 767px) {
  #app-workspace {
    bottom: var(--sp-10);       /* 56px — bottom-nav height */
  }
  body[data-shell-status]:not([data-shell-status=""]) #app-workspace,
  body[data-offline] #app-workspace {
    bottom: calc(var(--sp-10) + 32px); /* bottom-nav + statusbar */
  }
}

/* ======================================================================
   STATUS BAR — HIDDEN by default; visible only when there's a condition
   ======================================================================
   User feedback 2026-04-22: status bar was part of the "too cluttered"
   chrome. Now hides itself when nothing to report (all-green state).
   Visible when body has:
     data-shell-status="offline"  (network down)
     data-shell-status="loading"  (WASM still initializing)
     data-shell-status="queued"   (pending-writes queue non-empty)
     data-shell-status="error"    (any error condition)
     data-offline                 (legacy attribute — treated same)
   Empty/absent data-shell-status → status bar is display:none. */
#app-statusbar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  height: 32px;
  z-index: var(--z-statusbar);  /* 30 */
  background: var(--sf);
  border-top: 1px solid var(--bdr);
  display: none;                /* hidden by default */
  align-items: center;
  padding: 0 var(--sp-4);       /* 16px */
  gap: var(--sp-4);             /* 16px */
  font-family: var(--ff-ui);
  font-size: var(--fs-sm);      /* 12px */
  font-weight: var(--fw-medium);
  color: var(--dim);
  /* Numbers in status bar also must not animate */
}

/* Visible only when a status condition is active */
body[data-shell-status]:not([data-shell-status=""]) #app-statusbar,
body[data-offline] #app-statusbar {
  display: flex;
}

#app-statusbar .sb-dot {
  width: var(--sp-2);           /* 8px */
  height: var(--sp-2);
  border-radius: 50%;
  flex-shrink: 0;
}

#app-statusbar .sb-dot.ok     { background: var(--ok);   }
#app-statusbar .sb-dot.warn   { background: var(--warn);  }
#app-statusbar .sb-dot.error  { background: var(--no);    }

#app-statusbar .sb-sep {
  width: 1px;
  height: 16px;
  background: var(--bdr);
  flex-shrink: 0;
}

#app-statusbar .sb-spacer { flex: 1; }

/* Offline state — amber background */
body[data-offline] #app-statusbar {
  background: var(--warn);
  color: var(--warn-ink);
  border-top-color: var(--warn);
}

/* ======================================================================
   BOTTOM NAV (portrait tablet <768px only)
   ====================================================================== */
#app-bottom-nav {
  display: none;  /* hidden by default; shown only on portrait tablet */
  position: fixed;
  bottom: 32px;                 /* above status bar */
  left: 0;
  right: 0;
  height: var(--sp-10);         /* 56px */
  z-index: var(--z-sidebar);    /* 20 — same as sidebar since they are mutually exclusive */
  background: var(--sf);
  border-top: 1px solid var(--bdr);
  flex-direction: row;
  align-items: stretch;
}

@media (max-width: 767px) {
  #app-bottom-nav {
    display: flex;
  }
}

#app-bottom-nav .bnav-item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-1);             /* 4px icon-to-label */
  border: none;
  background: transparent;
  color: var(--dim);
  cursor: pointer;
  font-family: var(--ff-ui);
  font-size: var(--fs-sm);      /* 12px */
  font-weight: var(--fw-medium);
  min-height: var(--sp-10);     /* 56px tap target */
  padding: 0 var(--sp-1);
  text-decoration: none;
  transition: color var(--dur-chrome) var(--ease-out);
}

#app-bottom-nav .bnav-item:hover,
#app-bottom-nav .bnav-item:focus-visible {
  color: var(--txt);
  outline: none;
}

#app-bottom-nav .bnav-item[aria-current="page"],
#app-bottom-nav .bnav-item.active {
  color: var(--acc);
}

#app-bottom-nav .bnav-item .bnav-icon {
  width: var(--sp-6);           /* 24px */
  height: var(--sp-6);
}

#app-bottom-nav .bnav-item .bnav-label {
  font-size: var(--fs-sm);
  line-height: 1;
}

/* ======================================================================
   BODY DATA-PAGE ATTRIBUTE — active page visibility
   Every #pg-* div is hidden by default; the active page is shown
   based on the body[data-page] attribute. The existing go() function
   also toggles .on via classList, which keeps backward compatibility.
   ====================================================================== */

/* Default: pages hidden. The existing .pg / .on classes still work. */
/* This block merely ensures the new data-page attribute also works.  */
body[data-page="hub"]            #pg-hub            { display: block; }
body[data-page="cab"]            #pg-cab            { display: block; }
body[data-page="door"]           #pg-door           { display: block; }
body[data-page="orders"]         #pg-orders         { display: block; }
body[data-page="settings"]       #pg-settings       { display: block; }
body[data-page="server"]         #pg-server         { display: block; }
/* PR-STUDIO-1-FIXUP (2026-05-15): Cutlist Studio standalone page.
   Without this rule the page rendered hidden — the .pg.on class
   alone is overridden by the default-hidden behavior of #pg-*.
   PR-TMPL-7-APPLY-FIXUP (general-purpose P1, 2026-05-15): use
   `flex` (NOT `block`) so the .cs-page child's flex layout
   actually fills the page height; matches the implicit
   `.pg.on { display: flex }` the JS router toggles. */
body[data-page="cutlist-studio"] #pg-cutlist-studio { display: flex; }
/* PR-AUDIT-20 (2026-05-25): cabinet-side IPD page. Mirror of the
   pg-production rule so the page becomes visible when go() routes
   here. .on toggle is back-compat for the legacy display mode. */
body[data-page="production-cabinets"] #pg-production-cabinets { display: block; }

/* ======================================================================
   HUB TAB BAR — hidden (moved to sidebar's Library group)
   ======================================================================
   The old #h-tabs row (Home/Projects/Customers/Libraries/Cutlists) is now
   accessed via the sidebar's "Library" group. The tab buttons remain in
   the DOM so htab()'s .on-class sync + data-tab lookup still work, but
   they render at 0 height. Do NOT delete the #h-tabs div — legacy JS
   selectors depend on it. */
#h-tabs {
  display: none;
}

/* ======================================================================
   ACCESSIBILITY UTILITY — screen-reader-only visually hidden class
   ====================================================================== */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

/* ======================================================================
   TEMPLATE A COMPONENTS — Hub Dashboard (Phase I.4)
   All values from tokens.css. Zero colour literals. Zero inline styles.
   ====================================================================== */

/* ------------------------------------------------------------------
   Hub page header slot
   ------------------------------------------------------------------ */
.hub-page-header {
  padding: var(--sp-6) var(--sp-6) var(--sp-4);
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-4);
  flex-wrap: wrap;
}

.hub-page-header .hub-header-text {
  flex: 1;
  min-width: 0;
}

.hub-page-header h1 {
  font-family: var(--ff-ui);
  font-weight: var(--fw-bold);
  font-size: var(--fs-2xl);
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--txt);
  margin: 0;
}

.hub-page-header .hub-greeting {
  font-family: var(--ff-ui);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-xl);
  line-height: 1.3;
  color: var(--acc);
  margin: 0 0 var(--sp-1) 0;
}

.hub-page-header .hub-subtitle {
  font-family: var(--ff-ui);
  font-weight: var(--fw-regular);
  font-size: var(--fs-base);
  color: var(--dim);
  margin: var(--sp-1) 0 0 0;
  line-height: 1.5;
}

.hub-page-header .hub-primary-action {
  flex-shrink: 0;
  min-width: var(--sp-10);   /* 56px */
  min-height: var(--sp-9);   /* 48px */
  padding: 0 var(--sp-5);
  background: var(--acc);
  color: var(--acc-ink);
  border: none;
  border-radius: var(--radius-sm);
  font-family: var(--ff-ui);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-base);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  /* Transitions on chrome properties only — never text content */
  transition: background-color var(--dur-chrome) var(--ease-out),
              transform var(--dur-chrome) var(--ease-out),
              box-shadow var(--dur-chrome) var(--ease-out);
}

.hub-page-header .hub-primary-action:hover,
.hub-page-header .hub-primary-action:focus-visible {
  background: var(--acc2, var(--acc));
  transform: translateY(-1px);
  box-shadow: var(--shadow-card-hover);
  outline: none;
}

/* PR-HOME-REDESIGN BF-2 (2026-06-13): header create actions (right of the
   page title) + a flat ghost "secondary" button beside the filled primary. */
.hub-header-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-shrink: 0;
  flex-wrap: wrap;
}
.hub-header-actions .hub-secondary-action {
  min-height: var(--sp-9);   /* 48px — matches the primary */
  padding: 0 var(--sp-4);
  background: transparent;
  color: var(--txt);
  border: 1px solid var(--bdr-h, var(--bdr));
  border-radius: var(--radius-sm);
  font-family: var(--ff-ui);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-base);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  transition: border-color var(--dur-chrome) var(--ease-out),
              background var(--dur-chrome) var(--ease-out);
}
.hub-header-actions .hub-secondary-action:hover,
.hub-header-actions .hub-secondary-action:focus-visible {
  border-color: var(--acc);
  background: var(--pn-h);
  outline: none;
}

/* PR-HOME-REDESIGN BF-2: the two finished studio launcher cards, shown only
   inside the empty-queue state. Hairline border + accent icon chip + title/
   sub — flat, no glow, no lift (FORGE). Single card (floor) spans full width. */
.hub-studio-cards {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--sp-3);
  width: 100%;
  max-width: 560px;
  margin: var(--sp-4) auto 0;
}
.hub-studio-cards.is-single { grid-template-columns: minmax(0, 1fr); }
.hub-studio-card {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-4);
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: var(--radius-card, 12px);
  cursor: pointer;
  text-align: left;
  transition: border-color var(--dur-chrome) var(--ease-out),
              background var(--dur-chrome) var(--ease-out);
}
.hub-studio-card:hover,
.hub-studio-card:focus-visible {
  border-color: var(--acc);
  background: var(--pn-h);
  outline: none;
}
.hub-studio-card .hsc-ico {
  flex-shrink: 0;
  width: 40px; height: 40px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--acc) 14%, transparent);
  color: var(--acc);
}
.hub-studio-card .hsc-ico svg { width: 22px; height: 22px; }
.hub-studio-card .hsc-txt { display: flex; flex-direction: column; min-width: 0; }
.hub-studio-card .hsc-title {
  font-family: var(--ff-ui);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-base);
  color: var(--txt);
}
.hub-studio-card .hsc-sub {
  font-size: var(--fs-sm, 12px);
  color: var(--dim);
  margin-top: 2px;
}

/* PR-HOME-REDESIGN (2026-06-13): EXCEPTION TICKER — one hairline row above the
   KPI strip. Calm/dim by default ("All clear"); warning-colored + clickable
   when something needs attention. Mono, matching the forge-pill color budget
   (late = --no/rose, rush + QC = --warn/amber). NO decorative glow. */
.hub-exceptions {
  margin: var(--sp-3) var(--sp-6) 0;
  padding: var(--sp-2) var(--sp-4);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
  font-family: var(--ff-mono, ui-monospace, monospace);
  font-size: 12px;
  letter-spacing: 0.04em;
  border: 1px solid var(--bdr);
  border-radius: var(--radius-sm);
  background: var(--sf);
  min-height: 34px;
}
.hub-exceptions[data-alert] {
  border-color: color-mix(in srgb, var(--no) 45%, var(--bdr));
  background: color-mix(in srgb, var(--no) 7%, var(--sf));
}
.hub-exc-key {
  text-transform: uppercase;
  font-weight: var(--fw-semibold, 600);
  color: var(--dim);
  margin-right: var(--sp-1);
}
.hub-exc-clear { color: var(--dim); }
.hub-exc-sep { color: var(--bdr-h, var(--bdr)); }
.hub-exc-seg {
  background: none;
  border: none;
  font: inherit;
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 4px;
  font-weight: var(--fw-semibold, 600);
  letter-spacing: 0.06em;
}
.hub-exc-seg:hover { background: var(--pn-h); }
.hub-exc-seg:focus-visible { background: var(--pn-h); outline: 2px solid var(--acc); outline-offset: 1px; }
.hub-exc-seg.is-late { color: var(--no); }
.hub-exc-seg.is-rush { color: var(--warn); }
.hub-exc-seg.is-qc   { color: var(--warn); }
/* Transient focus when the ticker scrolls the rail into view — an outline
   pulse (no layout shift, not a decorative glow). */
.forge-flash { animation: forge-flash-kf 1.2s var(--ease-out, ease); }
@keyframes forge-flash-kf {
  0%, 25% { outline: 2px solid var(--acc); outline-offset: 3px; }
  100%    { outline: 2px solid transparent; outline-offset: 3px; }
}

/* ------------------------------------------------------------------
   KPI Strip — sticky below topbar (spec §A, blueprint §4 Template A)
   ------------------------------------------------------------------ */
.kpi-strip {
  /* 2026-04-23 hub revamp: KPI strip is NOT sticky anymore — the user
     reported it as "the moving header thing" because it followed the
     scroll and obscured content. Now it sits inline at the top of the
     content area and scrolls with the page like everything else.
     Layout: grid of 5 cards (responsive); no border-bottom (the cards
     have their own borders). */
  position: static;
  background: transparent;
  border-bottom: none;
  padding: var(--sp-5) var(--sp-6) 0;
  display: grid;
  grid-template-columns: repeat(6, 1fr); /* PR-FORGE-HUB-HEALTHKPI: +On-time tile */
  gap: var(--sp-3);
  min-height: 0;
  align-items: stretch;
}

.kpi-tile {
  /* Each KPI is now a real card with its own border, hover lift, and
     press feedback. Removed the leading vertical-rule divider trick
     (which only worked when the strip was a single flex row). */
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: var(--sp-1);
  min-width: 0;
  min-height: 80px;
  padding: var(--sp-3) var(--sp-4);
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: var(--radius-card, 12px);
  position: relative;
  overflow: hidden;
  transition: transform var(--dur-chrome) var(--ease-out),
              border-color var(--dur-chrome) var(--ease-out),
              box-shadow var(--dur-chrome) var(--ease-out);
}
/* PR-HOME-REDESIGN (2026-06-13): the .kpi-tile::before 2px accent strip
   (6 little blue bars under the KPI strip) was part of the blue-chrome the
   operator flagged — deleted. The flat hairline border carries the tile. */
.kpi-tile:hover {
  /* PR-UI-FORGE (2026-06-13): flat pro-tool hover — a border + bg tint,
     NOT a lift. Killing translateY/box-shadow on data surfaces is the
     single biggest "pro software, not a website" signal. */
  border-color: var(--bdr-h);
  background: var(--pn-h);
}
.kpi-tile:first-child {
  /* Override the legacy "no border-left" rule from the old flex layout */
  border-left: 1px solid var(--bdr);
  padding-left: var(--sp-4);
}

.kpi-tile-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: var(--radius-sm, 6px);
  background: color-mix(in srgb, var(--acc) 12%, var(--inp));
  color: var(--acc);
  margin-bottom: var(--sp-2);
  flex-shrink: 0;
}
.kpi-tile-placeholder .kpi-tile-icon {
  background: color-mix(in srgb, var(--dim) 18%, var(--inp));
  color: var(--dim);
}

.kpi-tile-label {
  font-family: var(--ff-ui);
  font-weight: var(--fw-semibold);
  font-size: 11px;
  /* Lifted off --dim so the operator reads "OPEN JOBS / DUE ≤ 7 DAYS" at
     a glance against the dark hero panel — secondary text token was too
     low-contrast for shop-floor scan distance. Mix toward --txt keeps
     the visual hierarchy under the bigger value below. */
  color: color-mix(in srgb, var(--txt) 75%, var(--dim));
  line-height: 1.2;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin: 0;
  transition: none;
}

.kpi-tile-value {
  font-family: var(--ff-mono);
  font-weight: var(--fw-bold);
  font-size: 26px;
  color: var(--txt);
  line-height: 1;
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
  /* CRITICAL: no transition on numeric display — math-guardian gate 12 */
  transition: none;
}

/* Blocked tile placeholder — muted, en-dash placeholder */
.kpi-tile-placeholder .kpi-tile-value {
  color: var(--dim);
  font-weight: var(--fw-medium);
  font-size: 22px;
}
/* Removed the " *" suffix on labels — em-dash placeholder reads as
   "not available yet" without needing the asterisk + warn-orange star. */

/* Portrait tablet — wrap to 2-column grid */
@media (max-width: 767px) {
  .kpi-strip {
    display: grid;
    grid-template-columns: 1fr 1fr;
    padding: var(--sp-3);
  }
  .kpi-tile {
    /* Card-style on tablet too — not divider-style. */
    min-height: 64px;
    padding: var(--sp-3);
  }
}
@media (max-width: 600px) {
  .kpi-strip {
    grid-template-columns: 1fr 1fr;
    padding: var(--sp-3);
  }
}

/* ------------------------------------------------------------------
   Card component (blueprint §5 component 4)
   ------------------------------------------------------------------ */
.card {
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  display: flex;
  flex-direction: column;
  min-height: 44px;
  overflow: hidden;
  transition: border-color var(--dur-chrome) var(--ease-out),
              transform    var(--dur-chrome) var(--ease-out),
              box-shadow   var(--dur-chrome) var(--ease-out);
  cursor: pointer;
}

.card:hover,
.card:focus-visible {
  /* PR-UI-FORGE (2026-06-13): flat pro-tool hover — border + bg tint, no
     lift/shadow on data surfaces (shadows reserved for popovers/modals). */
  border-color: var(--bdr-h);
  background: var(--pn-h);
  outline: none;
}

.card-header {
  padding: var(--sp-4) var(--sp-6) var(--sp-3);
  border-bottom: 1px solid var(--bdr);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
}

.card-header h3 {
  font-family: var(--ff-ui);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-lg);
  line-height: 1.35;
  color: var(--txt);
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.card-body {
  padding: var(--sp-4) var(--sp-6);
  flex: 1;
  color: var(--dim);
  font-family: var(--ff-ui);
  font-size: var(--fs-base);
  line-height: 1.5;
}

.card-actions {
  padding: var(--sp-3) var(--sp-6);
  border-top: 1px solid var(--bdr);
  display: flex;
  gap: var(--sp-3);
  align-items: center;
}

.card-actions button {
  min-height: var(--sp-9);   /* 48px tap target */
  min-width: var(--sp-9);
  padding: 0 var(--sp-4);
  border-radius: var(--radius-sm);
  border: 1px solid var(--bdr);
  background: transparent;
  color: var(--txt);
  font-family: var(--ff-ui);
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
  cursor: pointer;
  transition: background-color var(--dur-chrome) var(--ease-out),
              border-color     var(--dur-chrome) var(--ease-out),
              color            var(--dur-chrome) var(--ease-out);
}

.card-actions button:hover,
.card-actions button:focus-visible {
  border-color: var(--acc);
  color: var(--acc);
  outline: none;
}

.card-actions button.card-action-primary {
  background: var(--acc);
  color: var(--acc-ink);
  border-color: transparent;
  font-weight: var(--fw-semibold);
}

.card-actions button.card-action-primary:hover {
  background: var(--acc2, var(--acc));
  color: var(--acc-ink);
}

/* Status pill on cards */
.card-status {
  font-family: var(--ff-ui);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  padding: var(--sp-1) var(--sp-3);
  border-radius: var(--radius-xs);
  white-space: nowrap;
  flex-shrink: 0;
  /* No transition — status is a data value, snaps per --dur-snap */
  transition: none;
}

/* PR-LIB-COLOR-TOKENS (2026-05-25): fallbacks now route through tokens.
   Background fallback uses color-mix to derive a 12%-alpha tint from the
   appropriate status token; ink fallback points at the corresponding
   ink/dim token. No hardcoded rgba/hex. */
.card-status-draft       { background: var(--status-draft,    color-mix(in srgb, var(--dim) 12%, transparent)); color: var(--status-draft-ink,    var(--dim)); }
.card-status-in-progress { background: var(--status-progress, color-mix(in srgb, var(--ok)  12%, transparent)); color: var(--status-progress-ink, var(--acc)); }
.card-status-complete    { background: var(--status-complete, var(--lime));                                     color: var(--status-complete-ink, var(--lime-ink)); }
.card-status-cancelled   { background: var(--status-alert,    color-mix(in srgb, var(--no)  12%, transparent)); color: var(--status-alert-ink,    var(--no)); }

/* ------------------------------------------------------------------
   Tile grid — auto-fill min 260px (blueprint §4 Template A)
   ------------------------------------------------------------------ */
.tile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--sp-6);
  padding: var(--sp-4) var(--sp-6);
}

/* ------------------------------------------------------------------
   Hub section heading row
   ------------------------------------------------------------------ */
.hub-section-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-6) var(--sp-6) var(--sp-3);
}

.hub-section-heading h2 {
  font-family: var(--ff-ui);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-xl);
  line-height: 1.3;
  letter-spacing: -0.005em;
  color: var(--txt);
  margin: 0;
}

/* ------------------------------------------------------------------
   Activity ribbon (horizontal scroll of recent projects)
   ------------------------------------------------------------------ */
.activity-ribbon {
  padding: var(--sp-4) var(--sp-6) var(--sp-6);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  /* Mask left/right edges so content fades at boundary */
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--sp-4),
                       #000 calc(100% - var(--sp-4)), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 var(--sp-4),
                       #000 calc(100% - var(--sp-4)), transparent 100%);
}

.activity-ribbon-inner {
  display: flex;
  gap: var(--sp-4);
  width: max-content;
  padding: var(--sp-1) var(--sp-4);  /* side padding inside the mask */
}

.activity-card {
  width: 220px;
  flex-shrink: 0;
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: var(--radius-md);
  padding: var(--sp-4);
  cursor: pointer;
  transition: border-color var(--dur-chrome) var(--ease-out),
              transform    var(--dur-chrome) var(--ease-out);
}

.activity-card:hover,
.activity-card:focus-visible {
  border-color: var(--acc);
  transform: translateY(-2px);
  outline: none;
}

.activity-card-name {
  font-family: var(--ff-ui);
  font-weight: var(--fw-medium);
  font-size: var(--fs-base);
  color: var(--txt);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  margin-bottom: var(--sp-1);
}

.activity-card-meta {
  font-family: var(--ff-ui);
  font-size: var(--fs-sm);
  color: var(--dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.activity-card-when {
  font-family: var(--ff-mono);
  font-size: var(--fs-sm);
  color: var(--dim);
  margin-top: var(--sp-2);
  /* Timestamp is display text — no transition */
  transition: none;
}

/* ------------------------------------------------------------------
   Empty / Loading / Error state blocks (blueprint §5 component 11)
   ------------------------------------------------------------------ */
.empty-state,
.loading-state,
.error-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--sp-8) var(--sp-6);
  gap: var(--sp-4);
  min-height: 200px;
}

.empty-state .state-icon,
.loading-state .state-icon,
.error-state .state-icon {
  width: 48px;
  height: 48px;
  color: var(--dim);
  flex-shrink: 0;
}

.empty-state h3,
.loading-state h3,
.error-state h3 {
  font-family: var(--ff-ui);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-lg);
  color: var(--txt);
  margin: 0;
  line-height: 1.35;
}

.empty-state p,
.loading-state p,
.error-state p {
  font-family: var(--ff-ui);
  font-size: var(--fs-base);
  color: var(--dim);
  margin: 0;
  max-width: 320px;
  line-height: 1.5;
}

.empty-state button,
.error-state button {
  min-height: var(--sp-9);   /* 48px tap target */
  padding: 0 var(--sp-5);
  background: var(--acc);
  color: var(--acc-ink);
  border: none;
  border-radius: var(--radius-sm);
  font-family: var(--ff-ui);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-base);
  cursor: pointer;
  transition: background-color var(--dur-chrome) var(--ease-out),
              transform        var(--dur-chrome) var(--ease-out);
}

.empty-state button:hover,
.error-state button:hover,
.empty-state button:focus-visible,
.error-state button:focus-visible {
  background: var(--acc2, var(--acc));
  transform: translateY(-1px);
  outline: none;
}

/* Skeleton shimmer for loading state */
.skeleton-row {
  height: 20px;
  background: linear-gradient(90deg,
    var(--pn) 0%, var(--pn-h, var(--inp)) 50%, var(--pn) 100%);
  background-size: 200% 100%;
  border-radius: var(--radius-xs);
  width: 100%;
  animation: skeleton-shimmer 1.4s ease-in-out infinite;
}

@keyframes skeleton-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

@media (prefers-reduced-motion: reduce) {
  .skeleton-row {
    animation: none;
    background: var(--pn);
  }
}

/* ------------------------------------------------------------------
   Hub project card specifics
   ------------------------------------------------------------------ */
.hub-project-card {
  /* Inherits .card — these are overrides for project-specific layout */
  min-height: 120px;
}

.hub-project-card .card-header h3 {
  font-size: var(--fs-base);   /* slightly smaller for project names */
}

.hub-project-card .card-body .proj-client {
  font-size: var(--fs-sm);
  color: var(--dim);
  margin-top: var(--sp-1);
}

/* ------------------------------------------------------------------
   Hub page wrapper — padding for content within #app-workspace
   ------------------------------------------------------------------ */
#hub-template-body {
  padding-bottom: var(--sp-7);  /* 32px bottom breathing room */
}

/* Legacy hidden stat anchors — must stay in DOM for JS wiring */
#hub-stat-anchors {
  display: none;
  position: absolute;
  pointer-events: none;
}

/* ------------------------------------------------------------------
   Blocked KPI note footer
   ------------------------------------------------------------------ */
.kpi-strip-note {
  width: 100%;
  padding: var(--sp-1) 0 0;
  font-family: var(--ff-ui);
  font-size: var(--fs-sm);
  color: var(--dim);
  letter-spacing: 0.02em;
}

/* ======================================================================
   END TEMPLATE A COMPONENTS
   ====================================================================== */


/* ======================================================================
   SHOP STANDARDS — #h-std tab content (Library group)
   ======================================================================
   Moved out of the Settings drawer (too dense; felt hidden away) onto
   its own Hub tab alongside the libraries. Organised into 7 visually-
   distinct sections — Dimensions / Carcass / Doors / Toe Kick / Hinge
   Bore / Drawer Slides / Sheet Stock — each in its own Card with an
   icon, title, subtitle, and a grid of token-styled numeric inputs.
   Save bar stays in the header (not a floating sticky) so the user
   always sees the ✓ Saved status near the values they just typed. */

#h-std {
  padding-bottom: var(--sp-9);
}

/* Header row — title + subtitle left, save controls right */
.std-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-6);
  padding: var(--sp-6) 0 var(--sp-7) 0;
  border-bottom: 1px solid var(--bdr);
  margin-bottom: var(--sp-7);
  flex-wrap: wrap;
}

.std-header h2 {
  font-family: var(--ff-ui);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-2xl);     /* 24px */
  line-height: 1.25;
  letter-spacing: -0.01em;
  margin: 0 0 var(--sp-2) 0;
  color: var(--txt);
  border: none;
  padding: 0;
}

.std-subtitle {
  font-family: var(--ff-ui);
  font-size: var(--fs-base);
  line-height: 1.5;
  color: var(--dim);
  margin: 0;
  max-width: 60ch;              /* keeps the copy line reads breathable */
}

.std-header-copy {
  flex: 1;
  min-width: 260px;
}

.std-header-status {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  flex-shrink: 0;
  min-height: var(--sp-9);
}

.std-status-msg {
  font-family: var(--ff-ui);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  color: var(--ok);
  min-width: 60px;
  text-align: right;
}

.std-save-btn {
  min-width: var(--sp-12);      /* 96px — roomy save target */
  min-height: var(--sp-9);
  padding: 0 var(--sp-5);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-base);
}

/* Body — vertical stack of section cards with generous spacing */
.std-body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);             /* 24px between sections */
}

/* Section card — where each group of related fields lives */
.std-section {
  background: var(--sf);
  border: 1px solid var(--bdr);
  border-radius: var(--radius-md);
  padding: var(--sp-6);
  box-shadow: var(--shadow-card);
}

.std-section-header {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-bottom: var(--sp-5);
  padding-bottom: var(--sp-4);
  border-bottom: 1px solid var(--bdr);
}

.std-section-icon {
  flex-shrink: 0;
  width: var(--sp-8);           /* 40px */
  height: var(--sp-8);
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--acc) 12%, transparent);
  color: var(--acc);
}

.std-section-title-wrap {
  flex: 1;
  min-width: 0;
}

.std-section-title {
  font-family: var(--ff-ui);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-lg);      /* 17px */
  line-height: 1.35;
  color: var(--txt);
  margin: 0;
}

.std-section-subtitle {
  font-family: var(--ff-ui);
  font-size: var(--fs-sm);      /* 12px */
  line-height: 1.4;
  color: var(--dim);
  margin: var(--sp-1) 0 0 0;
}

/* Grid of input cards inside a section */
.std-field-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--sp-4) var(--sp-5);
}

/* Each field card — label stacked above a numeric input with unit badge */
.std-field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}

.std-field-label {
  font-family: var(--ff-ui);
  font-size: var(--fs-sm);
  font-weight: var(--fw-medium);
  line-height: 1.3;
  color: var(--txt);
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-2);
}

.std-field-unit {
  font-family: var(--ff-mono);
  font-size: var(--fs-xs, 11px);
  font-weight: var(--fw-regular);
  letter-spacing: 0.04em;
  color: var(--dim);
  text-transform: lowercase;
}

.std-field input[type="number"],
.std-field input[type="text"],
.std-field select {
  min-height: var(--sp-9);      /* 48px tap target */
  padding: 0 var(--sp-3);
  background: var(--bg);
  border: 1px solid var(--bdr);
  border-radius: var(--radius-sm);
  color: var(--txt);
  font-family: var(--ff-mono);
  font-size: var(--fs-lg);      /* 17px — numeric values read at arm's length */
  font-weight: var(--fw-medium);
  letter-spacing: 0.02em;
  /* No transition on value-bearing inputs — they snap per motion rule.
     Border/background transitions on focus are chrome (allowed). */
}

.std-field input[type="number"]:focus,
.std-field input[type="text"]:focus,
.std-field select:focus {
  outline: none;
  border-color: var(--acc);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--acc) 20%, transparent);
}

.std-field select {
  cursor: pointer;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none' stroke='%23707070' stroke-width='2'%3E%3Cpath d='M1 1l5 5 5-5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--sp-3) center;
  padding-right: var(--sp-7);
  appearance: none;
}

/* Error state reserved for future validation */
.std-field.std-field-error input {
  border-color: var(--no);
}

/* Portrait tablet — 2-column header stack */
@media (max-width: 767px) {
  .std-header {
    flex-direction: column;
    gap: var(--sp-4);
  }
  .std-header-status {
    width: 100%;
    justify-content: flex-end;
  }
  .std-section {
    padding: var(--sp-5) var(--sp-4);
  }
  .std-field-grid {
    gap: var(--sp-3) var(--sp-4);
  }
}

/* ============================================================ *
 *  HUB — "Command Center" (2026-04-23 revamp)                  *
 * ------------------------------------------------------------ *
 *  Replaces the prior bento-widget grid + animated dash-bar    *
 *  with a stable two-column layout: left = studios + jobs      *
 *  table, right = always-visible settings rail. Patterns       *
 *  drawn from ServiceTitan, GitHub My Work, Linear home,       *
 *  Tulip shop-floor dashboards, and IBM Carbon dashboard       *
 *  guidelines. See agent design report for citations.          *
 * ============================================================ */

.hub-studio-shortcut {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4);
  background: var(--inp);
  border: 1px solid var(--bdr);
  border-radius: var(--radius-pill, 999px);
  font-family: var(--ff-ui);
  font-size: 13px;
  font-weight: var(--fw-semibold);
  color: var(--acc);
  min-height: 48px;
  cursor: pointer;
  text-decoration: none;
  transition: border-color var(--dur-chrome) var(--ease-out),
              background var(--dur-chrome) var(--ease-out),
              transform var(--dur-chrome) var(--ease-out);
}
.hub-studio-shortcut:hover {
  border-color: var(--acc);
  background: color-mix(in srgb, var(--acc) 8%, var(--inp));
  transform: translateY(-1px);
}
.hub-studio-shortcut:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 2px;
}
.hub-studio-shortcut svg { width: 16px; height: 16px; flex-shrink: 0; }

.hub-content-area {
  display: flex;
  gap: var(--sp-5);
  padding: var(--sp-5) var(--sp-6);
  align-items: flex-start;
}
.hub-left-col {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}
.hub-right-rail {
  width: 300px;
  flex: 0 0 300px;
  position: sticky;
  top: calc(56px + 56px + var(--sp-3));
  padding: var(--sp-4);
  background: var(--sf);
  border: 1px solid var(--bdr);
  border-radius: var(--radius-card, 12px);
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  max-height: calc(100vh - 56px - 56px - var(--sp-6));
  /* PR-MDEV-1: prefer svh on mobile so this doesn't reflow when the iOS
     Safari URL bar slides. Modern browsers use the second; pre-2022
     browsers fall through to the 100vh line above. */
  max-height: calc(100svh - 56px - 56px - var(--sp-6));
  overflow-y: auto;
}

.hub-studio-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--sp-4);
}

.hub-jobs-card {
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: var(--radius-card, 12px);
  overflow: hidden;
}
.hub-jobs-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--bdr);
}
.hub-jobs-header h2 {
  font-family: var(--ff-ui);
  font-weight: var(--fw-semibold);
  font-size: 16px;
  color: var(--white);
  margin: 0;
}
.hub-jobs-header .hub-jobs-count {
  font-family: var(--ff-ui);
  font-size: 12px;
  color: var(--dim);
  margin-left: var(--sp-2);
}
.hub-jobs-filter {
  display: inline-flex;
  gap: 1px;
  background: var(--bdr);
  border-radius: var(--radius-sm, 6px);
  padding: 1px;
  overflow: hidden;
}
.hub-jobs-filter button {
  background: var(--inp);
  border: none;
  color: var(--dim);
  font-family: var(--ff-ui);
  font-size: 12px;
  font-weight: var(--fw-semibold);
  padding: 6px 12px;
  cursor: pointer;
  transition: background var(--dur-chrome) var(--ease-out),
              color var(--dur-chrome) var(--ease-out);
  min-height: 32px;
}
.hub-jobs-filter button:hover { color: var(--txt); background: var(--pn-h); }
.hub-jobs-filter button[data-active="true"] {
  background: var(--acc);
  color: var(--acc-ink, var(--bg));
}

.hub-jobs-table {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--ff-ui);
}
.hub-jobs-table th {
  text-align: left;
  font-size: 11px;
  font-weight: var(--fw-semibold);
  color: var(--dim);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: var(--sp-3) var(--sp-4);
  background: var(--sf);
  border-bottom: 1px solid var(--bdr);
}
.hub-jobs-table td {
  /* PR-HOME-REDESIGN BF-3 (2026-06-13): tighter rows (52px → 38px) so the
     grid reads as a dense glance, not a wall. */
  padding: var(--sp-2) var(--sp-4);
  height: 38px;
  border-bottom: 1px solid color-mix(in srgb, var(--bdr) 50%, transparent);
  color: var(--txt);
  font-size: 14px;
  vertical-align: middle;
}
/* "Show all (N)" / "Show fewer" toggle row — quiet, full-width, accent on hover. */
.hub-jobs-table .hub-jobs-showall-row td {
  text-align: center;
  color: var(--dim);
  font-size: 12px;
  letter-spacing: 0.04em;
  height: 34px;
  cursor: pointer;
}
.hub-jobs-table .hub-jobs-showall-row:hover td { color: var(--acc); background: var(--pn-h); }
.hub-jobs-table tbody tr {
  cursor: pointer;
  transition: background var(--dur-chrome) var(--ease-out);
}
.hub-jobs-table tbody tr:hover { background: var(--pn-h); }
.hub-jobs-table tbody tr:last-child td { border-bottom: none; }
.hub-jobs-table .hub-job-name { font-weight: var(--fw-semibold); color: var(--txt); }
.hub-jobs-table .hub-job-customer { color: var(--dim); }
.hub-jobs-table .hub-job-due { font-variant-numeric: tabular-nums; color: var(--dim); }
.hub-jobs-table .hub-job-due.due-soon { color: var(--warn); font-weight: var(--fw-semibold); }
.hub-jobs-table .hub-job-due.due-now  { color: var(--no);   font-weight: var(--fw-semibold); }
.hub-jobs-table .hub-job-status-pill {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 10px;
  border-radius: var(--radius-pill, 999px);
  font-size: 11px;
  font-weight: var(--fw-semibold);
  background: color-mix(in srgb, var(--acc) 12%, var(--inp));
  color: var(--acc);
  white-space: nowrap;
}
.hub-jobs-table .hub-job-status-pill[data-status="complete"]  { background: color-mix(in srgb, var(--ok)   14%, var(--inp)); color: var(--ok); }
.hub-jobs-table .hub-job-status-pill[data-status="cancelled"] { background: color-mix(in srgb, var(--no)   14%, var(--inp)); color: var(--no); }
.hub-jobs-table .hub-job-status-pill[data-status="draft"]     { background: color-mix(in srgb, var(--dim) 22%, var(--inp)); color: var(--dim); }

.hub-jobs-table .hub-job-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  color: var(--dim);
  border-radius: var(--radius-sm, 6px);
  transition: background var(--dur-chrome) var(--ease-out),
              color var(--dur-chrome) var(--ease-out);
}
.hub-jobs-table tbody tr:hover .hub-job-arrow {
  color: var(--acc);
  background: color-mix(in srgb, var(--acc) 10%, transparent);
}

.hub-jobs-empty,
.hub-jobs-error {
  padding: var(--sp-6);
  text-align: center;
  color: var(--dim);
  font-size: 13px;
}
.hub-jobs-empty button,
.hub-jobs-error button {
  margin-top: var(--sp-3);
  min-height: 44px;
  padding: 0 var(--sp-4);
  background: var(--acc);
  color: var(--acc-ink, var(--bg));
  border: none;
  border-radius: var(--radius-sm, 6px);
  font-weight: var(--fw-semibold);
  cursor: pointer;
}

.hub-jobs-add-row td {
  padding: var(--sp-3) var(--sp-4);
  text-align: center;
  font-size: 13px;
  color: var(--acc);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  background: color-mix(in srgb, var(--acc) 4%, transparent);
}
.hub-jobs-add-row:hover td { background: color-mix(in srgb, var(--acc) 10%, transparent); }

.hub-rail-section {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.hub-rail-section + .hub-rail-section {
  padding-top: var(--sp-3);
  border-top: 1px solid color-mix(in srgb, var(--bdr) 50%, transparent);
}
.hub-rail-label {
  font-family: var(--ff-ui);
  font-size: 10px;
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--dim);
}
.hub-rail-themes {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-2);
}
.hub-rail-theme {
  display: block;
  width: 100%;
  height: 36px;
  border-radius: var(--radius-sm, 6px);
  border: 1.5px solid var(--bdr);
  cursor: pointer;
  position: relative;
  overflow: hidden;
  background: var(--inp);
  transition: transform var(--dur-chrome) var(--ease-out),
              border-color var(--dur-chrome) var(--ease-out);
  padding: 0;
}
.hub-rail-theme:hover { transform: translateY(-1px); border-color: var(--acc); }
.hub-rail-theme[data-active="true"] {
  border-color: var(--acc);
  box-shadow: 0 0 0 1px var(--acc) inset;
}
.hub-rail-theme-label {
  position: absolute;
  bottom: 2px;
  left: 4px;
  font-size: 9px;
  font-weight: var(--fw-semibold);
  color: color-mix(in srgb, var(--txt) 80%, transparent);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  pointer-events: none;
}

.hub-rail-units {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1px;
  background: var(--bdr);
  border-radius: var(--radius-sm, 6px);
  overflow: hidden;
}
.hub-rail-units button {
  background: var(--inp);
  border: none;
  color: var(--dim);
  font-family: var(--ff-ui);
  font-size: 12px;
  font-weight: var(--fw-semibold);
  padding: 10px 0;
  cursor: pointer;
  min-height: 40px;
  transition: background var(--dur-chrome) var(--ease-out),
              color var(--dur-chrome) var(--ease-out);
}
.hub-rail-units button:hover { color: var(--txt); }
.hub-rail-units button[data-active="true"] {
  background: var(--acc);
  color: var(--acc-ink, var(--bg));
}

.hub-rail-link {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  background: var(--inp);
  border: 1px solid var(--bdr);
  border-radius: var(--radius-sm, 6px);
  color: var(--txt);
  font-family: var(--ff-ui);
  font-size: 13px;
  font-weight: var(--fw-medium);
  cursor: pointer;
  text-decoration: none;
  min-height: 40px;
  transition: border-color var(--dur-chrome) var(--ease-out),
              background var(--dur-chrome) var(--ease-out);
}
.hub-rail-link:hover {
  border-color: var(--acc);
  background: color-mix(in srgb, var(--acc) 6%, var(--inp));
}
.hub-rail-link svg { width: 14px; height: 14px; color: var(--acc); flex-shrink: 0; }
.hub-rail-link-aside { margin-left: auto; font-size: 11px; color: var(--dim); }

.hub-rail-status {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: 11px;
  color: var(--dim);
  font-family: var(--ff-mono, ui-monospace);
}
.hub-rail-status .hub-rail-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ok);
  flex-shrink: 0;
}
.hub-rail-status[data-state="offline"] .hub-rail-dot { background: var(--no); }

@media (max-width: 1100px) {
  .hub-content-area { flex-direction: column; }
  .hub-right-rail {
    width: 100%;
    flex: 1 1 auto;
    position: static;
    max-height: none;
    top: auto;
  }
  .hub-studio-row { grid-template-columns: 1fr; }
}
@media (max-width: 600px) {
  .hub-content-area { padding: var(--sp-3); gap: var(--sp-4); }
  .hub-jobs-table th:nth-child(2),
  .hub-jobs-table td:nth-child(2) { display: none; }
}

/* ============================================================ *
 *  PRESS-INTO-IT ANIMATION SYSTEM (2026-04-23)                 *
 * ------------------------------------------------------------ *
 *  User: "add some nice animations so when you click a button   *
 *  it feels like it pulls you into it."                         *
 *                                                                *
 *  Pattern: hover lift (translateY-2 + soft shadow) -> active   *
 *  push (translateY+1, scale 0.97, inset shadow, accent halo).  *
 *  Combined feel = depth perception. Each interactive surface  *
 *  on the hub gets the .press class behavior.                   *
 *                                                                *
 *  Tokens-only (color-mix on var(--acc) for the halo).          *
 *  Respects prefers-reduced-motion: no transforms, only         *
 *  background color change for accessibility.                   *
 * ============================================================ */

.press,
.hub-studio-shortcut,
.hub-rail-link,
.hub-rail-theme,
.hub-rail-units button,
.hub-jobs-filter button,
.hub-page-header .hub-primary-action,
#topbar-home-btn,
.hero-card,
.kpi-tile {
  /* Smooth out: translate + scale + shadow on hover/active */
  transition:
    transform 180ms cubic-bezier(0.4, 0, 0.2, 1),
    box-shadow 180ms cubic-bezier(0.4, 0, 0.2, 1),
    border-color 180ms ease,
    background 180ms ease,
    color 180ms ease,
    opacity 180ms ease,
    filter 180ms ease;
  will-change: transform;
}

/* Active = "pulls you into it" — translate down, slight scale,
   inset shadow + accent halo. Bezier is snappy on press, not bouncy. */
.press:active,
.hub-studio-shortcut:active,
.hub-rail-link:active,
.hub-rail-theme:active,
.hub-rail-units button:active,
.hub-jobs-filter button:active,
.hub-page-header .hub-primary-action:active,
#topbar-home-btn:active,
.hero-card:active,
.kpi-tile:active {
  transform: translateY(2px) scale(0.97);
  /* PR-LIB-COLOR-TOKENS: composite press shadow lives in tokens.css. */
  box-shadow: var(--shadow-press-acc);
  transition-duration: 90ms;
}

/* Jobs table row press — same feel, scoped to <tr> selector */
.hub-jobs-table tbody tr {
  transition:
    transform 180ms cubic-bezier(0.4, 0, 0.2, 1),
    box-shadow 180ms cubic-bezier(0.4, 0, 0.2, 1),
    background 180ms ease;
}
.hub-jobs-table tbody tr:active {
  transform: scale(0.995);
  background: color-mix(in srgb, var(--acc) 14%, var(--pn));
  box-shadow: inset 3px 0 0 var(--acc);
  transition-duration: 90ms;
}

/* Reduced-motion fallback — keep color change but kill transforms */
@media (prefers-reduced-motion: reduce) {
  .press, .hub-studio-shortcut, .hub-rail-link, .hub-rail-theme,
  .hub-rail-units button, .hub-jobs-filter button,
  .hub-page-header .hub-primary-action, #topbar-home-btn,
  .hero-card, .kpi-tile, .hub-jobs-table tbody tr {
    transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
  }
  .press:active, .hub-studio-shortcut:active, .hub-rail-link:active,
  .hub-rail-theme:active, .hub-rail-units button:active,
  .hub-jobs-filter button:active, .hub-page-header .hub-primary-action:active,
  #topbar-home-btn:active, .hero-card:active, .kpi-tile:active,
  .hub-jobs-table tbody tr:active {
    transform: none !important;
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--acc) 30%, transparent);
  }
}

/* ============================================================ *
 *  HUB ENTRANCE ANIMATIONS — subtle stagger fade-up              *
 * ------------------------------------------------------------ *
 *  When the hub mounts, each major section eases in from below   *
 *  with a tiny 60ms stagger. Cinematic without being precious.   *
 *  Total reveal duration ~400ms. Reduced-motion users get an     *
 *  instant render with no transforms.                            *
 * ============================================================ */

@keyframes hubEnterUp {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: translateY(0); }
}

#hub-template-body .hub-page-header,
#hub-template-body .kpi-strip,
#hub-template-body .hub-content-area {
  animation: hubEnterUp 360ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
#hub-template-body .hub-page-header { animation-delay: 0ms;   }
#hub-template-body .kpi-strip       { animation-delay: 60ms;  }
#hub-template-body .hub-content-area { animation-delay: 120ms; }

@media (prefers-reduced-motion: reduce) {
  #hub-template-body .hub-page-header,
  #hub-template-body .kpi-strip,
  #hub-template-body .hub-content-area {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

/* ============================================================ *
 *  HERO-CARD POLISH — gradient overlay, smoother shadow         *
 * ------------------------------------------------------------ *
 *  The studio tiles got a subtle theme-accent gradient over the *
 *  background image so the title pill + quick-action bar read   *
 *  consistently across both image and theme variations.         *
 * ============================================================ */

.hero-card {
  border: 1px solid var(--bdr);
  /* PR-LIB-COLOR-TOKENS: composite shadow lives in tokens.css. */
  box-shadow: var(--shadow-hero-rest);
}
.hero-card::after {
  /* Bottom-fade gradient that ramps from accent-tinted dark at the
     bottom edge up to transparent. Makes the studio-pill + quick-
     action bar legible regardless of background image. */
  content: "";
  position: absolute;
  inset: 0;
  /* PR-LIB-COLOR-TOKENS: bottom-fade uses the --scrim-strong token tinted
     by 8% accent so the fade follows the active theme. */
  background: linear-gradient(
    to bottom,
    transparent 40%,
    color-mix(in srgb, var(--acc) 8%, var(--scrim-strong)) 100%
  );
  pointer-events: none;
  border-radius: inherit;
  transition: opacity 200ms ease;
  opacity: 0.85;
}
.hero-card:hover {
  border-color: var(--acc);
  transform: translateY(-3px);
  box-shadow: var(--shadow-hero-hover);
}
.hero-card:hover::after { opacity: 1; }
/* Make the studio-pill + hero-quick sit ABOVE the gradient overlay */
.hero-card .studio-pill,
.hero-card .hero-quick,
.hero-card .hero-badge { position: relative; z-index: 2; }

/* ----------------------------------------------------------------------
   Admin-only marker (2026-04-24 Phase 2 of Hub admin-gating)

   Tiny corner pill that an admin sees on tiles/widgets/nav-items that
   are admin-only. Non-admins never render the host element so they
   never see the badge. Sits in the top-RIGHT corner so it doesn't
   collide with the existing top-LEFT `.hero-badge` ("3D · NESTING")
   on hero-cards. `pointer-events:none` so it never steals clicks
   from the parent tile.

   Color: solid accent fill with --bg text — the project's whitelisted
   CTA-on-accent pattern. Box-shadow uses color-mix on the accent so
   it tints in any theme without hardcoded rgba.
   ---------------------------------------------------------------------- */
.admin-only-badge {
    position: absolute;
    top: 14px;
    right: 14px;
    z-index: 3;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 5px 12px;
    border-radius: 6px;
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.6px;
    text-transform: uppercase;
    /* --acc-ink is per-theme tuned for legibility on var(--acc) (AAA on
       Onyx + dark themes, ~5:1 on light themes — matches every other
       CTA-on-accent in the app). Don't swap to --bg, which can collide
       with light themes' near-white background. */
    color: var(--acc-ink);
    background: var(--acc);
    box-shadow: 0 2px 10px color-mix(in srgb, var(--acc) 30%, transparent);
    pointer-events: none;
    user-select: none;
}
.admin-only-badge::before {
    content: '';
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--acc-ink);
    opacity: 0.65;
}

/* ----------------------------------------------------------------------
   Admin-only visibility (2026-04-24 Phase 3 of Hub admin-gating)

   Any element marked `class="admin-only"` is hidden whenever the
   current user's role isn't "admin". Role is mirrored onto <body>
   as `data-role="<role>"` by `_syncBodyRole()` in index.html, called
   from setAuth(), revalidateAuth(), and once at module-load.

   This is the SECOND line of defense — JS already filters Hub widgets
   and `go()` silently bounces non-admins from gated routes. CSS is
   the cheap belt-and-suspenders that catches anything that slipped
   through (e.g. static markup that future-us forgets to filter in JS).
   ---------------------------------------------------------------------- */
body:not([data-role="admin"]) .admin-only { display: none !important; }

/* ----------------------------------------------------------------------
   Hub studio row layout when Cabinet is hidden (2026-04-24, Phase 3)

   The default `.hub-studio-row` is `grid-template-columns: 1fr 1fr`
   so Cabinet + Door sit side-by-side. When the Cabinet hero-card is
   hidden by the admin-only rule above, the row would otherwise leave
   an empty slot on the left. Switch to a single column so the surviving
   Door tile fills the row.

   This is intentionally tied to `body[data-role]` rather than `:has()`
   so it works on every browser and is trivial to extend per-role later
   when per-user dashboard customization lands.
   ---------------------------------------------------------------------- */
body:not([data-role="admin"]) .hub-studio-row { grid-template-columns: 1fr; }
/* When the row collapses to a single hero-card, the surviving card needs
   more height to read as an anchor tile rather than a squat banner — its
   default 160px min-height was tuned for the 2-up layout. */
body:not([data-role="admin"]) .hub-studio-row .hero-card { min-height: 200px; }

/* ============================================================ *
 *  DEPTH-MORPH PAGE TRANSITIONS (2026-04-23, iOS-grade upgrade) *
 * ------------------------------------------------------------ *
 *  Multi-layer page navigation. Source element morphs to       *
 *  fullscreen via TRANSFORM (compositor-only, no layout         *
 *  thrash). Five variants per the design spec:                  *
 *    hero-card  — 380ms bouncy spring + scrim with backdrop blur *
 *    cta        — 320ms accent flood, no blur                   *
 *    pill       — 260ms crossfade + directional slide           *
 *    row        — 300ms slide-right + recede left list          *
 *    icon-btn   — 240ms zoom out current + zoom in destination  *
 *                                                                *
 *  Sources: Apple HIG Motion, Material 3 motion tokens, iOS     *
 *  App Store card expand, SwiftUI matchedGeometryEffect.        *
 * ============================================================ */

/* ── Outgoing page exit animations (per variant) ───────────── *
 *  2026-04-23-late retune: all forward-nav exits dropped their
 *  backdrop-filter blur (blur reads as "loading"), lightened the
 *  scale/opacity range (was 0.96/0.65 — felt heavy), matched
 *  durations to the variant specs, and swapped in Apple's smooth
 *  curve cubic-bezier(0.32, 0.72, 0.24, 1) everywhere for
 *  perceptual consistency with the back-home icon-btn variant
 *  the user called out as the target feel.                      */
.pg.is-exiting-recede {
  /* hero-card / cta exit: subtle scale + opacity drop, NO blur */
  animation: pgExitRecede 240ms cubic-bezier(0.32, 0.72, 0.24, 1) both;
  will-change: transform, opacity;
  pointer-events: none;
}
.pg.is-exiting-slide {
  /* row exit: list slides slightly left + mild dim */
  animation: pgExitSlide 220ms cubic-bezier(0.32, 0.72, 0.24, 1) both;
  will-change: transform, opacity;
  pointer-events: none;
}
.pg.is-exiting-zoom {
  /* icon-btn back nav: page zooms out toward camera + fades */
  animation: pgExitZoom 200ms cubic-bezier(0.4, 0, 1, 1) both;
  will-change: transform, opacity;
  pointer-events: none;
}

@keyframes pgExitRecede {
  from { transform: scale(1);    opacity: 1; }
  to   { transform: scale(0.98); opacity: 0.92; }
}
@keyframes pgExitSlide {
  from { transform: translateX(0);     opacity: 1; }
  to   { transform: translateX(-16px); opacity: 0.88; }
}
@keyframes pgExitZoom {
  from { transform: scale(1);    opacity: 1; }
  to   { transform: scale(1.04); opacity: 0; }
}

/* ── Backdrop scrim (hero-card + cta only) ─────────────────── *
 *  2026-04-23-late retune: removed backdrop-filter: blur()
 *  entirely. Blur was the single biggest contributor to the
 *  "feels like loading" perception. Kept the darkening layer
 *  but dropped opacity from 0.42 → 0.22 (subtle dim, not a
 *  "screen is gone" feel).                                     */
#page-transit-scrim {
  display: none;
  position: fixed;
  inset: 0;
  background: transparent;
  pointer-events: none;
  z-index: 9997;
  will-change: opacity;
}
#page-transit-scrim.is-active {
  display: block;
  animation: scrimIn 220ms cubic-bezier(0.32, 0.72, 0.24, 1) both;
}
#page-transit-scrim.is-fading-out {
  animation: scrimOut 180ms cubic-bezier(0.4, 0, 0.2, 1) both;
}
/* PR-LIB-COLOR-TOKENS: keyframes use color-mix(--scrim, transparent) so
   the page-transit dim follows the same scrim token as the sidebar
   backdrop — strict token-only. */
@keyframes scrimIn {
  from { background: transparent; }
  to   { background: color-mix(in srgb, var(--scrim) 55%, transparent); }
}
@keyframes scrimOut {
  from { background: color-mix(in srgb, var(--scrim) 55%, transparent); }
  to   { background: transparent; }
}

/* ── Morph surface ────────────────────────────────────────── */
/* TRANSFORM-based, not layout-based. Always inset:0 (fullscreen
   from the start); the WAAPI animation morphs the transform from
   a translate+scale that VISUALLY puts it at the source rect, out
   to identity. GPU-only, 60fps even on tablets. */
#page-transit-overlay {
  display: none;
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 9998;
  background: var(--acc);
  border-radius: 0;
  transform-origin: center center;
  will-change: transform, border-radius, opacity;
  /* No CSS transition here — WAAPI animate() handles all timing for
     spring control + fill: 'both' precision. */
}
#page-transit-overlay.is-morphing {
  display: block;
}
#page-transit-overlay.is-fading-out {
  animation: morphFadeOut 180ms cubic-bezier(0.4, 0, 0.2, 1) both;
}
@keyframes morphFadeOut {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* ── Destination page entrance (per variant) ───────────────── *
 *  2026-04-23-late retune: tightened scale ranges so the reveal
 *  is subtle — the eye should barely register the scale happens,
 *  it just feels like the page "catches" into focus. Zoom stays
 *  at 0.94 (icon-btn is the user's "perfect" reference point),
 *  everything else pulled in closer to 1.0.                     */
@keyframes pgRevealScale {
  /* hero-card + cta — page catches at near-identity */
  from { opacity: 0; transform: scale(0.995); }
  to   { opacity: 1; transform: scale(1); }
}
@keyframes pgRevealSlide {
  /* row — slides in from right, shorter distance */
  from { opacity: 0; transform: translateX(16px); }
  to   { opacity: 1; transform: translateX(0); }
}
@keyframes pgRevealZoom {
  /* icon-btn — user reference point, unchanged */
  from { opacity: 0; transform: scale(0.94); }
  to   { opacity: 1; transform: scale(1); }
}
@keyframes pgRevealFade {
  /* pill — near-instant crossfade, no directional hint */
  from { opacity: 0; }
  to   { opacity: 1; }
}

.pg.transit-reveal-scale { animation: pgRevealScale 180ms cubic-bezier(0.32, 0.72, 0.24, 1) both; }
.pg.transit-reveal-slide { animation: pgRevealSlide 200ms cubic-bezier(0.32, 0.72, 0.24, 1) both; }
.pg.transit-reveal-zoom  { animation: pgRevealZoom  240ms cubic-bezier(0.25, 1, 0.5, 1)   both; }
.pg.transit-reveal-fade  { animation: pgRevealFade  180ms cubic-bezier(0.32, 0.72, 0.24, 1) both; }

/* Reduced-motion fallback — strip every transform / blur, keep
   color-only feedback. The actual page-swap fires synchronously
   so the operator still gets instant nav. */
@media (prefers-reduced-motion: reduce) {
  .pg.is-exiting-recede,
  .pg.is-exiting-slide,
  .pg.is-exiting-zoom { animation: none !important; opacity: 1 !important; transform: none !important; filter: none !important; }
  #page-transit-scrim   { display: none !important; }
  #page-transit-overlay { display: none !important; }
  .pg.transit-reveal-scale,
  .pg.transit-reveal-slide,
  .pg.transit-reveal-zoom,
  .pg.transit-reveal-fade { animation: none !important; opacity: 1 !important; transform: none !important; }
}


/* ======================================================================
   RESPONSIVE BREAKPOINTS — Global layout
   ======================================================================
   Target viewports:
     ≥1280px  — desktop / shop laptop        (full layout)
     1024-1279px — landscape tablet           (condensed hub two-col)
     768-1023px  — landscape tablet narrow    (single-col hub, compact topbar)
     <768px   — portrait tablet / phone       (bottom-nav, stacked layouts)
     <480px   — small phone (edge case)       (icon-only topbar)

   Priority: readability and tap-targets over density. Never hide
   essential data at any breakpoint — collapse it instead.
   ====================================================================== */

/* ── Topbar — side padding tightens on smaller screens ─────────── */
@media (max-width: 1023px) {
  #app-topbar {
    padding: 0 var(--sp-4);   /* 16px vs 24px desktop */
    gap: var(--sp-3);         /* 12px vs 16px desktop */
  }
}
@media (max-width: 767px) {
  #app-topbar {
    padding: 0 var(--sp-3);   /* 12px — tablet portrait */
    gap: var(--sp-2);         /* 8px */
  }
}

/* ── KPI strip — 5→3→2 column collapse ─────────────────────────── */
/* Already has 5-col default + 2-col portrait (line ~827 above).
   Add an intermediate 3-col tier for landscape tablet (1024px). */
@media (max-width: 1100px) and (min-width: 768px) {
  .kpi-strip {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* ── Hub content area — single-column on medium tablets ─────────── */
/* Already collapses at 1100px (line ~1788 above) — the right-rail
   stacks below on all viewports <1100px, which is correct. */

/* ── Hub studio row — always single-col on narrower screens ─────── */
@media (max-width: 900px) {
  .hub-studio-row { grid-template-columns: 1fr; }
}

/* ── Page content max-width — keep line lengths sane WITHOUT wasting an
   ultrawide monitor. PR-RESPONSIVE-STUDIO (2026-07-30): operator wants
   "everything equal from ultrawide monitors to small phones", so the old
   hard 1440px cap (which centered a narrow column on a 3440px display) is
   now fluid — up to 1760px, scaling with the viewport. Card/KPI grids fan
   out into extra columns below (that's where the real ultrawide win is). ── */
@media (min-width: 1600px) {
  .hub-content-area,
  .hub-page-header,
  .kpi-strip {
    max-width: min(1760px, 94vw);
    margin-left: auto;
    margin-right: auto;
  }
}
/* Ultrawide: let the dashboard card + KPI grids ADD columns instead of just
   stretching. auto-fill + minmax is a safe override — it only ever increases
   the column count on very wide viewports. */
@media (min-width: 1600px) {
  .hub-stats, .kpi-grid, .kpi-strip, .cf-grid-cards, .sg {
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  }
}
@media (min-width: 2400px) {
  .hub-content-area, .hub-page-header, .kpi-strip { max-width: min(2100px, 92vw); }
}

/* ── Hub page header — tighten padding on medium screens ───────── */
@media (max-width: 1023px) {
  .hub-page-header {
    padding: var(--sp-5) var(--sp-5) var(--sp-3);
  }
  .hub-content-area {
    padding: var(--sp-4) var(--sp-5);
  }
  .kpi-strip {
    padding: var(--sp-4) var(--sp-5) 0;
  }
}
@media (max-width: 767px) {
  .hub-page-header {
    padding: var(--sp-4) var(--sp-4) var(--sp-2);
  }
  .hub-content-area {
    padding: var(--sp-3) var(--sp-4);
    gap: var(--sp-4);
  }
}

/* ── Jobs table — hide low-priority columns on small screens ─────── */
/* col 2 = Customer already hidden at 600px (existing rule above).
   Also hide the Due date column below 480px to keep name + status readable. */
@media (max-width: 480px) {
  .hub-jobs-table th:nth-child(3),
  .hub-jobs-table td:nth-child(3) { display: none; }
}

/* ── Hub right rail — min-width guard on landscape tablet ────────── */
@media (max-width: 1100px) {
  .hub-right-rail {
    min-width: 0;
    width: 100%;
  }
}

/* ── Tile grid — fewer columns on medium screens ─────────────────── */
@media (max-width: 900px) {
  .tile-grid {
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    padding: var(--sp-3) var(--sp-4);
    gap: var(--sp-4);
  }
}
@media (max-width: 600px) {
  .tile-grid {
    grid-template-columns: 1fr 1fr;
    padding: var(--sp-3);
    gap: var(--sp-3);
  }
}

/* ── Standards tab (h-std) — single column on medium screens ─────── */
@media (max-width: 900px) {
  .std-field-grid {
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  }
}
@media (max-width: 600px) {
  .std-field-grid {
    grid-template-columns: 1fr 1fr;
  }
  #h-std { padding-bottom: var(--sp-10); }
}

/* ── Card body — tighter padding on small screens ─────────────────── */
@media (max-width: 767px) {
  .card-header { padding: var(--sp-3) var(--sp-4) var(--sp-2); }
  .card-body   { padding: var(--sp-3) var(--sp-4); }
  .card-actions { padding: var(--sp-2) var(--sp-4); }
}

/* ── Prevent horizontal scroll on workspace ─────────────────────── */
#app-workspace {
  /* overflow-x already hidden; add word-wrap safety */
  word-break: break-word;
  overflow-wrap: break-word;
}

/* ── Bottom nav — hide status bar overlap on portrait tablet ─────── */
/* When both status bar + bottom nav are visible the workspace
   bottom offset is already calculated in the existing rule above.
   This ensures the bottom nav doesn't sit ON the status bar. */
@media (max-width: 767px) {
  body[data-shell-status]:not([data-shell-status=""]) #app-bottom-nav,
  body[data-offline] #app-bottom-nav {
    bottom: 32px; /* sit above the 32px status bar */
  }
}

/* ── Library-editor auto-fill flash ───────────────────────────────
   Fires on Sheet Good thickness → Default Panel Detail auto-apply
   (web/door-studio.js ~autoApplyDefaultPanelDetailFromThickness).
   Tokens-only — uses the existing success color.

   Animation (audit fix 2026-04-23): outline transitions only animate
   when the property mutates between two non-`none` values. Toggling
   `outline` from implicit-none to `2px solid var(--ok)` doesn't fade.
   Pre-declare a transparent outline on the target select, then animate
   only outline-color when the class lands. Same 1.2s fade matches the
   JS setTimeout that removes the class.
   ─────────────────────────────────────────────────────────────────── */
#ed-mpdet {
  outline: 2px solid transparent;
  outline-offset: 2px;
  transition: outline-color 1.2s ease-out;
}
.field-autofilled {
  outline-color: var(--ok);
}

/* ── Version drift banner (Phase 2 — 2026-04-23) ────────────────────
   Anchored at the top of #app-workspace; appears only when WASM and
   server fingerprints disagree. Amber/warn token to read as
   "attention" without alarming the operator into thinking the system
   is broken. Per memory: NO backdrop blur (reads as loading). Per UX
   spec: non-blocking, dismissible, persistent text-only — does not
   block clicks beneath it because it's anchored at the top of the
   page and the operator scrolls past it. */
.version-banner {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4);
  margin: 0;
  background: color-mix(in srgb, var(--warn) 14%, var(--pn));
  border-bottom: 1px solid color-mix(in srgb, var(--warn) 50%, var(--bdr));
  color: var(--txt);
  font-family: var(--ff-ui);
  font-size: 13px;
  line-height: 1.35;
  /* No transition — appearance is rare and abrupt is correct (the
     operator should NOTICE this). */
}
.version-banner[hidden] { display: none; }

.version-banner-icon {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--warn) 60%, transparent);
  color: var(--bg);
  font-weight: var(--fw-bold);
  font-size: 14px;
}
.version-banner-msg { flex: 0 1 auto; }
.version-banner-msg strong {
  font-weight: var(--fw-semibold);
  color: var(--txt);
}
.version-banner-detail {
  flex: 1 1 auto;
  font-size: 11px;
  color: var(--dim);
  font-family: var(--ff-mono);
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.version-banner-close {
  flex-shrink: 0;
  background: transparent;
  border: 1px solid transparent;
  color: var(--dim);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  padding: 4px 10px;
  border-radius: var(--radius-sm, 6px);
  min-width: 44px;     /* tap target ≥ 44 (WCAG 2.5.5) */
  min-height: 44px;
  transition: background var(--dur-chrome) var(--ease-out),
              color var(--dur-chrome) var(--ease-out);
}
.version-banner-close:hover {
  background: color-mix(in srgb, var(--txt) 8%, transparent);
  color: var(--txt);
}
.version-banner-close:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 2px;
}

/* Narrow tablet portrait — stack icon + message + close, hide the
   tech-detail string. */
@media (max-width: 600px) {
  .version-banner { font-size: 12px; padding: var(--sp-2) var(--sp-3); gap: var(--sp-2); }
  .version-banner-detail { display: none; }
}

/* ── Order conflict banner (Phase 3 — 2026-04-23) ─────────────────────
   Sibling of .version-banner, but louder: red/no token (failure colour)
   instead of amber/warn (warning colour) so the operator distinguishes
   "your save was blocked" from "your preview math may be stale." No
   close button — operator MUST choose Reload or View Draft so no
   pending edits are silently lost. */
.conflict-banner {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-4);
  margin: 0;
  background: color-mix(in srgb, var(--no) 14%, var(--pn));
  border-bottom: 1px solid color-mix(in srgb, var(--no) 55%, var(--bdr));
  color: var(--txt);
  font-family: var(--ff-ui);
  font-size: 13px;
  line-height: 1.35;
}
.conflict-banner[hidden] { display: none; }

.conflict-banner-icon {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: color-mix(in srgb, var(--no) 70%, transparent);
  color: var(--bg);
  font-weight: var(--fw-bold);
  font-size: 14px;
}
.conflict-banner-msg { flex: 0 1 auto; }
.conflict-banner-msg strong {
  font-weight: var(--fw-semibold);
  color: var(--txt);
}
.conflict-banner-detail {
  flex: 1 1 auto;
  font-size: 11px;
  color: var(--dim);
  font-family: var(--ff-mono);
  text-align: right;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.conflict-banner-actions {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}
.conflict-banner-btn-primary,
.conflict-banner-btn-secondary {
  min-height: 44px;       /* WCAG 2.5.5 tap target */
  min-width: 44px;
  padding: 0 var(--sp-3);
  font-family: var(--ff-ui);
  font-size: 12px;
  font-weight: var(--fw-semibold);
  border-radius: var(--radius-sm, 6px);
  cursor: pointer;
  transition: background var(--dur-chrome) var(--ease-out),
              border-color var(--dur-chrome) var(--ease-out);
}
.conflict-banner-btn-primary {
  background: var(--no);
  color: var(--bg);
  border: 1px solid var(--no);
}
.conflict-banner-btn-primary:hover {
  background: color-mix(in srgb, var(--no) 88%, black);
}
.conflict-banner-btn-secondary {
  background: transparent;
  color: var(--txt);
  border: 1px solid color-mix(in srgb, var(--txt) 40%, transparent);
}
.conflict-banner-btn-secondary:hover {
  background: color-mix(in srgb, var(--txt) 8%, transparent);
}
.conflict-banner-btn-primary:focus-visible,
.conflict-banner-btn-secondary:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 2px;
}

/* Narrow tablet — stack vertically, hide detail string. */
@media (max-width: 600px) {
  .conflict-banner {
    flex-wrap: wrap;
    font-size: 12px;
    padding: var(--sp-2) var(--sp-3);
    gap: var(--sp-2);
  }
  .conflict-banner-detail { display: none; }
  .conflict-banner-actions { width: 100%; justify-content: flex-end; }
}

/* ======================================================================
   TOPBAR USER CHIP (2026-04-24)
   Shows display name + role + sign-out. Hidden by default; visible only
   under body[data-role] (set by _syncBodyRole at index.html:6368).
   All colors via color-mix on theme tokens — zero raw hex/rgba.
   Motion: 220ms cubic-bezier(0.32,0.72,0.24,1) — Depth-Morph curve.
   ====================================================================== */
.topbar-user-chip {
  --ease-chip: cubic-bezier(0.32, 0.72, 0.24, 1);
  --dur-chip: 220ms;
  display: none;
  align-items: center;
  gap: var(--sp-2);
  height: var(--sp-10);
  padding: 0 var(--sp-2);
}
body[data-role] .topbar-user-chip { display: flex; }

/* PR-HOME-BENCH (2026-06-13): the user-meta is now the account-dropdown button
   (Settings moved here from the removed gear). Row layout + button reset. */
.topbar-user-chip { position: relative; }
.topbar-user-meta {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: var(--sp-2);
  max-width: 240px;
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 8px;
  color: var(--txt);
  transition: background .14s ease;
}
.topbar-user-meta:hover,
.topbar-user-meta:focus-visible { background: var(--pn-h); outline: none; }
.topbar-acct-caret { font-size: 10px; color: var(--dim); }
.topbar-account-menu {
  position: absolute; top: calc(100% + 6px); right: 0;
  background: var(--pn); border: 1px solid var(--bdr); border-radius: 10px;
  box-shadow: 0 12px 32px rgba(0,0,0,.3); padding: 6px; min-width: 184px; z-index: 60;
}
.topbar-account-menu[hidden] { display: none; }
.topbar-account-menu button {
  display: flex; align-items: center; gap: 9px; width: 100%;
  padding: 9px 12px; background: transparent; border: none; border-radius: 7px;
  font: 600 13px var(--ff-ui); color: var(--txt); cursor: pointer; text-align: left;
}
.topbar-account-menu button:hover,
.topbar-account-menu button:focus-visible { background: var(--pn-h); color: var(--acc); outline: none; }
.topbar-user-name {
  font-size: 13px;
  font-weight: var(--fw-semibold);
  color: var(--txt);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 160px;
  line-height: 1;
}

.topbar-role-badge {
  display: inline-block;
  font-size: 9px;
  font-weight: var(--fw-bold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 2px var(--sp-2);
  border-radius: var(--radius-xs);
  line-height: 1.4;
  white-space: nowrap;
}
body[data-role="admin"] .topbar-role-badge {
  background: color-mix(in oklab, var(--acc) 22%, var(--pn));
  color: var(--acc2);
  border: 1px solid color-mix(in oklab, var(--acc) 40%, var(--bdr));
}
body[data-role="designer"] .topbar-role-badge {
  background: color-mix(in oklab, var(--acc) 14%, var(--pn));
  color: var(--dim);
  border: 1px solid color-mix(in oklab, var(--acc) 25%, var(--bdr));
}
body[data-role="shop_floor"] .topbar-role-badge {
  background: color-mix(in oklab, var(--warn) 18%, var(--pn));
  color: var(--warn);
  border: 1px solid color-mix(in oklab, var(--warn) 35%, var(--bdr));
}
body[data-role="viewer"] .topbar-role-badge {
  background: color-mix(in oklab, var(--dim) 14%, var(--pn));
  color: var(--dim);
  border: 1px solid color-mix(in oklab, var(--dim) 30%, var(--bdr));
}

.topbar-signout-wrap {
  position: relative;
  display: flex;
  align-items: center;
  height: var(--sp-10);
}

.topbar-signout-btn {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 0 var(--sp-3);
  height: 48px;
  min-width: 48px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--dim);
  font-size: 12px;
  font-weight: var(--fw-semibold);
  font-family: var(--ff-ui);
  letter-spacing: 0.2px;
  cursor: pointer;
  transition:
    color        var(--dur-chip) var(--ease-chip),
    background   var(--dur-chip) var(--ease-chip),
    border-color var(--dur-chip) var(--ease-chip),
    transform    var(--dur-chip) var(--ease-chip);
  white-space: nowrap;
}
.topbar-signout-btn:hover {
  color: var(--txt);
  background: var(--pn-h, color-mix(in oklab, var(--pn) 80%, var(--txt) 6%));
  border-color: color-mix(in oklab, var(--bdr) 80%, var(--txt) 10%);
}
.topbar-signout-btn:active {
  transform: scale(0.97);
  background: color-mix(in oklab, var(--no) 10%, var(--pn));
  color: var(--no);
  border-color: color-mix(in oklab, var(--no) 30%, var(--bdr));
}
.topbar-signout-btn:focus-visible {
  outline: 2px solid var(--ok);
  outline-offset: 2px;
}

#topbar-user-chip[data-confirming] {
  background: color-mix(in oklab, var(--no) 10%, var(--pn));
  border-radius: var(--radius-sm);
  outline: 1px solid color-mix(in oklab, var(--no) 28%, var(--bdr));
  padding: 0 var(--sp-3);
  transition:
    background    var(--dur-chip) var(--ease-chip),
    outline-color var(--dur-chip) var(--ease-chip),
    padding       var(--dur-chip) var(--ease-chip);
}
#topbar-user-chip[data-confirming] .topbar-signout-btn { display: none; }

.topbar-confirm-region {
  display: none;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--sp-1);
  pointer-events: none;
}
#topbar-user-chip[data-confirming] .topbar-confirm-region {
  display: flex;
  pointer-events: auto;
}

.topbar-confirm-question {
  font-size: 13px;
  font-weight: var(--fw-semibold);
  color: color-mix(in oklab, var(--no) 80%, var(--txt));
  white-space: nowrap;
  line-height: 1.3;
}
.topbar-confirm-draft-warn {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--warn);
  line-height: 1.2;
}
.topbar-confirm-actions {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

.topbar-confirm-btn {
  padding: 0 var(--sp-3);
  height: 36px;
  min-width: 72px;
  border: none;
  border-radius: var(--radius-pill, 999px);
  background: color-mix(in oklab, var(--no) 85%, var(--pn));
  color: var(--bg);
  font-size: 12px;
  font-weight: var(--fw-bold);
  font-family: var(--ff-ui);
  letter-spacing: 0.2px;
  cursor: pointer;
  transition:
    background var(--dur-chip) var(--ease-chip),
    transform  var(--dur-chip) var(--ease-chip);
}
.topbar-confirm-btn:active {
  transform: scale(0.96);
  background: var(--no);
}
.topbar-confirm-btn:focus-visible {
  outline: 2px solid var(--ok);
  outline-offset: 2px;
}

.topbar-cancel-btn {
  padding: 0 var(--sp-3);
  height: 36px;
  min-width: 64px;
  border: 1px solid color-mix(in oklab, var(--bdr) 80%, var(--no) 20%);
  border-radius: var(--radius-pill, 999px);
  background: var(--pn-h, var(--pn));
  color: var(--txt);
  font-size: 12px;
  font-weight: var(--fw-medium);
  font-family: var(--ff-ui);
  cursor: pointer;
  transition:
    background var(--dur-chip) var(--ease-chip),
    transform  var(--dur-chip) var(--ease-chip);
}
.topbar-cancel-btn:active {
  transform: scale(0.97);
  background: var(--pn);
}
.topbar-cancel-btn:focus-visible {
  outline: 2px solid var(--ok);
  outline-offset: 2px;
}

.topbar-confirm-countdown {
  font-size: 11px;
  font-weight: var(--fw-medium);
  color: var(--dim);
  min-width: 12px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

@media (prefers-reduced-motion: reduce) {
  .topbar-user-chip { --dur-chip: 0ms; }
}

@media (max-width: 600px) {
  .topbar-user-meta { max-width: 100px; }
  .topbar-user-name { max-width: 100px; }
  .topbar-signout-label { display: none; }
  .topbar-signout-btn { padding: 0 var(--sp-2); min-width: 44px; }
}

/* ============================================================
 * LIBRARY HUB — card-grid landing for the Libraries tab.
 * (PR-LIBHUB 2026-05-02)
 *
 * Replaces the dense 19-tab strip with a 4-up responsive card
 * grid. Every color routes through the project's CSS-variable
 * theme tokens so palette swaps in Settings → Color Theme
 * restyle the hub for free.
 *
 * Layout breakpoints:
 *   ≥1281px  → 4 columns  (desktop / large tablet landscape)
 *   ≤1280px  → 3 columns
 *   ≤900px   → 2 columns  (tablet portrait — primary shop floor)
 *   ≤520px   → 1 column   (phone)
 *
 * Tap targets: search input min-height 44px, "Open" button
 * min-height 40px, full-card click + Enter/Space activation —
 * gloves-friendly per shop-floor UX standards.
 * ============================================================ */

.lib-hub-header { margin-bottom: 22px; }

.lib-hub-title-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}

.lib-hub-title {
  color: var(--white);
  font-size: 28px;
  font-weight: 600;
  letter-spacing: -0.2px;
  margin: 0;
}

.lib-hub-actions {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  align-items: center;
}

.lib-hub-add-btn {
  white-space: nowrap;
  min-height: 40px;
  padding: 0 18px;
  font-weight: 600;
}

.lib-hub-search-row {
  position: relative;
  max-width: 560px;
}

.lib-hub-search-icon {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  pointer-events: none;
  opacity: 0.6;
  font-size: 14px;
}

.lib-hub-search-input {
  width: 100%;
  min-height: 44px;
  padding: 10px 14px 10px 40px;
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: 10px;
  color: var(--white);
  font-size: 14px;
  transition: border-color .14s ease, box-shadow .14s ease;
}

.lib-hub-search-input::placeholder { color: var(--dim); }

.lib-hub-search-input:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--acc) 50%, var(--bdr));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--acc) 15%, transparent);
}

/* PR-LIB-FLUID (2026-05-24): grid auto-flows columns based on a fluid
   minimum card width (clamp 180→240px). The browser packs as many
   equal-width cards as fit, so:
     320px phone → 1 col      720px tablet portrait → 3 cols
     1100px laptop → 4 cols   1600px monitor → 6 cols
     2560px ultrawide → 9+ cols    4K TV → 10+ cols
   Continuous flow — no media-query step-jumps. */
.lib-hub-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(clamp(180px, 22vw, 240px), 1fr));
  gap: clamp(10px, 1.2vw, 18px);
}

.lib-hub-card {
  display: flex;
  flex-direction: column;
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: 12px;
  overflow: hidden;
  transition: transform .14s ease, border-color .14s ease, box-shadow .14s ease;
  cursor: pointer;
}

.lib-hub-card:hover {
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--acc) 35%, var(--bdr));
  box-shadow: var(--shadow-3);
}

.lib-hub-card:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 2px;
}

.lib-hub-thumb {
  /* Shorter than 16/10 — keeps cards from feeling top-heavy in
   * the grid. Operators recognize libraries by title + icon, not
   * by thumbnail size, so we trade height for density. */
  aspect-ratio: 16 / 7;
  display: flex;
  align-items: center;
  justify-content: center;
  border-bottom: 1px solid var(--bdr);
  position: relative;
  overflow: hidden;
  /* Default tint — overridden per [data-accent] below. */
  background: linear-gradient(135deg,
    color-mix(in srgb, var(--acc) 16%, var(--pn)),
    color-mix(in srgb, var(--acc) 4%, var(--pn)));
  color: color-mix(in srgb, var(--acc) 78%, var(--white));
}

.lib-hub-thumb svg {
  width: 42%;
  height: 78%;
  /* --shadow-2 is single-layer (0 2px 4px) so it's valid as a drop-shadow
     argument; this re-uses the design-system shadow scale. */
  filter: drop-shadow(var(--shadow-2));
}

/* Real-photo overlay. Stacks on top of the SVG so that when the
 * shop drops a JPG into web/assets/library-thumbs/ the photo shows
 * — and when the file's missing or the user is offline, onerror
 * removes the <img> and the SVG underneath stays visible. No
 * mode flag, no UI toggle: progressive enhancement.
 *
 * The slight inset-shadow + tinted gradient overlay keep the photo
 * feeling part of the themed UI rather than a stock-photo collage. */
.lib-hub-thumb-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  /* Subtle dark scrim at the bottom so the card title (in the body
   * just below the thumb) feels visually connected to the photo. */
  filter: saturate(1.05) contrast(1.02);
}

.lib-hub-thumb-img::after {
  /* Note: ::after on <img> is invalid (replaced element). The scrim
   * lives on the parent .lib-hub-thumb instead — see the rule below. */
  content: none;
}

.lib-hub-card:hover .lib-hub-thumb-img {
  transform: scale(1.02);
  transition: transform .25s ease;
}

/* Per-category accent tints — each card has a slightly different
 * background gradient + icon color so the grid is scannable.
 * PR-LIB-COLOR-TOKENS (2026-05-25): the 9 wood-shop-evocative hex
 * values that used to live inline here are now design-system tokens
 * (--acc-cabinet through --acc-inventory) defined in tokens.css. The
 * icon-color is derived from the same token via color-mix so a single
 * source-of-truth controls each category's hue. */
.lib-hub-card[data-accent="cabinet"]   .lib-hub-thumb { background: linear-gradient(135deg, color-mix(in srgb, var(--acc-cabinet) 22%, var(--pn)), color-mix(in srgb, var(--acc-cabinet) 4%, var(--pn))); color: color-mix(in srgb, var(--acc-cabinet) 75%, var(--white)); }
.lib-hub-card[data-accent="door"]      .lib-hub-thumb { background: linear-gradient(135deg, color-mix(in srgb, var(--acc-door) 22%, var(--pn)), color-mix(in srgb, var(--acc-door) 4%, var(--pn))); color: color-mix(in srgb, var(--acc-door) 75%, var(--white)); }
.lib-hub-card[data-accent="sheet"]     .lib-hub-thumb { background: linear-gradient(135deg, color-mix(in srgb, var(--acc-sheet) 22%, var(--pn)), color-mix(in srgb, var(--acc-sheet) 4%, var(--pn))); color: color-mix(in srgb, var(--acc-sheet) 75%, var(--white)); }
.lib-hub-card[data-accent="finish"]    .lib-hub-thumb { background: linear-gradient(135deg, color-mix(in srgb, var(--acc-finish) 22%, var(--pn)), color-mix(in srgb, var(--acc-finish) 4%, var(--pn))); color: color-mix(in srgb, var(--acc-finish) 75%, var(--white)); }
.lib-hub-card[data-accent="drawer"]    .lib-hub-thumb { background: linear-gradient(135deg, color-mix(in srgb, var(--acc-drawer) 22%, var(--pn)), color-mix(in srgb, var(--acc-drawer) 4%, var(--pn))); color: color-mix(in srgb, var(--acc-drawer) 75%, var(--white)); }
.lib-hub-card[data-accent="lumber"]    .lib-hub-thumb { background: linear-gradient(135deg, color-mix(in srgb, var(--acc-lumber) 22%, var(--pn)), color-mix(in srgb, var(--acc-lumber) 4%, var(--pn))); color: color-mix(in srgb, var(--acc-lumber) 75%, var(--white)); }
.lib-hub-card[data-accent="profile"]   .lib-hub-thumb { background: linear-gradient(135deg, color-mix(in srgb, var(--acc-profile) 22%, var(--pn)), color-mix(in srgb, var(--acc-profile) 4%, var(--pn))); color: color-mix(in srgb, var(--acc-profile) 75%, var(--white)); }
.lib-hub-card[data-accent="hardware"]  .lib-hub-thumb { background: linear-gradient(135deg, color-mix(in srgb, var(--acc-hardware) 22%, var(--pn)), color-mix(in srgb, var(--acc-hardware) 4%, var(--pn))); color: color-mix(in srgb, var(--acc-hardware) 75%, var(--white)); }
.lib-hub-card[data-accent="category"]  .lib-hub-thumb { background: linear-gradient(135deg, color-mix(in srgb, var(--acc) 22%, var(--pn)), color-mix(in srgb, var(--acc) 4%, var(--pn))); }
.lib-hub-card[data-accent="inventory"] .lib-hub-thumb { background: linear-gradient(135deg, color-mix(in srgb, var(--acc-inventory) 22%, var(--pn)), color-mix(in srgb, var(--acc-inventory) 4%, var(--pn))); color: color-mix(in srgb, var(--acc-inventory) 75%, var(--white)); }

.lib-hub-card-body {
  display: flex;
  flex-direction: column;
  padding: 12px 14px 12px;
  gap: 3px;
  flex: 1;
}

.lib-hub-card-title {
  color: var(--white);
  font-size: 14px;
  font-weight: 600;
  margin: 0;
  line-height: 1.25;
}

.lib-hub-card-meta {
  display: flex;
  gap: 8px;
  align-items: baseline;
  margin-top: 2px;
  font-size: 10.5px;
  color: var(--dim);
}

.lib-hub-count {
  color: color-mix(in srgb, var(--acc) 80%, var(--white));
  font-weight: 600;
  font-size: 11.5px;
}

.lib-hub-mtime { font-size: 10.5px; }

.lib-hub-card-desc {
  color: var(--dim);
  font-size: 11.5px;
  line-height: 1.4;
  margin: 4px 0 10px;
  flex: 1;
  /* Clamp to 2 lines so cards align across the row. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.lib-hub-open {
  width: 100%;
  min-height: 36px;
  padding: 0 12px;
  font-size: 13px;
  font-weight: 600;
  margin-top: auto;
}

.lib-guide-panel {
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: 10px;
  padding: 16px 20px;
  margin-bottom: 18px;
  font-size: 12px;
  line-height: 1.6;
  color: var(--txt, var(--white));
}

.lib-detail-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-bottom: 18px;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--bdr);
}

.lib-detail-back {
  min-height: 40px;
  padding: 0 14px;
  font-size: 13px;
  font-weight: 600;
}

.lib-detail-title {
  color: var(--white);
  font-size: 22px;
  font-weight: 600;
  margin: 0;
  flex: 1;
}

.lib-hub-empty {
  text-align: center;
  color: var(--dim);
  padding: 60px 20px;
  font-size: 14px;
}

/* PR-LIB-FLUID (2026-05-24): column-count breakpoints (5/4/3/2/1) removed
 * — the .lib-hub-grid auto-fill above handles column count continuously.
 * Only PHONE-tier content tweaks remain: shrink the title, full-width the
 * action row, and fall to single-column at very narrow widths (where the
 * 180px clamp would otherwise force a horizontal scroll). */
@media (max-width: 480px) {
  .lib-hub-grid { grid-template-columns: 1fr; }
  .lib-hub-title { font-size: clamp(20px, 5vw, 24px); }
  .lib-hub-header { margin-bottom: 14px; }
  .lib-hub-title-row { margin-bottom: 10px; gap: 10px; }
  .lib-hub-add-btn { width: 100%; }
  .lib-hub-actions { width: 100%; }
  .lib-hub-card-body { padding: 10px 12px; }
  .lib-hub-thumb { aspect-ratio: 16 / 9; }
}

/* Hub card title & body padding scale with viewport too — small monitors
 * get tight 11px titles, 4K board mount gets a comfortable 16px. */
.lib-hub-title { font-size: clamp(22px, 1.8vw, 32px); }
.lib-hub-card-title { font-size: clamp(12px, 1vw, 15px); }
.lib-hub-card-body { padding: clamp(10px, 1vw, 14px) clamp(12px, 1.2vw, 16px); }

/* PR-LIB-RESPONSIVE (2026-05-24): lib-detail-header at narrow widths —
 * stack the Back button + title + Add button vertically so each gets a
 * full-width tap target on phones. Title still wraps if needed. */
@media (max-width: 640px) {
  .lib-detail-header {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
  }
  .lib-detail-title { font-size: 18px; flex: 0 0 auto; text-align: center; }
  .lib-detail-back { width: 100%; }
  .lib-detail-toolbar { gap: 8px; }
  .lib-detail-search { min-width: 0; flex: 1 1 100%; }
}

/* ============================================================
 * LIBRARY DETAIL-VIEW SHELL  (PR-LIBHUB-PHASE1 2026-05-03)
 *
 * Inside the hub (the card grid above), each card opens a per-
 * library detail view. This block styles that detail view's:
 *   - 72px accent strip (matches the hub card's tint)
 *   - 56px toolbar (search + bulk + density toggle)
 *   - bulk-mode bar (revealed when rows are selected)
 *   - 4 unified states (loading skeleton / empty / no-results / error)
 *   - card-density grid (160×200px tiles)
 *
 * Coexists with the legacy renderLibTable's `.lib-toolbar` /
 * `.lib-table` styles — those stay until the per-type feature
 * flag rolls every library type onto the unified renderer.
 *
 * All colors via the project's CSS-variable theme tokens — re-tints
 * automatically when Settings → Color Theme changes.
 * ============================================================ */

/* ── Accent strip ─────────────────────────────────────────────── */
.lib-detail-strip {
  position: relative;
  height: 12px;
  margin: 0 0 14px;
  border-radius: 6px;
  overflow: hidden;
  /* Default tint — overridden per [data-accent="<libtype>"] below
   * to match the hub card the user just clicked. */
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--acc) 22%, var(--pn)),
    color-mix(in srgb, var(--acc) 4%,  var(--pn)));
}
.lib-detail-strip-meta {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 0 14px;
  pointer-events: none;
}
.lib-detail-count {
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.3px;
  color: color-mix(in srgb, var(--acc) 70%, var(--white));
  text-transform: uppercase;
}

/* Per-library accent tints — same palette as the hub card thumbs
 * so the operator's eye flows from the clicked card into the
 * detail view without a jarring color jump.
 * PR-LIB-COLOR-TOKENS (2026-05-25): all 9 hex values now route through
 * the --acc-* category tokens defined in tokens.css. */
.lib-detail-strip[data-accent="cabinet"]          { background: linear-gradient(90deg, color-mix(in srgb, var(--acc-cabinet) 25%, var(--pn)), color-mix(in srgb, var(--acc-cabinet) 4%, var(--pn))); }
.lib-detail-strip[data-accent="door"]             { background: linear-gradient(90deg, color-mix(in srgb, var(--acc-door) 25%, var(--pn)), color-mix(in srgb, var(--acc-door) 4%, var(--pn))); }
.lib-detail-strip[data-accent="material"]         { background: linear-gradient(90deg, color-mix(in srgb, var(--acc-sheet) 25%, var(--pn)), color-mix(in srgb, var(--acc-sheet) 4%, var(--pn))); }
.lib-detail-strip[data-accent="finish"]           { background: linear-gradient(90deg, color-mix(in srgb, var(--acc-finish) 25%, var(--pn)), color-mix(in srgb, var(--acc-finish) 4%, var(--pn))); }
.lib-detail-strip[data-accent="drawer_box"]       { background: linear-gradient(90deg, color-mix(in srgb, var(--acc-drawer) 25%, var(--pn)), color-mix(in srgb, var(--acc-drawer) 4%, var(--pn))); }
.lib-detail-strip[data-accent="wood_type"]        { background: linear-gradient(90deg, color-mix(in srgb, var(--acc-lumber) 25%, var(--pn)), color-mix(in srgb, var(--acc-lumber) 4%, var(--pn))); }
.lib-detail-strip[data-accent="hinge"],
.lib-detail-strip[data-accent="slide"],
.lib-detail-strip[data-accent="pull"],
.lib-detail-strip[data-accent="shelf_pin"],
.lib-detail-strip[data-accent="corner_hardware"]  { background: linear-gradient(90deg, color-mix(in srgb, var(--acc-hardware) 25%, var(--pn)), color-mix(in srgb, var(--acc-hardware) 4%, var(--pn))); }
.lib-detail-strip[data-accent="edge_detail"],
.lib-detail-strip[data-accent="frame_detail"],
.lib-detail-strip[data-accent="panel"],
.lib-detail-strip[data-accent="trim"],
.lib-detail-strip[data-accent="sticking_profile"],
.lib-detail-strip[data-accent="miter_profile"],
.lib-detail-strip[data-accent="panel_profile"]    { background: linear-gradient(90deg, color-mix(in srgb, var(--acc-profile) 25%, var(--pn)), color-mix(in srgb, var(--acc-profile) 4%, var(--pn))); }
.lib-detail-strip[data-accent="inventory"]        { background: linear-gradient(90deg, color-mix(in srgb, var(--acc-inventory) 25%, var(--pn)), color-mix(in srgb, var(--acc-inventory) 4%, var(--pn))); }

/* ── Toolbar (search + density toggle) ────────────────────────── */
.lib-detail-toolbar {
  display: flex;
  gap: 10px;
  align-items: center;
  margin-bottom: 14px;
  flex-wrap: wrap;
}

.lib-detail-search {
  position: relative;
  flex: 1;
  min-width: 220px;
}
.lib-detail-search svg {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  width: 16px;
  height: 16px;
  pointer-events: none;
  color: var(--dim);
}
.lib-detail-search-input {
  width: 100%;
  min-height: 44px;            /* shop-floor tap target */
  padding: 10px 14px 10px 38px;
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: 10px;
  color: var(--white);
  font-size: 14px;
  transition: border-color .14s ease, box-shadow .14s ease;
}
.lib-detail-search-input::placeholder { color: var(--dim); }
.lib-detail-search-input:focus {
  outline: none;
  border-color: color-mix(in srgb, var(--acc) 50%, var(--bdr));
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--acc) 15%, transparent);
}

.lib-detail-bulk-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0 12px;
  min-height: 44px;
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: 10px;
  color: var(--white);
  font-size: 13px;
  cursor: pointer;
  user-select: none;
}
.lib-detail-bulk-toggle:hover {
  border-color: color-mix(in srgb, var(--acc) 35%, var(--bdr));
}
.lib-detail-bulk-toggle input { accent-color: var(--acc); }

.lib-detail-density {
  display: inline-flex;
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: 10px;
  overflow: hidden;
}
.lib-detail-density-btn {
  width: 44px;
  height: 44px;            /* WCAG 2.5.5 target size */
  border: none;
  background: transparent;
  color: var(--dim);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background .14s ease, color .14s ease;
}
.lib-detail-density-btn svg { width: 18px; height: 18px; }
.lib-detail-density-btn:hover {
  background: color-mix(in srgb, var(--acc) 8%, transparent);
  color: var(--white);
}
.lib-detail-density-btn[aria-pressed="true"] {
  background: color-mix(in srgb, var(--acc) 16%, transparent);
  color: var(--acc);
}
.lib-detail-density-btn:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: -2px;
}

/* ── Bulk-mode bar ────────────────────────────────────────────── */
/* PR-LIBHUB-PHASE3 (2026-05-03) duplicate-fix: the Phase 1 stub for
 * `.lib-detail-bulk-bar` + `.lib-detail-bulk-count` was removed here.
 * The canonical layout lives in the Phase 3 block below (search for
 * "LIBRARY DETAIL — BULK BAR + UNDO TOAST"). The Phase 1 rule was
 * shadowed by Phase 3 via cascade order anyway, but leaving it
 * meant a future reader had to chase two rule blocks for the same
 * selector. */

/* ── 4 unified states ─────────────────────────────────────────── */
.lib-detail-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 60px 20px 80px;
  gap: 8px;
}
.lib-detail-state-icon {
  width: 48px;
  height: 48px;
  color: var(--dim);
  margin-bottom: 8px;
}
.lib-detail-state-icon.error { color: var(--no); }
.lib-detail-state-headline {
  font-size: 16px;
  font-weight: 600;
  color: var(--white);
}
.lib-detail-state-body {
  font-size: 13px;
  color: var(--dim);
  max-width: 420px;
  line-height: 1.5;
  margin-bottom: 8px;
}
.lib-detail-state .ba,
.lib-detail-state .bg {
  min-height: 44px;
  padding: 0 18px;
}

/* Skeleton shimmer — fires for the LOADING state. Drives a
 * background-position sweep on a 200%-wide gradient so the panel
 * feels like it's about to receive content. Respects
 * prefers-reduced-motion via the @media block below. */
.lib-detail-skeleton { padding: 6px 0; }
.lib-skel-row {
  display: grid;
  grid-template-columns: 40px 1fr 1fr 1fr;
  gap: 12px;
  padding: 12px 8px;
  align-items: center;
  border-bottom: 1px solid var(--bdr);
  min-height: 52px;
}
.lib-skel-thumb,
.lib-skel-bar {
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--bdr) 60%, var(--pn)),
    color-mix(in srgb, var(--bdr) 30%, var(--pn)),
    color-mix(in srgb, var(--bdr) 60%, var(--pn)));
  background-size: 200% 100%;
  animation: lib-skel-shimmer 1.2s linear infinite;
  border-radius: 4px;
  height: 12px;
}
.lib-skel-thumb { width: 32px; height: 32px; border-radius: 6px; }
.lib-skel-bar.w60 { width: 60%; }
.lib-skel-bar.w40 { width: 40%; }
.lib-skel-bar.w30 { width: 30%; }
@keyframes lib-skel-shimmer {
  0%   { background-position: 100% 0; }
  100% { background-position: -100% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .lib-skel-thumb,
  .lib-skel-bar { animation: none; }
}

/* ── Card-density grid (visual recognition mode) ──────────────── */
.lib-detail-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 14px;
}
.lib-card {
  position: relative;
  display: flex;
  flex-direction: column;
  height: 200px;
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: 10px;
  overflow: hidden;
  cursor: pointer;
  transition: transform .14s ease, border-color .14s ease, box-shadow .14s ease;
}
.lib-card:hover {
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--acc) 35%, var(--bdr));
  box-shadow: var(--shadow-3);
}
.lib-card:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 2px;
}
.lib-card.selected {
  border-color: var(--acc);
  background: color-mix(in srgb, var(--acc) 6%, var(--pn));
}
.lib-card-thumb {
  height: 80px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--acc) 8%, var(--pn));
  border-bottom: 1px solid var(--bdr);
}
.lib-card-thumb .lib-thumb,
.lib-card-thumb .lib-thumb-swatch {
  width: 40px;
  height: 40px;
}
.lib-card-body {
  display: flex;
  flex-direction: column;
  padding: 10px 12px;
  gap: 4px;
  flex: 1;
  overflow: hidden;
}
.lib-card-name {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
  color: var(--white);
  line-height: 1.25;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.lib-card-specs {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 11px;
  color: var(--dim);
  overflow: hidden;
}
.lib-card-spec {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.lib-card-bulk {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: 4px;
}
.lib-card-bulk input { accent-color: var(--acc); cursor: pointer; }

/* ── Mobile / tablet: density toggle hides; force list mode ──── */
@media (max-width: 600px) {
  .lib-detail-density { display: none; }
  .lib-detail-cards { grid-template-columns: 1fr; }
}

/* ============================================================
 * LIBRARY DETAIL — FILTER CHIPS  (PR-LIBHUB-PHASE2 2026-05-03)
 *
 * Renders below the toolbar and above the body. One group per
 * filter chip declared in LIB_DETAIL_CONFIGS[type].filterChips —
 * each group lists every distinct value found in the loaded
 * items, sorted by frequency descending, with a count badge.
 *
 * Hidden when the loaded items have no values for any chip in
 * the group (the JS layer suppresses rendering). Active chips
 * use the accent fill; inactive use the plain panel background.
 * ============================================================ */
.lib-chip-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin: 0 0 14px;
}

.lib-chip-group {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  padding: 4px 8px;
  background: color-mix(in srgb, var(--pn) 80%, transparent);
  border: 1px solid var(--bdr);
  border-radius: 8px;
}

.lib-chip-group-label {
  font-size: 11px;
  font-weight: 600;
  color: var(--dim);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  margin-right: 2px;
}

.lib-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 32px;        /* WCAG 2.5.5 not enforced on chips per
                               2.2 AA; 32px is the visual standard */
  padding: 4px 10px;
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: 999px;
  color: var(--white);
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: background .12s ease, border-color .12s ease;
}

.lib-chip:hover {
  background: color-mix(in srgb, var(--acc) 8%, var(--pn));
  border-color: color-mix(in srgb, var(--acc) 35%, var(--bdr));
}

.lib-chip.active {
  background: color-mix(in srgb, var(--acc) 18%, var(--pn));
  border-color: var(--acc);
  color: var(--white);
}

.lib-chip:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 2px;
}

.lib-chip-count {
  font-size: 11px;
  font-weight: 600;
  color: color-mix(in srgb, var(--acc) 70%, var(--white));
  background: color-mix(in srgb, var(--acc) 14%, transparent);
  padding: 1px 6px;
  border-radius: 999px;
}

.lib-chip-clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  background: transparent;
  border: 1px solid var(--bdr);
  border-radius: 50%;
  color: var(--dim);
  font-size: 12px;
  cursor: pointer;
  margin-left: 2px;
}

.lib-chip-clear:hover {
  color: var(--white);
  border-color: var(--white);
}

/* ============================================================
 * LIBRARY DETAIL — ★ RECENT VIRTUAL SECTION  (PHASE 2)
 *
 * Above the main list. Hidden when search or chip is active
 * (operator's intent is narrow-search; Recent at top would be
 * noise then). Hidden when empty. List density: 5 items in a
 * horizontal row; card density: 3 items.
 * ============================================================ */
.lib-recent-group {
  margin: 0 0 18px;
  padding: 12px 14px;
  background: color-mix(in srgb, var(--acc) 4%, var(--pn));
  border: 1px solid color-mix(in srgb, var(--acc) 18%, var(--bdr));
  border-radius: 10px;
}

.lib-recent-label {
  margin: 0 0 8px;
  font-size: 11px;
  font-weight: 700;
  color: color-mix(in srgb, var(--acc) 80%, var(--white));
  text-transform: uppercase;
  letter-spacing: 0.6px;
}

.lib-recent-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.lib-recent-item {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px 6px 8px;
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: 999px;
  color: var(--white);
  font-size: 12px;
  cursor: pointer;
  min-height: 36px;
  transition: border-color .12s ease, transform .12s ease;
  max-width: 240px;
}

.lib-recent-item:hover {
  border-color: color-mix(in srgb, var(--acc) 50%, var(--bdr));
  transform: translateY(-1px);
}

.lib-recent-item:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 2px;
}

.lib-recent-thumb {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  flex-shrink: 0;
}

.lib-recent-thumb .lib-thumb,
.lib-recent-thumb .lib-thumb-swatch {
  width: 24px;
  height: 24px;
}

.lib-recent-name {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 500;
}

/* ============================================================
 * COMMAND PALETTE  (Ctrl+K / Cmd+K)  (PHASE 2)
 *
 * Modal overlay with a 540px-wide centered card. Searches every
 * library's libCache at once. Up/Down navigation + Enter to open
 * + Esc to close, all keyboard-accessible.
 * ============================================================ */
.lib-palette-modal {
  position: fixed;
  inset: 0;
  z-index: 9999;
  /* PR-LIB-COLOR-TOKENS: --scrim-strong = rgba(0,0,0,.55). */
  background: var(--scrim-strong);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 80px 16px 16px;
  animation: lib-palette-fade .12s ease-out;
}

.lib-palette-modal.hidden { display: none; }

@keyframes lib-palette-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.lib-palette-card {
  width: 100%;
  max-width: 580px;
  background: var(--sf, var(--pn));
  border: 1px solid var(--bdr);
  border-radius: 14px;
  box-shadow: var(--shadow-5);    /* drawer / sheet elevation */
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.lib-palette-search-wrap {
  position: relative;
  padding: 0;
  border-bottom: 1px solid var(--bdr);
}

.lib-palette-search-icon {
  position: absolute;
  left: 18px;
  top: 50%;
  transform: translateY(-50%);
  width: 18px;
  height: 18px;
  color: var(--dim);
  pointer-events: none;
}

.lib-palette-input {
  width: 100%;
  height: 56px;
  padding: 0 18px 0 48px;
  background: transparent;
  border: none;
  color: var(--white);
  font-size: 15px;
  outline: none;
}

.lib-palette-input::placeholder { color: var(--dim); }

.lib-palette-results {
  max-height: 50vh;
  overflow-y: auto;
  padding: 6px 0;
}

.lib-palette-empty {
  padding: 30px 18px;
  text-align: center;
  color: var(--dim);
  font-size: 13px;
}

.lib-palette-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 10px 18px;
  background: transparent;
  border: none;
  color: var(--white);
  font-size: 13px;
  cursor: pointer;
  text-align: left;
  border-radius: 0;
  min-height: 40px;
}

.lib-palette-item.active,
.lib-palette-item:hover {
  background: color-mix(in srgb, var(--acc) 14%, transparent);
}

.lib-palette-item:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: -2px;
}

.lib-palette-type {
  font-size: 11px;
  font-weight: 600;
  color: color-mix(in srgb, var(--acc) 80%, var(--white));
  background: color-mix(in srgb, var(--acc) 12%, transparent);
  padding: 2px 8px;
  border-radius: 999px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  flex-shrink: 0;
  min-width: 80px;
  text-align: center;
}

.lib-palette-name {
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  flex: 1;
}

.lib-palette-hint {
  display: flex;
  gap: 16px;
  padding: 10px 18px;
  background: color-mix(in srgb, var(--bdr) 30%, var(--pn));
  border-top: 1px solid var(--bdr);
  font-size: 11px;
  color: var(--dim);
}

.lib-palette-hint kbd {
  display: inline-block;
  margin-right: 4px;
  padding: 1px 5px;
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: 4px;
  font-family: inherit;
  font-size: 10px;
  font-weight: 600;
  color: var(--white);
}

@media (max-width: 600px) {
  .lib-palette-modal { padding: 24px 8px; }
  .lib-palette-input { font-size: 14px; height: 48px; padding-left: 42px; }
}

/* ============================================================
 * LIBRARY DETAIL — BULK BAR + UNDO TOAST  (PR-LIBHUB-PHASE3 2026-05-03)
 *
 * Phase 3 closes anti-pattern 2 (bulk-mode-behind-toggle): the
 * "Bulk" toggle is gone; checkboxes are always present per row
 * and the bulk bar slides in automatically when ≥1 row is
 * selected. The bar carries Cancel / Select-all / Delete /
 * Export-CSV.
 *
 * The Undo toast extends the existing #toast-stack with an
 * "Undo" action button + 5s window. Calling code lives in
 * _showUndoToast(); see PR-LIBHUB-PHASE3 in index.html.
 * ============================================================ */

/* ── Always-present bulk checkbox column ─────────────────────── */
.lib-cell-bulk {
  width: 32px;
  min-width: 32px;
  padding: 0 4px;
  text-align: center;
}

/* Subtle when nothing selected — checkbox still visible but the
 * row doesn't shout "selection mode active" until it actually is. */
.lib-cell-bulk-off input[type="checkbox"] { opacity: 0.45; }
.lib-cell-bulk-off:hover input[type="checkbox"] { opacity: 1; }
.lib-cell-bulk-on  input[type="checkbox"] { opacity: 1; }

.lib-cell-bulk input[type="checkbox"] {
  width: 16px;
  height: 16px;
  accent-color: var(--acc);
  cursor: pointer;
  transition: opacity .12s ease;
}

.lib-row-selected td {
  background: color-mix(in srgb, var(--acc) 8%, transparent);
}

/* Card-mode checkbox — top-left corner, subtle until selected. */
.lib-card-bulk-off { opacity: 0.45; }
.lib-card-bulk-off:hover,
.lib-card:hover .lib-card-bulk-off { opacity: 1; }
.lib-card-bulk-on  { opacity: 1; }

/* ── New bulk-bar layout ──────────────────────────────────────── */
.lib-detail-bulk-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 14px;
  padding: 8px 10px;
  background: color-mix(in srgb, var(--acc) 12%, var(--pn));
  border: 1px solid color-mix(in srgb, var(--acc) 40%, var(--bdr));
  border-radius: 10px;
  /* Slide-in from above on first appearance. */
  animation: lib-bulk-bar-in .14s ease-out;
}

@keyframes lib-bulk-bar-in {
  from { transform: translateY(-4px); opacity: 0; }
  to   { transform: translateY(0);    opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .lib-detail-bulk-bar { animation: none; }
}

.lib-bulk-cancel {
  width: 36px;
  height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: 8px;
  color: var(--white);
  font-size: 16px;
  cursor: pointer;
  flex-shrink: 0;
}
.lib-bulk-cancel:hover { border-color: var(--white); }
.lib-bulk-cancel:focus-visible { outline: 2px solid var(--acc); outline-offset: 2px; }

.lib-bulk-count-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 36px;
  padding: 0 12px;
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: 999px;
  color: var(--white);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}
.lib-bulk-count-chip svg {
  width: 14px;
  height: 14px;
  transition: transform .14s ease;
}
.lib-bulk-count-chip.expanded svg { transform: rotate(180deg); }
.lib-bulk-count-chip:hover { border-color: var(--acc); }

.lib-bulk-select-all {
  height: 36px;
  padding: 0 14px;
  background: var(--acc);
  border: 1px solid var(--acc);
  border-radius: 999px;
  color: var(--acc-ink);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  /* Subtle pulse to suggest "tap me again to confirm." */
  animation: lib-bulk-select-pulse 1.6s ease-in-out infinite;
}
@keyframes lib-bulk-select-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--acc) 55%, transparent); }
  50%      { box-shadow: 0 0 0 6px color-mix(in srgb, var(--acc) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .lib-bulk-select-all { animation: none; }
}

.lib-bulk-spacer { flex: 1; }

.lib-bulk-action {
  height: 36px;
  padding: 0 14px;
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: 8px;
  color: var(--white);
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
}
.lib-bulk-action:hover {
  border-color: color-mix(in srgb, var(--acc) 50%, var(--bdr));
  background: color-mix(in srgb, var(--acc) 8%, var(--pn));
}
.lib-bulk-action.danger { color: var(--no); }
.lib-bulk-action.danger:hover {
  background: color-mix(in srgb, var(--no) 12%, var(--pn));
  border-color: var(--no);
}
.lib-bulk-action:focus-visible { outline: 2px solid var(--acc); outline-offset: 2px; }

@media (max-width: 700px) {
  .lib-detail-bulk-bar { flex-wrap: wrap; }
  .lib-bulk-spacer { width: 100%; height: 0; }
}

/* ── Undo-toast variant ───────────────────────────────────────── */
.toast-undo .lib-undo-btn {
  margin-left: auto;
  margin-right: 6px;
  height: 28px;
  padding: 0 12px;
  background: var(--acc);
  border: 1px solid var(--acc);
  border-radius: 6px;
  color: var(--acc-ink);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.toast-undo .lib-undo-btn:hover { filter: brightness(1.08); }
.toast-undo .lib-undo-btn:focus-visible {
  outline: 2px solid var(--white);
  outline-offset: 2px;
}

/* ============================================================
 * PHASE 4 — ENGAGEMENT POLISH  (PR-LIBHUB-PHASE4 2026-05-03)
 *
 * 1. Hover-peek floating panel  (.lib-peek)
 * 2. Photo overlay in detail-view thumbs  (.lib-thumb-photo)
 * 3. "Similar in this library" footer in edit drawer
 * 4. "● Unsaved" / "✓ Saved" status pill in edit drawer
 *
 * All animations gate on prefers-reduced-motion at the bottom
 * of this block.
 * ============================================================ */

/* ── Hover-peek floating panel ────────────────────────────────── */
.lib-peek {
  position: fixed;
  z-index: 80;                  /* above modals (palette = 9999), below toasts */
  width: 320px;
  max-height: 320px;
  /* PR-LIB-COLOR-TOKENS: surface routes through --bg so it theme-swaps;
     96% alpha keeps the blur-backdrop translucency intact. */
  background: color-mix(in srgb, var(--bg) 96%, transparent);
  -webkit-backdrop-filter: blur(16px) saturate(160%);
  backdrop-filter: blur(16px) saturate(160%);
  border: 1px solid var(--bdr);
  border-radius: 10px;
  padding: 12px;
  box-shadow: var(--shadow-6);  /* floating panel — modal-class elevation */
  pointer-events: none;
  overflow: hidden;
  animation: lib-peek-in .15s ease-out;
}

@keyframes lib-peek-in {
  from { opacity: 0; transform: translateY(2px); }
  to   { opacity: 1; transform: translateY(0); }
}

.lib-peek-thumb {
  position: relative;
  width: 100%;
  height: 100px;
  /* PR-LIB-COLOR-TOKENS: bare var(--bg) — fallback hex stripped. */
  background: linear-gradient(180deg, var(--pn), var(--bg));
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 8px;
  border: 1px solid var(--hairline-white);
  overflow: hidden;
}
.lib-peek-thumb .lib-thumb,
.lib-peek-thumb .lib-thumb-swatch {
  width: 56px;
  height: 56px;
}
.lib-peek-photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Photos win over the SVG when present; the SVG sits underneath
   * as a fallback the onerror cascade can fall back to. */
}

.lib-peek-name {
  color: var(--white);
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 6px;
}

.lib-peek-rows {
  display: grid;
  grid-template-columns: max-content 1fr;
  column-gap: 10px;
  row-gap: 4px;
  font-size: 11px;
  max-height: 140px;
  overflow-y: auto;
}
.lib-peek-key {
  color: var(--dim);
  font-weight: 500;
  white-space: nowrap;
}
.lib-peek-val {
  color: var(--white);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.lib-peek-empty {
  grid-column: 1 / -1;
  color: var(--dim);
  font-size: 11px;
  text-align: center;
  padding: 16px 0;
  font-style: italic;
}

/* Hide on coarse-pointer / touch devices entirely. The JS
 * handler also short-circuits on `(pointer: coarse)`, but the
 * media-query rule is the second line of defense. */
@media (pointer: coarse) {
  .lib-peek { display: none; }
}

/* ── Photo overlay on the standard 32px thumb cells ───────────── */
.lib-thumb-with-photo {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
}
.lib-thumb-with-photo .lib-thumb,
.lib-thumb-with-photo .lib-thumb-swatch {
  /* SVG sits under the photo. If the photo loads, it covers the
   * SVG; if the onerror cascade removes the photo, the SVG is
   * still there. */
  position: absolute;
  inset: 0;
}
.lib-thumb-with-photo .lib-thumb-photo {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 4px;
}

/* In card density, the thumb is 80px tall — let the photo fill
 * the entire region. */
.lib-card-thumb .lib-thumb-with-photo {
  width: 100%;
  height: 100%;
}
.lib-card-thumb .lib-thumb-with-photo .lib-thumb,
.lib-card-thumb .lib-thumb-with-photo .lib-thumb-swatch {
  width: 40%;
  height: 40%;
  position: absolute;
  inset: auto;
}
.lib-card-thumb .lib-thumb-with-photo .lib-thumb-photo {
  border-radius: 0;
}

/* ── "Similar in this library" footer (edit drawer) ───────────── */
.lib-similar-footer {
  margin: 18px 0 14px;
  padding: 12px 14px;
  background: color-mix(in srgb, var(--acc) 4%, var(--pn));
  border: 1px solid color-mix(in srgb, var(--acc) 18%, var(--bdr));
  border-radius: 10px;
}

.lib-similar-label {
  margin: 0 0 8px;
  font-size: 11px;
  font-weight: 700;
  color: color-mix(in srgb, var(--acc) 80%, var(--white));
  text-transform: uppercase;
  letter-spacing: 0.6px;
}

.lib-similar-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.lib-similar-item {
  padding: 6px 12px;
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: 999px;
  color: var(--white);
  font-size: 12px;
  cursor: pointer;
  min-height: 32px;
  transition: border-color .12s ease, transform .12s ease;
  white-space: nowrap;
  max-width: 240px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.lib-similar-item:hover {
  border-color: color-mix(in srgb, var(--acc) 50%, var(--bdr));
  transform: translateY(-1px);
}
.lib-similar-item:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 2px;
}

/* ── Edit-drawer status pill ──────────────────────────────────── */
.lib-edit-status {
  display: inline-block;
  margin: 0 0 8px;
  padding: 3px 10px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.3px;
  transition: opacity .14s ease;
}
.lib-edit-status.hidden { display: none; }
.lib-edit-status.unsaved {
  background: color-mix(in srgb, var(--warn) 18%, var(--pn));
  border: 1px solid color-mix(in srgb, var(--warn) 40%, var(--bdr));
  color: var(--warn);
}
.lib-edit-status.saved {
  background: color-mix(in srgb, var(--ok) 18%, var(--pn));
  border: 1px solid color-mix(in srgb, var(--ok) 40%, var(--bdr));
  color: var(--ok);
  animation: lib-status-fade-in .2s ease-out;
}
@keyframes lib-status-fade-in {
  from { opacity: 0; transform: translateX(-4px); }
  to   { opacity: 1; transform: translateX(0); }
}

/* ── prefers-reduced-motion: kill every Phase 4 animation ─────── */
@media (prefers-reduced-motion: reduce) {
  .lib-peek         { animation: none; }
  .lib-edit-status.saved { animation: none; }
  .lib-similar-item:hover { transform: none; }
}

/* ============================================================
 * PHASE 6 — SMART EMPTY STATES + ONBOARDING  (PR-LIBHUB-PHASE6 2026-05-03)
 *
 * 1. .lib-detail-state-actions / .lib-detail-state-readonly /
 *    .lib-detail-state-help — role-aware empty-state CTA layout.
 *    Admin / designer get the "+ Add first item" primary button;
 *    worker / viewer get the read-only message.
 * 2. .lib-tour-nudge — first-session ⌘K + / shortcut tip. One-time
 *    callout above the toolbar. Dismissible. Stored seen-state in
 *    localStorage `csp.lib.tour-seen`.
 * ============================================================ */

.lib-detail-state-actions {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  justify-content: center;
  margin-top: 4px;
}

.lib-detail-state-help {
  min-height: 44px;
  padding: 0 16px;
  font-size: 12px;
}

.lib-detail-state-readonly {
  max-width: 360px;
  padding: 10px 14px;
  background: color-mix(in srgb, var(--bdr) 30%, var(--pn));
  border: 1px solid var(--bdr);
  border-radius: 8px;
  color: var(--dim);
  font-size: 12px;
  line-height: 1.5;
  text-align: center;
}

/* ── First-session ⌘K nudge ───────────────────────────────────── */
.lib-tour-nudge {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 14px;
  margin: 0 0 14px;
  background: color-mix(in srgb, var(--acc) 8%, var(--pn));
  border: 1px solid color-mix(in srgb, var(--acc) 30%, var(--bdr));
  border-radius: 8px;
  font-size: 12px;
  color: var(--white);
  animation: lib-tour-in .2s ease-out;
}

@keyframes lib-tour-in {
  from { opacity: 0; transform: translateY(-3px); }
  to   { opacity: 1; transform: translateY(0); }
}

.lib-tour-nudge-out {
  animation: lib-tour-out .2s ease-in forwards;
}

@keyframes lib-tour-out {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(-3px); }
}

.lib-tour-nudge-icon {
  font-size: 16px;
  flex-shrink: 0;
}

.lib-tour-nudge-text {
  flex: 1;
  line-height: 1.4;
}

.lib-tour-nudge-text kbd {
  display: inline-block;
  padding: 1px 6px;
  margin: 0 2px;
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: 4px;
  font-family: inherit;
  font-size: 10.5px;
  font-weight: 600;
  color: var(--white);
}

.lib-tour-nudge-dismiss {
  width: 28px;
  height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
  color: var(--dim);
  font-size: 14px;
  cursor: pointer;
  flex-shrink: 0;
  transition: color .12s ease, border-color .12s ease;
}

.lib-tour-nudge-dismiss:hover {
  color: var(--white);
  border-color: var(--bdr);
}

.lib-tour-nudge-dismiss:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .lib-tour-nudge,
  .lib-tour-nudge-out { animation: none; }
}

@media (max-width: 600px) {
  .lib-tour-nudge { font-size: 11px; padding: 8px 10px; }
  .lib-tour-nudge-icon { font-size: 14px; }
}

/* ============================================================
 * PHASE 7 — METRICS DASHBOARD + CSAT  (PR-LIBHUB-PHASE7 2026-05-03)
 *
 * Two surfaces:
 *   1. .libmet-* — admin dashboard at /admin/library-metrics.
 *      Reuses .rep-controls + .rep-kpi-* visual language from
 *      Shop Reports so admins see one consistent metrics aesthetic.
 *   2. .lib-csat-prompt — bottom-right pill prompt that fires once
 *      after the user's 3rd library open. Scoring 1-5 + dismiss ✕.
 * ============================================================ */

.libmet-win-btn.on {
  background: var(--acc);
  border-color: var(--acc);
  color: var(--acc-ink);
}

.libmet-table thead th {
  position: sticky;
  top: 0;
  background: var(--pn);
  z-index: 1;
}

.libmet-table tbody tr:last-child {
  border-bottom: none !important;
}

/* ── CSAT prompt pill ─────────────────────────────────────────── */
.lib-csat-prompt {
  position: fixed;
  bottom: 24px;
  right: 24px;
  z-index: 200;
  background: var(--sf, var(--pn));
  border: 1px solid var(--bdr);
  border-radius: 12px;
  padding: 16px 18px 14px;
  width: 320px;
  box-shadow: var(--shadow-5);   /* drawer-class floating pill */
  animation: lib-csat-in .2s ease-out;
}

@keyframes lib-csat-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

.lib-csat-prompt-out {
  animation: lib-csat-out .2s ease-in forwards;
}

@keyframes lib-csat-out {
  from { opacity: 1; transform: translateY(0); }
  to   { opacity: 0; transform: translateY(8px); }
}

.lib-csat-q {
  font-size: 13px;
  font-weight: 600;
  color: var(--white);
  margin: 0 24px 12px 0;
  line-height: 1.35;
}

.lib-csat-scale {
  display: flex;
  gap: 6px;
  margin-bottom: 4px;
}

.lib-csat-btn {
  flex: 1;
  height: 40px;
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: 8px;
  color: var(--white);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: background .12s ease, border-color .12s ease;
}

.lib-csat-btn:hover {
  background: color-mix(in srgb, var(--acc) 12%, var(--pn));
  border-color: var(--acc);
}

.lib-csat-btn:focus-visible {
  outline: 2px solid var(--acc);
  outline-offset: 2px;
}

.lib-csat-scale-meta {
  display: flex;
  justify-content: space-between;
  font-size: 10px;
  color: var(--dim);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  padding: 0 2px;
}

.lib-csat-skip {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 24px;
  height: 24px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
  color: var(--dim);
  font-size: 13px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.lib-csat-skip:hover {
  color: var(--white);
  border-color: var(--bdr);
}

.lib-csat-thanks {
  font-size: 13px;
  font-weight: 500;
  color: color-mix(in srgb, var(--acc) 80%, var(--white));
  text-align: center;
  padding: 6px 0;
}

@media (prefers-reduced-motion: reduce) {
  .lib-csat-prompt,
  .lib-csat-prompt-out { animation: none; }
}

@media (max-width: 600px) {
  .lib-csat-prompt {
    bottom: 16px;
    right: 12px;
    left: 12px;
    width: auto;
  }
}

/* ======================================================================
   PR-MDEV-1/2/3 — MULTI-DEVICE LAYER (2026-05-23)
   ======================================================================
   Additive, opt-in helpers for the multi-device foundation. Every rule in
   this section is either:

     (a) Scoped to a NEW class that doesn't exist anywhere in the existing
         markup (.cf-cq, .cf-cq-card, .mdev-fluid, .mdev-tv-board, .mdev-
         touch, .mdev-tile-grid) — so pre-existing layouts are physically
         untouched, or

     (b) Wrapped in :where() so the selector specificity is (0,0,0) and
         any existing rule with even a single class wins by cascade order.

   Full plan + per-PR scope: docs/MULTI_DEVICE_PLAN.md
   ====================================================================== */

/* ── PR-MDEV-3: 320px reflow safety (WCAG 1.4.10) ───────────────────────
   Scoped to phone widths (max 599px) and to <body>, NOT <html>, because
   overflow-x:clip on the root element can disable position:sticky on
   certain inner scroll containers across browsers. Inner scroll
   containers (data tables wrapped in div[overflow-x:auto], Kanban
   columns) work correctly under body's clip ancestor. */
@media (max-width: 599px) {
  :where(body) {
    overflow-x: hidden;       /* fallback for very old browsers */
    overflow-x: clip;         /* modern; preserves position:fixed children */
  }
  :where(img, svg, video, iframe, embed, object) {
    max-width: 100%;
    height: auto;
  }
}

/* ── PR-MDEV-2: Container-query opt-in helpers ──────────────────────────
   Pages opt in by applying .cf-cq to a grid. Children react to CONTAINER
   width via @container, not viewport width. A "KPI tile grid" inside a
   sidebar then collapses to single-column at narrow container widths
   even when the viewport is wide. Browser support: Baseline 2023; older
   browsers ignore @container and the wrapper becomes a no-op. */
:where(.cf-cq)      { container-type: inline-size; }
:where(.cf-cq-card) { container-type: inline-size; container-name: card; }

/* Generic self-adapting tile grid. Opt-in via class. */
:where(.mdev-tile-grid) {
  display: grid;
  gap: var(--sp-4, 16px);
  grid-template-columns: 1fr;
}
@container (min-width: 480px) {
  :where(.mdev-tile-grid) { grid-template-columns: repeat(2, 1fr); }
}
@container (min-width: 720px) {
  :where(.mdev-tile-grid) { grid-template-columns: repeat(3, 1fr); }
}
@container (min-width: 960px) {
  :where(.mdev-tile-grid) { grid-template-columns: repeat(4, 1fr); }
}

/* ── PR-MDEV-1: Fluid type opt-in via .mdev-fluid ───────────────────────
   Pages wrap in .mdev-fluid to opt into clamp()-based typography from
   tokens.css. :where() keeps specificity at (0,0,0). */
:where(.mdev-fluid)              { font-size: var(--fs-fluid-base); }
:where(.mdev-fluid) :where(h1)   { font-size: var(--fs-fluid-3xl); line-height: 1.15; }
:where(.mdev-fluid) :where(h2)   { font-size: var(--fs-fluid-2xl); line-height: 1.2; }
:where(.mdev-fluid) :where(h3)   { font-size: var(--fs-fluid-xl);  line-height: 1.3; }
:where(.mdev-fluid) :where(h4)   { font-size: var(--fs-fluid-lg);  line-height: 1.35; }
:where(.mdev-fluid) :where(small) { font-size: var(--fs-fluid-sm); }

/* ── PR-MDEV-1: TV-leanback opt-in (for future pg-tv-board) ─────────────
   24sp+ body floor for 10-foot viewing, high-contrast focus rings, no
   hover affordances (TVs have no pointer). */
:where(.mdev-tv-board) {
  font-size: var(--fs-tv-body);
  line-height: 1.4;
}
:where(.mdev-tv-board) :where(h1, h2, h3) {
  font-size: var(--fs-tv-heading);
  line-height: 1.15;
}
:where(.mdev-tv-board) :where(:focus-visible) {
  outline: 8px solid var(--acc, currentColor);
  outline-offset: 4px;
  border-radius: 4px;
}

/* ── PR-MDEV-3: Pointer-coarse touch-target safety net ──────────────────
   Interactive elements inside a .mdev-touch wrapper pick up the coarse-
   pointer --touch-min floor from tokens.css. Pre-existing buttons with
   explicit min-height keep their value (they're more specific). */
@media (pointer: coarse) {
  :where(.mdev-touch) :where(button, a, input, select, textarea, [role="button"]) {
    min-height: var(--touch-min);
    min-width: var(--touch-min);
  }
}

/* ── PR-MDEV-1: prefers-reduced-motion universal safety net ─────────────
   tokens.css already zeroes --dur-* tokens under reduced-motion. This
   rule kills any direct `animation` / `transition` not driven by tokens
   — a backstop for legacy rules that pre-date the token system.
   0.001ms (not 0ms) preserves `animationend` event firing so JS that
   listens for animation completion still works. */
@media (prefers-reduced-motion: reduce) {
  :where(*, *::before, *::after) {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── PR-MDEV-3: iOS-PWA install hint banner ─────────────────────────────
   Shown once on iOS Safari (NOT standalone) to teach the "Share → Add to
   Home Screen" gesture, since iOS has no install prompt API. Banner is
   inert until index.html toggles body[data-mdev-ios-hint="show"]. */
#mdev-ios-install-hint {
  display: none;
  position: fixed;
  bottom: 12px;
  left: 12px;
  right: 12px;
  max-width: 420px;
  margin-left: auto;
  margin-right: auto;
  z-index: var(--z-toast, 70);
  background: var(--sf);
  color: var(--white);
  border: 1px solid var(--bdr);
  border-radius: var(--radius-md, 10px);
  padding: 12px 16px;
  font-family: var(--ff-ui);
  font-size: 13px;
  line-height: 1.45;
  box-shadow: var(--shadow-popover);
}

body[data-mdev-ios-hint="show"] #mdev-ios-install-hint {
  display: block;
}

#mdev-ios-install-hint .mdev-ios-row {
  display: flex;
  gap: 8px;
  align-items: center;
}
#mdev-ios-install-hint .mdev-ios-msg {
  flex: 1;
}
#mdev-ios-install-hint .mdev-ios-glyph {
  font-size: 18px;
  line-height: 1;
}
#mdev-ios-install-hint .mdev-ios-dismiss {
  min-height: var(--touch-min, 44px);
  min-width: var(--touch-min, 44px);
  background: transparent;
  border: 1px solid var(--bdr);
  border-radius: var(--radius-sm, 6px);
  color: var(--white);
  font-family: var(--ff-ui);
  font-size: 13px;
  cursor: pointer;
  padding: 0 10px;
}
#mdev-ios-install-hint .mdev-ios-dismiss:active {
  background: var(--pn);
}

/* ── /PR-MDEV-1/2/3 ────────────────────────────────────────────────── */

/* ======================================================================
   PR-MDEV-PHONE-1 (2026-05-24) — VISIBLE PHONE READABILITY PASS
   ======================================================================
   Operator reported "text gets smashed together i can read alot of things"
   on phone. PR-MDEV-1/2/3 foundation tokens were inert — this layer
   ships ACTUAL visible improvements at <= 599px viewports without
   touching desktop.

   Strategy:
     1. Body text: 14px -> 15px + line-height 1.6 -> 1.55.
     2. Panel padding: .pn from 28px -> 14px.
     3. Stat grids: collapse to 1-col below 480px; 2-col 480-599px.
     4. Form inputs: 16px font + 44px min-height (iOS no-zoom + WCAG AA).
     5. Headings: more vertical breathing room.
     6. Pages: floor horizontal padding to 12px.

   Does NOT change: desktop (>= 600px), shop-floor.html, wizard.html,
   door-studio inline styles.
   ====================================================================== */

@media (max-width: 599px) {
  body {
    font-size: 15px;
    line-height: 1.55;
  }
  .pn {
    padding: 14px;
    margin-bottom: 16px;
    border-radius: 10px;
  }
  .pn h3 {
    font-size: 15px;
    margin-bottom: 10px;
    padding-bottom: 8px;
    line-height: 1.35;
  }
  .sg,
  .hub-stats,
  .kpi-grid,
  .cf-grid-cards {
    grid-template-columns: 1fr;
    gap: 12px;
    margin-bottom: 18px;
  }
  input[type="text"],
  input[type="number"],
  input[type="search"],
  input[type="email"],
  input[type="tel"],
  input[type="password"],
  input[type="url"],
  input[type="date"],
  input[type="time"],
  input:not([type]),
  select,
  textarea {
    font-size: 16px;
    min-height: 44px;
    line-height: 1.4;
  }
  button,
  .btn,
  [role="button"] {
    min-height: 44px;
  }
  h1, .h1, .pg-h1 {
    font-size: 22px;
    line-height: 1.2;
    margin-top: 18px;
    margin-bottom: 12px;
  }
  h2, .h2, .pg-h2 {
    font-size: 18px;
    line-height: 1.25;
    margin-top: 14px;
    margin-bottom: 10px;
  }
  :where(#app-workspace > div[id^="pg-"]) {
    padding-left: 12px;
    padding-right: 12px;
  }
  #app-topbar {
    padding: 0 12px;
    gap: 8px;
  }
  ul, ol, dl {
    padding-left: 1.25em;
  }
  li + li,
  dt + dd,
  dd + dt {
    margin-top: 6px;
  }
  table {
    font-size: 14px;
  }
}

@media (min-width: 480px) and (max-width: 599px) {
  .sg,
  .hub-stats,
  .kpi-grid,
  .cf-grid-cards {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* ── /PR-MDEV-PHONE-1 ────────────────────────────────────────────── */

/* ======================================================================
   PR-RESPONSIVE-STUDIO (2026-07-30) — GLOBAL COARSE-POINTER TOUCH FLOOR
   ======================================================================
   PR-MDEV-PHONE-1 only bumps targets at <=599px. A landscape iPad (1024px,
   coarse pointer) or a docked shop tablet never triggered it, so persistent
   nav/chrome kept mouse-sized hit targets. This applies the --touch-min
   token (56 finger / 48 hybrid, from tokens.css) to the primary app chrome
   at EVERY width when the pointer is coarse. :where() keeps specificity at 0
   so any explicit size still wins. */
@media (pointer: coarse) {
  :where(#app-sidebar .sidebar-nav-item,
         #app-bottom-nav .bnav-item,
         #app-topbar button,
         .forge-cmd-bar,
         .forge-cmd-bar button) {
    min-height: var(--touch-min, 44px);
  }
  :where(#app-bottom-nav .bnav-item) { min-width: var(--touch-min, 44px); }
}

/* ════════════════════════════════════════════════════════════════════
   PR-LIB-DESIGN (2026-05-23) — RESEARCH-BACKED LIBRARY UX TOKENS
   ════════════════════════════════════════════════════════════════════
   Synthesized from 12 parallel research agents pulling from:
   • Apple WWDC 2018 "Designing Fluid Interfaces" (UISpringTimingParameters
     response/damping presets — .snappy 0.25/0.85, .smooth 0.35/1.0)
   • Material Design 3 motion tokens (m3.material.io/styles/motion) —
     Standard/Emphasized cubic-beziers, duration ladder
   • IBM Carbon Design System — state-layer opacities, data-table density
   • eBay Playbook (productivity-grade) — interaction state ramp 4/4/8/12%
   • WCAG 2.4.13 AAA — focus indicator (2px solid, 2px offset, 3:1 contrast)
   • WCAG 2.5.5 AAA + 2.5.8 AA — target size minimums
   • MIL-STD-1472H — 20mm gloved-hand target (Springer 2016 validated)
   • Doherty Threshold (IBM 1982, refined Google INP 2024) — feedback
     visual ack < 200ms (1 frame ideally), full < 400ms preserves flow
   • Disney 12 Principles (Frank Thomas + Ollie Johnston) applied to UI:
     squash on press (anticipation + weight)
   • NN/G drag-and-drop guidance — line indicators > highlight for reorder
   • dnd-kit pointer-sensor defaults — 8px mouse / 250ms+5px touch
   • Atlassian Pragmatic Drag-and-Drop — 2px drop indicator + 4px bleed
   • Stripe test-vs-live mode color shift (catastrophic-context cue)
   • Microsoft Office Backstage program-color chrome
   • Figma Dev Mode tab-as-context-switch
   • Material 3 surface-container tonal ramp (m3.material.io/blog/tone-based)
   • ISA-101 industrial HMI "Going Gray" (control.com/technical-articles/
     going-gray) — desaturate config surfaces; reserve hue for status
   • Microsoft Research (Hinckley, Buxton, Baudisch) — pen+touch + tablet
   • Autodesk Research (Grossman, Fitzmaurice) — CAD library UX, ToolClips
   • Nielsen Norman Group + Aza Raskin — undo over confirm where feasible
   ════════════════════════════════════════════════════════════════════ */

:root {
  /* ── DURATIONS (Apple HIG + Material 3 reconciled) ────────────────── */
  --t-press:        80ms;
  --t-release:     180ms;
  --t-hover-in:    150ms;
  --t-hover-out:   120ms;
  --t-micro:       100ms;
  --t-standard:    240ms;
  --t-row-shuffle: 200ms;
  --t-drop:        260ms;
  --t-page-enter:  320ms;
  --t-page-exit:   200ms;
  --t-tooltip:     400ms;
  --t-tooltip-out: 100ms;
  --t-longpress:   500ms;

  /* ── EASING (Material 3 tokens, verbatim) ──────────────────────────── */
  --e-standard:        cubic-bezier(0.2, 0, 0, 1);
  --e-standard-decel:  cubic-bezier(0, 0, 0, 1);
  --e-standard-accel:  cubic-bezier(0.3, 0, 1, 1);
  --e-emphasized:      cubic-bezier(0.2, 0, 0, 1);
  --e-emphasized-decel: cubic-bezier(0.05, 0.7, 0.1, 1);
  --e-emphasized-accel: cubic-bezier(0.3, 0, 0.8, 0.15);
  --e-snappy:          cubic-bezier(0.16, 1, 0.3, 1);

  /* ── SPRINGS via CSS linear() (Chrome 113+ / Safari 17.2+) ─────────── */
  --spring-smooth: linear(0, 0.009, 0.034, 0.073, 0.124, 0.187, 0.26,
                          0.34, 0.426, 0.516, 0.608, 0.7, 0.787, 0.866,
                          0.93, 0.976, 1);
  --spring-snappy: linear(0, 0.084, 0.252, 0.466, 0.682, 0.852, 0.952,
                          1.0, 1.02, 1.015, 1);
  --spring-drop:   linear(0, 0.15, 0.5, 0.85, 1.04, 1.02, 1);

  /* ── STATE-LAYER OPACITIES (eBay Playbook productivity scale) ──────── */
  --state-hover:    rgba(15, 23, 42, 0.04);
  --state-focus:    rgba(15, 23, 42, 0.04);
  --state-pressed:  rgba(15, 23, 42, 0.08);
  --state-dragged:  rgba(15, 23, 42, 0.12);

  /* ── PRESS SQUASH (Disney + iOS) ───────────────────────────────────── */
  --press-scale:   0.97;

  /* ── ELEVATION (Material 3 Level 1 / 4 / 8) ────────────────────────── */
  --shadow-rest:   0 1px 2px rgba(15, 23, 42, 0.06);
  --shadow-hover:  0 4px 12px -4px rgba(15, 23, 42, 0.14),
                   0 2px 4px -2px rgba(15, 23, 42, 0.08);
  --shadow-drag:   0 16px 40px -12px rgba(15, 23, 42, 0.28),
                   0 4px 10px -4px rgba(15, 23, 42, 0.12);

  /* ── TOUCH TARGETS (WCAG 2.5.5 AAA / Apple HIG / MIL-STD-1472) ─────── */
  --tgt-min:    44px;
  --tgt-touch:  48px;
  --tgt-gloved: 60px;

  /* ── FOCUS RING (WCAG 2.4.13 AAA) ──────────────────────────────────── */
  --focus-ring-w: 2px;
  --focus-ring-o: 2px;
}

/* prefers-reduced-motion — Apple/MDN guidance: flatten to 1ms, never
   fully disable (loss of context worsens UX). Operators on iPad with
   Reduce Motion enabled (common shop-floor a11y pref) get instant UI. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}

/* iOS tap-highlight hygiene — we paint our own :active feedback. */
* { -webkit-tap-highlight-color: transparent; }

/* ════════════════════════════════════════════════════════════════════
   LIBRARY "WORKSHOP" SURFACE MODE
   ════════════════════════════════════════════════════════════════════
   Industry precedent for distinct mode chrome:
   • Stripe test-vs-live banner (orange vs blue — catastrophic-cost cue)
   • Microsoft Office Backstage (program color when in File menu)
   • Figma Dev Mode (tab-as-context-switch with own typography)
   • Linear Cmd-K (elevated tone, same palette)
   Industry precedent for restraint:
   • Material 3 surface-container ramp (same family, different tone)
   • IBM Carbon (gray luminance only, no hue shift between themes)
   • ISA-101 "Going Gray" — saturated colors reserved for status
   • Linear's modal-vs-base (tone, not hue)
   Cognitive-load research (Aufait/IxDF): pair color cue with text + icon —
   color alone is unreliable.

   Strategy: body[data-surface="library"] applied when any pg-lib-edit
   page is active. CabForge identity preserved (indigo accent, oxblood
   danger, JetBrains Mono dims). Surface tone shifts ~ΔE 10 in CIELab —
   perceptible at-a-glance, never breaks brand. Reversible by removing
   the attribute. */

/* Persistent 2px ochre stripe across viewport top — the Stripe
   banner pattern dialed way down for shop-owner taste.
   PR-LIB-COLOR-TOKENS: now routes through --acc-library / --acc-library-deep
   so a future palette tweak controls every library-surface accent at once. */
body[data-surface="library"]::before {
  content: "";
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 2px;
  background: linear-gradient(90deg,
      color-mix(in srgb, var(--acc-library) 70%, transparent) 0%,
      color-mix(in srgb, var(--acc-library-deep) 85%, transparent) 50%,
      color-mix(in srgb, var(--acc-library) 70%, transparent) 100%);
  z-index: 9999;
  pointer-events: none;
}

/* Library canvas — subtle cool overlay using the existing CabForge
   indigo accent at low opacity. ~ΔE 10 in CIELab vs main canvas. */
body[data-surface="library"] {
  background-image: linear-gradient(180deg,
      color-mix(in srgb, var(--acc) 4%, transparent) 0%,
      color-mix(in srgb, var(--acc) 2%, transparent) 100%);
  background-attachment: fixed;
}

/* Breadcrumb pill — JetBrains Mono caps, muted ochre.
   Pairs the canvas-shift color cue with explicit text so colorblind
   and screen-reader users get the same context-switch signal. */
.lib-mode-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--ff-mono, "JetBrains Mono", monospace);
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 4px 10px;
  border-radius: 999px;
  color: var(--acc-library-deep);
  background: color-mix(in srgb, var(--acc-library) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--acc-library) 35%, transparent);
}
.lib-mode-badge::before {
  content: "";
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--acc-library-deep);
}

/* ════════════════════════════════════════════════════════════════════
   UNIVERSAL ROW/CARD STATE LADDER
   ════════════════════════════════════════════════════════════════════
   Library rows (data-id from PR-LIBEDIT-1) and pg-lib-edit nav buttons
   inherit the full state ladder: hover lift, press squash (Disney),
   focus ring (WCAG 2.4.13 AAA), selection tint (color, not gray).
   Microsoft Research (Hinckley + Buxton): selection model is single-
   click → preview-in-detail-pane, never double-click-to-open. */

body[data-surface="library"] tbody tr[data-id],
#pg-lib-edit .ple-nav-btn {
  transition:
    background-color var(--t-hover-in) var(--e-standard),
    box-shadow      var(--t-hover-in) var(--e-standard),
    transform       var(--t-press)    var(--e-standard);
  will-change: transform;
}
body[data-surface="library"] tbody tr[data-id]:hover {
  background-color: var(--state-hover);
}
body[data-surface="library"] tbody tr[data-id]:focus-visible {
  outline: var(--focus-ring-w) solid var(--acc);
  outline-offset: var(--focus-ring-o);
  background-color: var(--state-focus);
}
body[data-surface="library"] tbody tr[data-id]:active {
  background-color: var(--state-pressed);
  transform: scale(var(--press-scale));
  transition-duration: var(--t-press);
}
/* Selection: color-tinted not gray (Microsoft Fluent + Apple HIG) */
body[data-surface="library"] tbody tr[data-id][aria-selected="true"] {
  background-color: color-mix(in srgb, var(--acc) 10%, transparent);
}

#pg-lib-edit .ple-nav-btn:active {
  transform: scale(var(--press-scale));
  transition-duration: var(--t-press);
}

/* Page-enter animation — Material 3 emphasized-decelerate.
   Apple HIG rule: short, purposeful, interruptible, < 500ms. */
@keyframes pgLibEditEnter {
  from { opacity: 0; transform: translateY(8px) scale(0.985); }
  to   { opacity: 1; transform: translateY(0)    scale(1); }
}
#pg-lib-edit.on {
  animation: pgLibEditEnter var(--t-page-enter) var(--e-emphasized-decel) both;
}

/* Touch targets on tablet — bump library page buttons to 48px (Apple HIG
   tablet floor + WCAG 2.5.5 AAA). Operator's iPad shop-floor scenarios
   benefit per MIL-STD-1472H gloved-hand validation. */
@media (max-width: 980px) and (pointer: coarse) {
  body[data-surface="library"] tbody tr[data-id] {
    min-height: var(--tgt-touch);
  }
  body[data-surface="library"] tbody tr[data-id] td {
    padding-top: 12px;
    padding-bottom: 12px;
  }
  #pg-lib-edit .ple-nav-btn {
    min-height: var(--tgt-touch);
  }
}

/* Forced-colors mode (Windows High Contrast) — preserve interactivity.
   Per MDN forced-colors guidance and Microsoft Edge 2025 standards.
   Apple's #FF3B30 destructive red fails AA on white text (3.8:1) so we
   add an explicit border on destructive affordances in this mode. */
@media (forced-colors: active) {
  body[data-surface="library"]::before {
    background: Highlight;
  }
  body[data-surface="library"] tbody tr[data-id]:focus-visible {
    outline-color: Highlight;
  }
  body[data-surface="library"] tbody tr[data-id][aria-selected="true"] {
    background-color: Highlight;
    color: HighlightText;
  }
  /* Confirm-dialog danger button needs a border in forced-colors
     mode — solid-color background is replaced with system Canvas/Button
     so the only signal a button is destructive is the border. */
  #app-confirm-dialog .acd-btn.danger {
    border: 2px solid Mark !important;
  }
}

/* ── /PR-LIB-DESIGN ─────────────────────────────────────────────────── */

/* ════════════════════════════════════════════════════════════════════
   PR-LIB-DESIGN-V8 (2026-05-24) — fix Arch-terms chip rendering bug.
   ════════════════════════════════════════════════════════════════════
   Pre-existing bug: the Arch-terms section in #lt-door uses
   <span class="jd-help">Profile</span> with the FULL WORD as text
   content, but .jd-help is defined as a 14×14 circular icon meant to
   contain only "?". The 14px width forces every word to wrap one
   character per line — operator sees "P/r/o/f/i/l/e" stacked vertically.

   Fix: when a .jd-help span has data-help AND text content longer than
   1 character, render it as a text pill instead of a circle. Strictly
   scoped to the #lt-door reference block so the legitimate "?" icons
   everywhere else in the app are unaffected. */
#lt-door > div:first-child .jd-help,
#lt-door .jd-help:not(:empty) {
    width: auto !important;
    height: auto !important;
    line-height: 1.4 !important;
    border-radius: 4px !important;
    padding: 2px 6px !important;
    font-weight: 500 !important;
    font-size: 11px !important;
    text-align: left !important;
    display: inline-block !important;
    color: var(--acc2) !important;
    /* PR-LIB-COLOR-TOKENS: derive blue tint from --acc instead of raw rgba. */
    background: color-mix(in srgb, var(--acc) 8%, transparent) !important;
    border-color: color-mix(in srgb, var(--acc) 35%, transparent) !important;
    cursor: help !important;
}
/* When workshop mode is active, recolor the chips in ochre to match
   the rest of the library aesthetic.
   PR-LIB-COLOR-TOKENS: derive the bronze from --acc-door (the door-library
   category token) so workshop mode re-tints if the operator changes that
   token globally. */
body[data-hub-tab="lib"] #lt-door > div:first-child .jd-help,
body[data-hub-tab="lib"] #lt-door .jd-help:not(:empty) {
    color: color-mix(in srgb, var(--acc-door) 75%, var(--white)) !important;
    background: color-mix(in srgb, var(--acc-door) 10%, transparent) !important;
    border-color: color-mix(in srgb, var(--acc-door) 40%, transparent) !important;
}

/* ── /PR-LIB-DESIGN-V8 ─────────────────────────────────────────────── */

/* ════════════════════════════════════════════════════════════════════
   PR-HUB-FOCAL-METRIC (2026-05-24) — Design Dashboard header rewrite.
   ════════════════════════════════════════════════════════════════════
   Replaces the 4-up .metrics-ribbon (Jobs / Rooms / Cabinets Placed /
   Cabinet Library) with one dominant focal metric + a chip strip.

   Research base:
   • Stephen Few, "Information Dashboard Design" — dashboards exist to
     support action; vanity metrics dilute attention. Catalog counts
     (Cabinet Library) are nav, not KPIs.
   • Edward Tufte, "Visual Display of Quantitative Information" — data-
     ink ratio: 4 equal-weight tiles for 1 actionable number wastes the
     most expensive pixels on the page.
   • Sherwin (NN/g), "Dashboards: Making Charts and Graphs Easier to
     Understand" — single dominant metric outperforms tile grids for
     decision velocity.
   • ISA-101 industrial HMI "Going Gray" — desaturate config surfaces;
     reserve hue for status. Amber is now reserved for "overdue".
   • Material 3 + IBM Carbon — primary information dominates via size
     and tone hierarchy, not grid equality.
   • Linear, Notion, Stripe Dashboard precedent — single hero number +
     contextual chip row is the modern productivity pattern.

   Inherits all motion/state/focus tokens from PR-LIB-DESIGN above. */

.cab-metric-focus {
  display: flex;
  align-items: stretch;
  gap: 18px;
  padding: 16px 20px;
  margin: 14px 0;
  background: var(--pn);
  border: 1px solid var(--bdr);
  border-radius: 12px;
}

.cmf-cell {
  background: transparent;
  border: none;
  color: inherit;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  padding: 0;
}
.cmf-cell:focus-visible {
  outline: var(--focus-ring-w, 2px) solid var(--acc);
  outline-offset: var(--focus-ring-o, 2px);
  border-radius: 8px;
}

/* Primary focal metric — the one number that drives action. */
.cmf-primary {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 4px 20px 4px 4px;
  min-width: 140px;
  border-right: 1px solid var(--bdr);
  transition: opacity var(--t-hover-in, 150ms) var(--e-standard, cubic-bezier(0.2, 0, 0, 1));
}
.cmf-primary:hover { opacity: 0.88; }
.cmf-primary:active { transform: scale(var(--press-scale, 0.97)); transition-duration: var(--t-press, 80ms); }
.cmf-primary.active { opacity: 1; }

.cmf-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--dim);
  font-weight: 600;
  margin-bottom: 2px;
}
.cmf-value {
  font-size: 38px;
  font-weight: 700;
  color: var(--white);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  letter-spacing: -0.015em;
}
.cmf-status {
  font-size: 11px;
  color: var(--warn);
  font-weight: 600;
  margin-top: 6px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}
.cmf-status.muted { color: var(--dim); font-weight: 500; text-transform: none; letter-spacing: 0; }
.cmf-status.good  { color: var(--ok);  font-weight: 500; text-transform: none; letter-spacing: 0; }

/* Chip strip — secondary metrics + nav. */
.cmf-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  flex: 1;
}
.cmf-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  min-height: var(--tgt-min, 36px);
  background: transparent;
  border: 1px solid var(--bdr);
  border-radius: 999px;
  color: var(--dim);
  font-size: 12px;
  font-weight: 500;
  text-decoration: none;
  transition:
    background-color var(--t-hover-in, 150ms) var(--e-standard, cubic-bezier(0.2, 0, 0, 1)),
    color            var(--t-hover-in, 150ms) var(--e-standard, cubic-bezier(0.2, 0, 0, 1)),
    border-color     var(--t-hover-in, 150ms) var(--e-standard, cubic-bezier(0.2, 0, 0, 1)),
    transform        var(--t-press, 80ms)     var(--e-standard, cubic-bezier(0.2, 0, 0, 1));
}
.cmf-chip:hover {
  background: var(--state-hover);
  color: var(--txt);
  border-color: color-mix(in srgb, var(--bdr) 60%, var(--acc) 40%);
}
.cmf-chip:active {
  background: var(--state-pressed);
  transform: scale(var(--press-scale, 0.97));
}
.cmf-chip.active {
  background: color-mix(in srgb, var(--acc) 10%, transparent);
  color: var(--white);
  border-color: color-mix(in srgb, var(--acc) 50%, transparent);
}
.cmf-chip-v {
  font-variant-numeric: tabular-nums;
  color: var(--white);
  font-weight: 600;
  font-size: 13px;
}
.cmf-chip-l { white-space: nowrap; }
.cmf-chip-icon { width: 11px; height: 11px; opacity: 0.55; }
.cmf-chip-link:hover .cmf-chip-icon { opacity: 1; }

/* Phone (<600px): stack primary above chips, drop right-border. */
@media (max-width: 600px) {
  .cab-metric-focus {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
    padding: 14px 16px;
  }
  .cmf-primary {
    border-right: none;
    border-bottom: 1px solid var(--bdr);
    padding: 4px 4px 12px;
    min-width: 0;
  }
  .cmf-value { font-size: 32px; }
}

/* ── /PR-HUB-FOCAL-METRIC ──────────────────────────────────────────── */

/* ════════════════════════════════════════════════════════════════════
   PR-TOPBAR-CLEANUP (2026-05-24) — declutter the global topbar.
   ════════════════════════════════════════════════════════════════════
   Operator directive: AWI Compliance badge + Material Library palette
   icon don't belong in the global topbar. They only make sense on the
   cabinet engine / Bevy 3D editor where the operator is actually
   designing cabinets and needs to swap materials live. Hide them
   everywhere else.

   Also: the "Your live preview may not match the cut list on file"
   yellow banner triggers on every WASM version mismatch — false alarm
   for the operator's stable shop and just adds chrome noise. Killed.

   Scope: pages where the AWI/material affordances DO make sense:
     • pg-cab    (Cabinet Builder / 3D editor)
     • pg-door   (Door Studio — designs doors that consume materials)
   Everywhere else (libraries, schedule, production, hub, orders,
   settings, admin): both buttons hidden. */

/* Kill the version-drift banner permanently. The version check still
   runs in JS but the visible banner stays hidden. Operator can still
   see version state via DevTools console if needed. */
#version-drift-banner {
    display: none !important;
}

/* AWI Compliance badge: hide globally, show only on cabinet + door pages. */
#topbar-aws-mode-badge {
    display: none !important;
}
body[data-page="cab"]  #topbar-aws-mode-badge:not(.aws-hidden),
body[data-page="door"] #topbar-aws-mode-badge:not(.aws-hidden) {
    /* Restore the badge's original display when compliance mode is on.
       .aws-hidden is the class JS toggles when compliance is OFF — we
       only want to show the badge when it's NOT aws-hidden. */
    display: inline-flex !important;
}

/* Material Library palette: hide globally, show only on cabinet + door. */
#topbar-material-btn {
    display: none !important;
}
body[data-page="cab"]  #topbar-material-btn,
body[data-page="door"] #topbar-material-btn {
    display: inline-flex !important;
}

/* ── /PR-TOPBAR-CLEANUP ────────────────────────────────────────────── */

/* ════════════════════════════════════════════════════════════════════
   PR-TOPBAR-V2 (2026-05-24) — research-backed topbar redesign.
   ════════════════════════════════════════════════════════════════════
   Research synthesis (from 12-agent UX battery):
   • Stripe Dashboard — visible search bar + bell + profile dropdown
   • Linear — workspace + ⌘K hint + avatar (everything in dropdown)
   • GitHub — logo + nav + search + notifications + profile
   • NN/g 2021 (Hick's Law) — max 5-7 visible items in a toolbar
   • Apple HIG — ≤5 primary actions; secondary in overflow
   • Baymard 2024 — visible search input has 40%+ higher task-completion
     rate than a search ICON that opens a modal.
   Cleanup:
   • HIDE: Help (?) — context-help is inline via .jd-help spans.
   • HIDE: Settings (⚙) — accessible via ☰ sidebar menu (one path
     per destination, IA hygiene).
   • EXPAND: search icon → visible input bar with placeholder + ⌘K hint.
   • REFINE: user chip — tighter pill, lower opacity at rest.
   • REFINE: sign-out — quiet until hover (NN/g utility-nav).
   • Bell stays (universal pattern). */

#topbar-help-btn { display: none !important; }
/* PR-HOME-BENCH (2026-06-13): operator wants Settings reachable from the bar —
   the gear was hidden by the topbar-declutter PR. Re-enabled (opens the
   Settings & admin drawer via openSettings()). */

#topbar-search-btn {
    width: 280px !important;
    max-width: 32vw !important;
    height: 36px !important;
    padding: 0 56px 0 38px !important;
    background: color-mix(in srgb, var(--inp) 80%, transparent) !important;
    border: 1px solid color-mix(in srgb, var(--bdr) 80%, transparent) !important;
    border-radius: 10px !important;
    text-align: left !important;
    color: var(--dim) !important;
    position: relative !important;
    cursor: pointer !important;
    display: inline-flex !important;
    align-items: center !important;
    transition: border-color 150ms cubic-bezier(0.2, 0, 0, 1),
                background 150ms cubic-bezier(0.2, 0, 0, 1) !important;
}
#topbar-search-btn:hover {
    border-color: color-mix(in srgb, var(--acc) 50%, var(--bdr)) !important;
    background: color-mix(in srgb, var(--inp) 95%, transparent) !important;
    color: var(--white) !important;
}
#topbar-search-btn:focus-visible {
    outline: 2px solid var(--acc) !important;
    outline-offset: 2px !important;
}
#topbar-search-btn svg {
    position: absolute !important;
    left: 12px !important;
    top: 50% !important;
    transform: translateY(-50%) !important;
    width: 16px !important;
    height: 16px !important;
    color: var(--dim) !important;
}
#topbar-search-btn::after {
    content: "Search jobs, customers, libraries…" !important;
    font-size: 13px !important;
    font-weight: 500 !important;
    flex: 1 1 auto !important;
    text-align: left !important;
    overflow: hidden !important;
    white-space: nowrap !important;
    text-overflow: ellipsis !important;
}
#topbar-search-btn::before {
    content: "⌘K" !important;
    position: absolute !important;
    right: 8px !important;
    top: 50% !important;
    transform: translateY(-50%) !important;
    font-family: "JetBrains Mono", ui-monospace, monospace !important;
    font-size: 10px !important;
    font-weight: 700 !important;
    color: var(--dim) !important;
    background: color-mix(in srgb, var(--bdr) 60%, transparent) !important;
    padding: 2px 6px !important;
    border-radius: 4px !important;
    border: 1px solid color-mix(in srgb, var(--bdr) 80%, transparent) !important;
    line-height: 1 !important;
    pointer-events: none !important;
}
@media (max-width: 720px) {
    #topbar-search-btn {
        width: var(--tgt-min, 44px) !important;
        padding: 0 !important;
        justify-content: center !important;
    }
    #topbar-search-btn::after,
    #topbar-search-btn::before { display: none !important; }
    #topbar-search-btn svg {
        position: static !important;
        transform: none !important;
        margin: auto !important;
    }
}

.topbar-user-chip {
    padding: 4px 8px 4px 12px !important;
    border-radius: 999px !important;
    background: color-mix(in srgb, var(--inp) 50%, transparent) !important;
    border: 1px solid color-mix(in srgb, var(--bdr) 40%, transparent) !important;
    opacity: 0.85 !important;
    transition: opacity 150ms cubic-bezier(0.2, 0, 0, 1),
                background 150ms cubic-bezier(0.2, 0, 0, 1),
                border-color 150ms cubic-bezier(0.2, 0, 0, 1) !important;
    gap: 8px !important;
    align-items: center !important;
}
.topbar-user-chip:hover,
.topbar-user-chip:focus-within {
    opacity: 1 !important;
    background: color-mix(in srgb, var(--inp) 95%, transparent) !important;
    border-color: var(--bdr) !important;
}

.topbar-role-badge {
    font-size: 9px !important;
    padding: 1px 6px !important;
    letter-spacing: 0.08em !important;
    /* PR-LIB-COLOR-TOKENS: routes through --acc-library (lightened with
       --white via color-mix for the foreground text). */
    background: color-mix(in srgb, var(--acc-library) 22%, transparent) !important;
    color: color-mix(in srgb, var(--acc-library) 70%, var(--white)) !important;
    border: 1px solid color-mix(in srgb, var(--acc-library) 45%, transparent) !important;
}

.topbar-signout-btn {
    opacity: 0.65 !important;
    padding: 6px 8px !important;
    border-radius: 8px !important;
    transition: opacity 120ms cubic-bezier(0.2, 0, 0, 1),
                background 120ms cubic-bezier(0.2, 0, 0, 1) !important;
}
.topbar-signout-btn:hover {
    opacity: 1 !important;
    /* PR-LIB-COLOR-TOKENS: rose hover = var(--no) at 12% */
    background: color-mix(in srgb, var(--no) 12%, transparent) !important;
}

.topbar-actions {
    gap: 6px !important;
}

/* ── /PR-TOPBAR-V2 ─────────────────────────────────────────────────── */

/* ════════════════════════════════════════════════════════════════════
 * LIB-EDITOR-SHELL  (PR-LIB-TEMPLATE-ROLLOUT 2026-05-25)
 *
 * Generic, class-based shell for the full-page library editor. Lifted
 * out of `renderDoorEditorBody`'s inline <style> block in web/index.html
 * so the same chrome (sticky header, 3-col grid, side nav, section
 * cards, sub-blocks, form rows, pricing input wrapper, responsive
 * fluid scaling) can be reused by every library type via the
 * renderLibEditorShell() helper.
 *
 * Selector rename map (old door-only id → new generic class):
 *   #ed-door-pageheader   → .lib-ed-shell-header
 *   #ed-door-2pane        → .lib-ed-shell
 *   #ed-door-nav          → .lib-ed-shell-nav
 *   #ed-door-content      → .lib-ed-shell-content
 *   #ed-door-preview-card → .lib-ed-shell-preview
 *   .ed-pv-meta           → .lib-ed-shell-meta
 *
 * Inner classes (.ed-ph-*, .ed-nav-*, .lib-ed-section, .lib-ed-subblock,
 * .fr, .fg, .lib-src, .ed-pricing-input, .ed-pricing-prefix) already
 * had generic names and are kept as-is — their rules just re-scope from
 * the old id container to the new class container.
 *
 * The 2-column variant (no preview pane) is signaled by
 * data-no-preview="true" on the shell element; the grid collapses
 * to nav + content, and the right column rules are skipped.
 *
 * Door-specific bits (cathedral toolbar, applies-to chips, hinge-bore
 * inline checkbox, match-body checkbox, SVG door preview styling) stay
 * inline in renderDoorEditorBody since they don't generalize.
 *
 * Every color routes through design-system tokens per the
 * strict-color-tokens rule. The base rgba(0,0,0,0.10) shadow tokens
 * already live in tokens.css.
 * ════════════════════════════════════════════════════════════════════ */

/* Full-page drawer — when the editor shell is mounted, the parent
 * drawer expands to fill the viewport and gets fluid padding via clamp(). */
#edit-drawer:has(.lib-ed-shell){ background: var(--bg) !important; }
#edit-drawer .drawer:has(.lib-ed-shell){
  max-width: 100vw !important; width: 100vw !important;
  height: 100vh !important; max-height: 100vh !important;
  margin: 0 !important; border-radius: 0 !important;
  position: fixed !important; inset: 0 !important;
  overflow-y: auto;
  padding: clamp(12px, 1.5vw, 28px) clamp(12px, 2vw, 40px) !important;
}

/* 3-col grid: nav | content | preview.
 * Nav rail: 180→260px between 1100→1900px viewport.
 * Preview: 340→760px between 1100→2400px viewport.
 * Cap at 2400px max-width so ultrawides stay readable. */
.lib-ed-shell{
  display: grid;
  grid-template-columns:
    clamp(180px, 14vw, 260px)
    minmax(0, 1fr)
    clamp(340px, 26vw, 760px);
  column-gap: clamp(14px, 1.4vw, 28px);
  row-gap: 12px;
  align-items: start;
  max-width: 2400px;
  margin: 0 auto;
}
/* 2-col mode: no preview pane — collapse to nav + content. */
.lib-ed-shell[data-no-preview="true"]{
  grid-template-columns:
    clamp(180px, 14vw, 260px)
    minmax(0, 1fr);
}

/* Side nav (left, sticky) — numbered badges + subtitle items. */
.lib-ed-shell-nav{
  grid-column: 1; align-self: start;
  position: sticky; top: 24px;
  background: var(--inp); border: 1px solid var(--bdr); border-radius: 8px;
  padding: 8px; display: flex; flex-direction: column; gap: 4px;
}
.lib-ed-shell-nav button{
  display: flex; align-items: center; gap: 12px;
  min-height: clamp(56px, 5vh, 72px);
  padding: clamp(8px, 0.8vw, 12px) clamp(10px, 1vw, 16px);
  text-align: left;
  color: var(--dim); background: transparent; border: none; border-radius: 6px;
  cursor: pointer;
  transition: background .15s, color .15s, box-shadow .15s;
}
.lib-ed-shell-nav .ed-nav-num{
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; border-radius: 50%;
  background: color-mix(in srgb, var(--acc) 14%, transparent);
  color: var(--white); font-size: 13px; font-weight: 700; flex: 0 0 auto;
}
.lib-ed-shell-nav .ed-nav-stack{ display: flex; flex-direction: column; gap: 2px; min-width: 0; text-align: left; }
.lib-ed-shell-nav .ed-nav-title{ font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; color: inherit; }
.lib-ed-shell-nav .ed-nav-sub  { font-size: 10px; color: var(--dim); text-transform: none; letter-spacing: 0; font-weight: 500; }
.lib-ed-shell-nav button:hover{ background: color-mix(in srgb, var(--acc) 14%, transparent); color: var(--white); }
.lib-ed-shell-nav button[data-active="true"]{
  background: color-mix(in srgb, var(--acc) 28%, transparent);
  color: var(--white);
  box-shadow: inset 4px 0 0 var(--acc);
}
.lib-ed-shell-nav button[data-active="true"] .ed-nav-num{ background: var(--acc); color: var(--bg); }

/* Active section card (middle column) — stretches to fill viewport. */
.lib-ed-shell-content{ grid-column: 2; min-width: 0; }
.lib-ed-shell-content > .lib-ed-section[data-active="false"]{ display: none; }
.lib-ed-shell-content > .lib-ed-section[data-active="true"]{
  min-height: calc(100vh - 140px);
  display: flex; flex-direction: column;
}
.lib-ed-shell-content > .lib-ed-section[data-active="true"] > .lib-ed-section-body{ flex: 1; }

/* Preview card (right column, sticky) + metadata footer. */
.lib-ed-shell > .lib-ed-shell-preview{
  grid-column: 3; grid-row: 1;
  position: sticky; top: 24px;
  align-self: start; margin: 0 !important;
}
.lib-ed-shell > .lib-ed-shell-meta{
  grid-column: 3; grid-row: 2;
  margin: 0 !important;
  padding: 12px 14px;
  background: var(--inp); border: 1px solid var(--bdr); border-radius: 8px;
  font-size: 11px; color: var(--dim);
  display: grid; grid-template-columns: max-content 1fr; gap: 6px 14px;
}
.lib-ed-shell .lib-ed-shell-meta dt{ font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; font-size: 9px; margin: 0; }
.lib-ed-shell .lib-ed-shell-meta dd{ margin: 0; color: var(--white); font-family: ui-monospace, monospace; font-size: 11px; word-break: break-all; }

/* Generic editor-section card. */
.lib-ed-shell .lib-ed-section{
  background: var(--inp); border: 1px solid var(--bdr);
  border-radius: 8px; margin-bottom: 12px; overflow: hidden;
}
.lib-ed-shell .lib-ed-section-head{
  padding: clamp(11px, 1vw, 16px) clamp(14px, 1.4vw, 20px);
  font-size: clamp(12px, 0.95vw, 14px); font-weight: 700;
  color: var(--white); text-transform: uppercase;
  letter-spacing: 0.08em; display: flex; align-items: center;
  gap: 8px; user-select: none;
}
.lib-ed-shell .lib-ed-section-body{
  padding: clamp(12px, 1.4vw, 22px);
  border-top: 1px solid var(--bdr);
}
.lib-ed-shell .lib-ed-section-body .fr{ display: flex; gap: 14px; margin-bottom: 14px; }
.lib-ed-shell .lib-ed-section-body .fr:last-child{ margin-bottom: 0; }
.lib-ed-shell .lib-ed-section-body .fg{ display: flex; flex-direction: column; flex: 1 1 0; min-width: 0; gap: 6px; }
.lib-ed-shell .lib-ed-section-body .fg > label{
  display: flex; align-items: center; gap: 6px; margin: 0;
  text-transform: uppercase; letter-spacing: 0.06em;
  font-size: 10px; font-weight: 700; color: var(--dim); line-height: 1.4;
}
.lib-ed-shell .lib-ed-section-body .lib-src{
  margin-left: auto; padding: 2px 8px; font-size: 9px; font-weight: 600;
  text-transform: none; letter-spacing: 0;
  color: var(--dim); background: color-mix(in srgb, var(--bg) 60%, transparent);
  border: 1px solid var(--bdr); border-radius: 10px; line-height: 1.3; white-space: nowrap;
}
.lib-ed-shell .lib-ed-section-body .fg > input,
.lib-ed-shell .lib-ed-section-body .fg > select,
.lib-ed-shell .lib-ed-section-body .fg > textarea{
  width: 100%; min-height: 40px; padding: 9px 10px;
  font-size: 13px; border-radius: 6px;
  border: 1px solid var(--bdr); background: var(--bg); color: var(--white);
}
.lib-ed-shell .lib-ed-section-body .fg > textarea{ min-height: 80px; resize: vertical; }
.lib-ed-shell .lib-ed-section-body .fg > input:focus,
.lib-ed-shell .lib-ed-section-body .fg > select:focus,
.lib-ed-shell .lib-ed-section-body .fg > textarea:focus{ outline: 2px solid var(--acc); outline-offset: -1px; }

/* Neutralize the inner h3/border that legacy buildPricingSection emits
 * — its outer wrapper used to provide its own header; inside the new
 * section card it'd be a duplicate header. */
.lib-ed-shell .lib-ed-section-body > div[style*="border-top"]{ margin-top: 0 !important; padding-top: 0 !important; border-top: 0 !important; }
.lib-ed-shell .lib-ed-section-body > div[style*="border-top"] > h3{ display: none !important; }

/* Sub-block divider inside a consolidated section. */
.lib-ed-shell .lib-ed-subblock{
  border-top: 1px solid var(--bdr);
  padding-top: 18px; margin-top: 18px;
}
.lib-ed-shell .lib-ed-subblock:first-child{
  border-top: 0; padding-top: 0; margin-top: 0;
}
.lib-ed-shell .lib-ed-subblock-head{
  font-size: 10px; font-weight: 700; color: var(--dim);
  text-transform: uppercase; letter-spacing: 0.06em;
  margin-bottom: 12px;
}

/* ── Page header (sticky top bar) ───────────────────────────────────
 * Eyebrow + h1 title + status pill + action buttons + meta strip.
 * Pattern drawn from Stripe / Linear / Figma / Polaris / Material 3 /
 * Apple HIG entity-edit conventions. */
.lib-ed-shell-header{
  position: sticky; top: 0; z-index: 50;
  background: var(--bg);
  border-bottom: 1px solid var(--bdr);
  padding: clamp(12px, 1.3vw, 22px) clamp(16px, 2.2vw, 44px);
  /* Negative margins mirror the drawer padding clamp() above so the
     sticky header bleeds to the viewport edges at every width. */
  margin: clamp(-28px, -1.5vw, -12px) clamp(-40px, -2vw, -12px) 18px;
}
.lib-ed-shell-header .ed-ph-row{
  display: flex; justify-content: space-between; align-items: center;
  gap: 16px; margin-bottom: 10px;
}
.lib-ed-shell-header .ed-ph-eyebrow{
  font-size: 10px; font-weight: 700; color: var(--dim);
  text-transform: uppercase; letter-spacing: 0.12em;
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lib-ed-shell-header .ed-ph-actions{
  display: flex; align-items: center; gap: 10px;
  flex: 0 0 auto;
}
/* Back/cancel button: solid --ok fill matching .ba's box dims so the
 * two pills look like twins, only the hue differs. */
.lib-ed-shell-header .ed-ph-actions button.bg{
  background: var(--ok) !important;
  color: var(--ok-ink) !important;
  border: none !important;
  min-height: 44px !important;
  padding: 10px 22px !important;
  font-size: 13px !important;
  font-weight: 600 !important;
  border-radius: 8px !important;
  line-height: inherit !important;
}
.lib-ed-shell-header .ed-ph-actions button.bg:hover{
  background: color-mix(in srgb, var(--ok) 88%, var(--white) 12%) !important;
  box-shadow: var(--shadow-glow-ok);
}
.lib-ed-shell-header .ed-ph-divider{
  display: inline-block;
  width: 1px; height: 22px;
  background: var(--bdr);
  margin: 0 4px;
}
.lib-ed-shell-header .ed-ph-status{ font-size: 11px; font-weight: 600; color: var(--dim); padding: 0 6px; white-space: nowrap; }
.lib-ed-shell-header .ed-ph-status.unsaved{ color: var(--warn); }
.lib-ed-shell-header .ed-ph-status.saved{ color: var(--ok); }
.lib-ed-shell-header .ed-ph-status.hidden{ display: none; }
.lib-ed-shell-header .ed-ph-title-row{
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; margin: 6px 0 8px;
}
.lib-ed-shell-header .ed-ph-title{
  font-size: clamp(22px, 2.4vw, 30px); font-weight: 700;
  color: var(--white); margin: 0; line-height: 1.15;
  border: none; padding: 0;
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lib-ed-shell-header .ed-ph-meta{
  display: flex; flex-wrap: wrap; align-items: center; gap: 12px;
  font-size: 11px; color: var(--dim);
}
.lib-ed-shell-header .ed-ph-sep{ color: var(--bdr); }
.lib-ed-shell-header .ed-ph-badge{
  padding: 3px 9px; border-radius: 10px;
  font-size: 9px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em;
}
.lib-ed-shell-header .ed-ph-badge-system{
  background: color-mix(in srgb, var(--acc) 18%, transparent);
  color: var(--white);
  border: 1px solid color-mix(in srgb, var(--acc) 35%, transparent);
}
.lib-ed-shell-header .ed-ph-badge-custom{
  background: var(--inp); color: var(--dim);
  border: 1px solid var(--bdr);
}

/* Hide the legacy drawer chrome (title bar, sub label, status, button
 * row, similar-items hint) when the shell header is active — otherwise
 * everything would duplicate above + below the form. */
#edit-drawer:has(.lib-ed-shell-header) > .drawer > .drawer-close,
#edit-drawer:has(.lib-ed-shell-header) > .drawer > #ed-title,
#edit-drawer:has(.lib-ed-shell-header) > .drawer > #ed-sub,
#edit-drawer:has(.lib-ed-shell-header) > .drawer > .sub,
#edit-drawer:has(.lib-ed-shell-header) > .drawer > #lib-edit-status,
#edit-drawer:has(.lib-ed-shell-header) > .drawer > .brow,
#edit-drawer:has(.lib-ed-shell-header) > .drawer > #ed-msg,
#edit-drawer:has(.lib-ed-shell-header) > .drawer > #lib-edit-similar{
  display: none !important;
}

/* Pricing input — wrapper holds the $ prefix + the number input so the
 * border-on-wrapper focus highlight doesn't shift the input geometry. */
.lib-ed-shell .ed-pricing-input{
  display: flex; align-items: stretch;
  width: 100%;
  border: 1px solid color-mix(in srgb, var(--bdr) 65%, var(--dim) 25%);
  border-radius: 6px;
  background: var(--bg);
  overflow: hidden;
  transition: border-color .15s, box-shadow .15s;
}
.lib-ed-shell .ed-pricing-input:focus-within{
  border-color: var(--acc);
  box-shadow: 0 0 0 1px var(--acc);
}
.lib-ed-shell .ed-pricing-input .ed-pricing-prefix{
  display: inline-flex; align-items: center;
  padding: 0 12px;
  font-size: 14px; font-weight: 600;
  color: var(--dim);
  background: color-mix(in srgb, var(--inp) 70%, transparent);
  border-right: 1px solid var(--bdr);
  user-select: none;
}
.lib-ed-shell .ed-pricing-input input{
  flex: 1 1 auto;
  width: 100%; min-width: 0;
  min-height: 42px;
  padding: 10px 12px;
  font-size: 14px;
  color: var(--white);
  background: transparent !important;
  border: 0 !important;
  outline: none !important;
  border-radius: 0;
  font-variant-numeric: tabular-nums;
}

/* ── Responsive ladders for the shell ───────────────────────────────
 *  980px: 3-col → vertical stack; nav rail becomes horizontal chip strip.
 *  640px: page-header action row wraps to a new line.
 *  480px: phone — actions stretch full-width, eyebrow shrinks. */
@media (max-width: 980px){
  #edit-drawer .drawer:has(.lib-ed-shell){ padding: 16px !important; }
  .lib-ed-shell{ grid-template-columns: 1fr; max-width: none; row-gap: 14px; }
  .lib-ed-shell-nav{
    grid-column: 1; position: static;
    flex-direction: row; flex-wrap: nowrap; overflow-x: auto;
    gap: 6px;
    padding: 6px; scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
  }
  .lib-ed-shell-nav button{
    flex: 0 0 auto; white-space: nowrap; min-height: 56px;
    scroll-snap-align: start;
  }
  .lib-ed-shell-nav .ed-nav-sub{ display: none; }
  .lib-ed-shell-content{ grid-column: 1; }
  .lib-ed-shell-content > .lib-ed-section[data-active="true"]{ min-height: auto; }
  .lib-ed-shell > .lib-ed-shell-preview{ grid-column: 1; grid-row: auto; position: static; margin: 0 !important; }
  .lib-ed-shell > .lib-ed-shell-meta{ grid-column: 1; grid-row: auto; }
  .lib-ed-shell .lib-ed-section-body .fr{ flex-wrap: wrap; }
  .lib-ed-shell .lib-ed-section-body .fg{ flex: 1 1 220px; min-width: 0; }
}
@media (max-width: 640px){
  #edit-drawer .drawer:has(.lib-ed-shell){ padding: 12px !important; }
  .lib-ed-shell-header{
    padding: 14px 16px 12px !important;
    margin: -12px -12px 14px !important;
  }
  .lib-ed-shell-header .ed-ph-row{ margin-bottom: 6px; }
  .lib-ed-shell-header .ed-ph-title-row{
    flex-wrap: wrap; gap: 10px; margin: 4px 0 6px;
  }
  .lib-ed-shell-header .ed-ph-title{
    flex: 1 1 100%; min-width: 0;
    white-space: normal; overflow: visible;
  }
  .lib-ed-shell-header .ed-ph-actions{
    flex: 1 1 100%; justify-content: flex-end;
    flex-wrap: wrap; gap: 8px;
  }
  .lib-ed-shell-header .ed-ph-meta{ font-size: 10px; gap: 8px; }
  .lib-ed-shell .lib-ed-section-body{ padding: 14px; }
  .lib-ed-shell .lib-ed-section-head{ padding: 12px 14px; font-size: 13px; }
}
@media (max-width: 480px){
  #edit-drawer .drawer:has(.lib-ed-shell){ padding: 8px !important; }
  .lib-ed-shell-header{
    padding: 12px 12px 10px !important;
    margin: -8px -8px 12px !important;
  }
  .lib-ed-shell-header .ed-ph-eyebrow{ font-size: 9px; letter-spacing: 0.08em; }
  .lib-ed-shell-header .ed-ph-actions button{
    flex: 1 1 auto; min-height: 44px;
  }
  .lib-ed-shell-header .ed-ph-divider{ display: none; }
  .lib-ed-shell-header .ed-ph-meta{ gap: 6px; }
  .lib-ed-shell-header .ed-ph-sep{ display: none; }
  .lib-ed-shell .lib-ed-section-body{ padding: 12px; }
  .lib-ed-shell .lib-ed-section-body .fr{ gap: 10px; margin-bottom: 10px; }
  .lib-ed-shell .lib-ed-section-body .fg{ flex: 1 1 100%; }
  .lib-ed-shell-nav button{ min-height: 48px; padding: 8px 10px; }
  .lib-ed-shell-nav .ed-nav-num{ width: 22px; height: 22px; font-size: 11px; }
  .lib-ed-shell-nav .ed-nav-title{ font-size: 10px; }
  .lib-ed-shell .ed-pricing-input input{ min-height: 48px; font-size: 15px; }
  .lib-ed-shell .ed-pricing-input .ed-pricing-prefix{ padding: 0 14px; font-size: 15px; }
}

/* ── /LIB-EDITOR-SHELL ─────────────────────────────────────────────── */


/* ======================================================================
   PR-DECOMP-1 (2026-05-25) — page-partial loader UX components
   ======================================================================
   Visual placeholders rendered into #pg-host while a partial loads or
   fails. Tokens-only (no hex), respects prefers-reduced-motion.

   .pg-shimmer       → loading state (subtle 3-bar placeholder)
   .pg-offline-err   → failure state (rose icon + retry button)

   The host (#pg-host) is a sibling of the other .pg divs inside <main>,
   so these inherit the page-fills-viewport layout. Timing thresholds:
     0–100ms   nothing (Doherty threshold; sub-100ms loaders flicker)
     100–600ms shimmer appears (CSS animates if motion allowed)
     >3s       shimmer is swapped for .pg-offline-err by _ensurePage()
   ====================================================================== */

#pg-host:empty {
    /* Inert when not actively loading anything. */
    display: none;
}
#pg-host:not(:empty) {
    /* Take over the viewport while showing a shimmer/error. Matches the
       layout pattern used by other .pg divs (block-or-flex full height). */
    display: block;
    min-height: 60vh;
    padding: 24px;
    box-sizing: border-box;
}

/* ── Shimmer (loading) ─────────────────────────────────────────────── */
.pg-shimmer {
    max-width: 820px;
    margin: 64px auto 0;
    padding: 24px 28px;
    background: var(--pn);
    border: 1px solid var(--bdr);
    border-radius: 10px;
}
.pg-shimmer-bar {
    height: 14px;
    margin: 0 0 12px 0;
    background: var(--pn-h);
    border-radius: 4px;
    background-image: linear-gradient(
        90deg,
        var(--pn-h) 0%,
        var(--bdr) 50%,
        var(--pn-h) 100%
    );
    background-size: 200% 100%;
    background-position: 100% 0;
    animation: pg-shimmer-slide 1.6s linear infinite;
}
.pg-shimmer-bar:last-child { margin-bottom: 0; }
.pg-shimmer-bar--mid   { width: 70%; }
.pg-shimmer-bar--short { width: 45%; }

@keyframes pg-shimmer-slide {
    0%   { background-position: 100% 0; }
    100% { background-position: -100% 0; }
}

/* Reduced-motion: drop the slide animation, keep a static dim block. */
@media (prefers-reduced-motion: reduce) {
    .pg-shimmer-bar {
        animation: none;
        background-image: none;
        background: var(--pn-h);
    }
}

/* ── Offline / load-failure error ───────────────────────────────────── */
.pg-offline-err {
    max-width: 480px;
    margin: 96px auto 0;
    padding: 28px 32px;
    background: var(--pn);
    border: 1px solid var(--bdr);
    border-left: 4px solid var(--no);
    border-radius: 10px;
    text-align: center;
}
.pg-offline-err__icon {
    width: 44px;
    height: 44px;
    margin: 0 auto 14px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: color-mix(in srgb, var(--no) 12%, transparent);
    color: var(--no);
    font: var(--fw-bold) 24px/1 var(--ff-ui);
}
.pg-offline-err__title {
    margin: 0 0 6px 0;
    color: var(--txt);
    font: var(--type-section-h3);
}
.pg-offline-err__body {
    margin: 0 0 18px 0;
    color: var(--dim);
    font: var(--type-body);
}
.pg-offline-err__retry {
    min-height: 44px;
    padding: 10px 22px;
    background: var(--pn-h);
    color: var(--txt);
    border: 1px solid var(--bdr);
    border-radius: 6px;
    font: var(--type-body);
    cursor: pointer;
}
.pg-offline-err__retry:hover {
    background: var(--bdr);
}
.pg-offline-err__retry:focus-visible {
    outline: 2px solid var(--acc);
    outline-offset: 2px;
}

/* ── /PR-DECOMP-1 ──────────────────────────────────────────────────── */

