

@layer tokens, reset, base, layout, components, pages, overrides;
/* ============================================================
   bnkops design system — CSS entry point
   Import order matters: tokens → base → layout → components → pages
   Each layer is declared here so @layer order is explicit.
   PostCSS processes: postcss-import (bundles @imports) →
     postcss-nesting (CSS nesting) → autoprefixer.
   ============================================================ */
/* --- Tokens (design primitives — never reference these directly in HTML) --- */
/* ============================================================
   tokens/color.css — M3 Color Role Tokens
   ------------------------------------------------------------
   These CSS custom properties are the single source of truth
   for all colors in the system. Components ONLY reference
   these semantic role names (e.g. --md-sys-color-primary),
   never raw hex values. This makes theming (dark mode,
   branding) a single-file change.

   Palette basis:
     Primary:   Slate Blue #2C5282  (hue 220° — professional ops)
     Secondary: Teal       #0F766E  (hue 175° — actionable CTAs)
     Tertiary:  Amber      #B45309  (warnings, overdue items)
     Error:     Red        #B91C1C  (standard M3 error)
   ============================================================ */
@layer tokens {

  /* ──────────────────────────────────────────────────────────
     LIGHT SCHEME (default)
     ────────────────────────────────────────────────────────── */
  :root {

    /* Primary — Slate Blue
       Use for: primary buttons, active nav state, focus rings,
       key interactive UI elements */
    --md-sys-color-primary:              #2C5282;
    --md-sys-color-on-primary:           #FFFFFF;
    --md-sys-color-primary-container:    #DBEAFE;  /* selected row bg */
    --md-sys-color-on-primary-container: #1E3A5F;

    /* Secondary — Teal
       Use for: secondary/tonal buttons, active nav indicator,
       secondary-container chips, positive actions */
    --md-sys-color-secondary:            #0F766E;
    --md-sys-color-on-secondary:         #FFFFFF;
    --md-sys-color-secondary-container:  #CCFBF1;
    --md-sys-color-on-secondary-container: #134E4A;

    /* Tertiary — Amber
       Use for: warning states, overdue dates, attention items */
    --md-sys-color-tertiary:             #B45309;
    --md-sys-color-on-tertiary:          #FFFFFF;
    --md-sys-color-tertiary-container:   #FEF3C7;
    --md-sys-color-on-tertiary-container:#78350F;

    /* Error
       Use for: error messages, destructive actions, validation */
    --md-sys-color-error:                #B91C1C;
    --md-sys-color-on-error:             #FFFFFF;
    --md-sys-color-error-container:      #FEE2E2;
    --md-sys-color-on-error-container:   #7F1D1D;

    /* Surface & Background
       surface-container = sidebar, table header bg
       surface = main content bg, card bg
       background = overall page bg */
    --md-sys-color-background:                 #F8FAFC;
    --md-sys-color-on-background:              #0F172A;
    --md-sys-color-surface:                    #FFFFFF;
    --md-sys-color-on-surface:                 #0F172A;
    --md-sys-color-surface-variant:            #E8EDF4;
    --md-sys-color-on-surface-variant:         #3A4657;
    --md-sys-color-surface-container-lowest:   #F4F7FB;   /* lowest elevation surface */
    --md-sys-color-surface-container-low:      #F0F4F9;   /* subtle tinted surface */
    --md-sys-color-surface-container:          #EEF2F8;   /* nav rail, table headers */
    --md-sys-color-surface-container-high:     #E4EAF3;
    --md-sys-color-surface-container-highest:  #D8E1EE;

    /* Outline
       outline = field borders, dividers
       outline-variant = subtle dividers, table row separators */
    --md-sys-color-outline:         #8A9BB5;
    --md-sys-color-outline-variant: #C5D0E0;

    /* Inverse (used for snackbars — dark on light / light on dark) */
    --md-sys-color-inverse-surface:    #1E293B;
    --md-sys-color-inverse-on-surface: #F1F5F9;

    /* Scrim — overlay behind modals/drawers on mobile */
    --md-sys-color-scrim: rgba(0, 0, 0, 0.32);

    /* ── Pipeline stage colors (semantic extensions, not M3 standard) ── */
    --color-stage-lead:        #6366F1;  /* indigo-500 */
    --color-stage-qualified:   #0891B2;  /* cyan-600   */
    --color-stage-proposal:    #D97706;  /* amber-600  */
    --color-stage-negotiation: #9333EA;  /* purple-600 */
    --color-stage-won:         #16A34A;  /* green-600  */
    --color-stage-lost:        #64748B;  /* slate-500  */

    /* ── Invoice status colors ──
       Note: --color-status-sent uses teal-600 (#0D9488) — a distinct hue from
       --color-stage-qualified (#0891B2, cyan-600) so invoice statuses and
       pipeline stages are visually separate families. */
    --color-status-draft:   #64748B;
    --color-status-sent:    #0D9488;  /* teal-600 — distinct from cyan pipeline stages */
    --color-status-paid:    #16A34A;
    --color-status-overdue: #B91C1C;

    /* ── Task status colors ── */
    --color-task-todo:        #64748B;
    --color-task-in-progress: #0891B2;
    --color-task-done:        #16A34A;
    --color-task-blocked:     #B91C1C;

    /* ── M3 State Layer Opacities ──
       Applied via ::after pseudo-element on interactive elements
       to communicate hover/focus/pressed/disabled states */
    --md-sys-state-hover-opacity:    0.08;
    --md-sys-state-focus-opacity:    0.12;
    --md-sys-state-pressed-opacity:  0.12;
    --md-sys-state-dragged-opacity:  0.16;
    --md-sys-state-disabled-opacity: 0.38;
  }

  /* ──────────────────────────────────────────────────────────
     DARK SCHEME — auto via prefers-color-scheme
     Surfaces invert: dark blues replace whites.
     Primary becomes a lighter tint for legibility on dark bg.
     ────────────────────────────────────────────────────────── */
  @media (prefers-color-scheme: dark) {
    :root {
      --md-sys-color-primary:              #93C5FD;   /* blue-300 — lighter for dark bg */
      --md-sys-color-on-primary:           #1E3A5F;
      --md-sys-color-primary-container:    #1D4ED8;
      --md-sys-color-on-primary-container: #DBEAFE;

      --md-sys-color-secondary:            #5EEAD4;   /* teal-300 */
      --md-sys-color-on-secondary:         #134E4A;
      --md-sys-color-secondary-container:  #0F766E;
      --md-sys-color-on-secondary-container: #CCFBF1;

      --md-sys-color-tertiary:             #FCD34D;
      --md-sys-color-on-tertiary:          #78350F;
      --md-sys-color-tertiary-container:   #B45309;
      --md-sys-color-on-tertiary-container:#FEF3C7;

      --md-sys-color-error:                #FCA5A5;
      --md-sys-color-on-error:             #7F1D1D;
      --md-sys-color-error-container:      #991B1B;
      --md-sys-color-on-error-container:   #FEE2E2;

      --md-sys-color-background:                 #0F172A;  /* slate-900 */
      --md-sys-color-on-background:              #E2E8F0;
      --md-sys-color-surface:                    #1E293B;  /* slate-800 */
      --md-sys-color-on-surface:                 #E2E8F0;
      --md-sys-color-surface-variant:            #253349;
      --md-sys-color-on-surface-variant:         #A8B9CC;
      --md-sys-color-surface-container-lowest:   #111827;   /* darkest surface */
      --md-sys-color-surface-container-low:      #141E2B;   /* subtle dark surface */
      --md-sys-color-surface-container:          #1A2436;
      --md-sys-color-surface-container-high:     #243044;
      --md-sys-color-surface-container-highest:  #2E3D54;

      --md-sys-color-outline:         #556070;
      --md-sys-color-outline-variant: #2D3E52;

      --md-sys-color-inverse-surface:    #E2E8F0;
      --md-sys-color-inverse-on-surface: #1E293B;
    }
  }

  /* ──────────────────────────────────────────────────────────
     MANUAL THEME OVERRIDES via data-theme attribute on <html>
     These let the theme-toggle Stimulus controller override
     the OS preference. JS toggles data-theme="dark"/"light".
     ────────────────────────────────────────────────────────── */
  [data-theme="dark"] {
    --md-sys-color-primary:              #93C5FD;
    --md-sys-color-on-primary:           #1E3A5F;
    --md-sys-color-primary-container:    #1D4ED8;
    --md-sys-color-on-primary-container: #DBEAFE;

    --md-sys-color-secondary:            #5EEAD4;
    --md-sys-color-on-secondary:         #134E4A;
    --md-sys-color-secondary-container:  #0F766E;
    --md-sys-color-on-secondary-container: #CCFBF1;

    --md-sys-color-tertiary:             #FCD34D;
    --md-sys-color-on-tertiary:          #78350F;
    --md-sys-color-tertiary-container:   #B45309;
    --md-sys-color-on-tertiary-container:#FEF3C7;

    --md-sys-color-error:                #FCA5A5;
    --md-sys-color-on-error:             #7F1D1D;
    --md-sys-color-error-container:      #991B1B;
    --md-sys-color-on-error-container:   #FEE2E2;

    --md-sys-color-background:                 #0F172A;
    --md-sys-color-on-background:              #E2E8F0;
    --md-sys-color-surface:                    #1E293B;
    --md-sys-color-on-surface:                 #E2E8F0;
    --md-sys-color-surface-variant:            #253349;
    --md-sys-color-on-surface-variant:         #A8B9CC;
    --md-sys-color-surface-container-lowest:   #111827;
    --md-sys-color-surface-container-low:      #141E2B;
    --md-sys-color-surface-container:          #1A2436;
    --md-sys-color-surface-container-high:     #243044;
    --md-sys-color-surface-container-highest:  #2E3D54;

    --md-sys-color-outline:         #556070;
    --md-sys-color-outline-variant: #2D3E52;

    --md-sys-color-inverse-surface:    #E2E8F0;
    --md-sys-color-inverse-on-surface: #1E293B;
  }

  [data-theme="light"] {
    /* Explicitly force light — overrides any OS dark preference */
    --md-sys-color-primary:              #2C5282;
    --md-sys-color-on-primary:           #FFFFFF;
    --md-sys-color-primary-container:    #DBEAFE;
    --md-sys-color-on-primary-container: #1E3A5F;

    --md-sys-color-secondary:            #0F766E;
    --md-sys-color-on-secondary:         #FFFFFF;
    --md-sys-color-secondary-container:  #CCFBF1;
    --md-sys-color-on-secondary-container: #134E4A;

    --md-sys-color-tertiary:             #B45309;
    --md-sys-color-on-tertiary:          #FFFFFF;
    --md-sys-color-tertiary-container:   #FEF3C7;
    --md-sys-color-on-tertiary-container:#78350F;

    --md-sys-color-error:                #B91C1C;
    --md-sys-color-on-error:             #FFFFFF;
    --md-sys-color-error-container:      #FEE2E2;
    --md-sys-color-on-error-container:   #7F1D1D;

    --md-sys-color-background:                 #F8FAFC;
    --md-sys-color-on-background:              #0F172A;
    --md-sys-color-surface:                    #FFFFFF;
    --md-sys-color-on-surface:                 #0F172A;
    --md-sys-color-surface-variant:            #E8EDF4;
    --md-sys-color-on-surface-variant:         #3A4657;
    --md-sys-color-surface-container-lowest:   #F4F7FB;
    --md-sys-color-surface-container-low:      #F0F4F9;
    --md-sys-color-surface-container:          #EEF2F8;
    --md-sys-color-surface-container-high:     #E4EAF3;
    --md-sys-color-surface-container-highest:  #D8E1EE;

    --md-sys-color-outline:         #8A9BB5;
    --md-sys-color-outline-variant: #C5D0E0;

    --md-sys-color-inverse-surface:    #1E293B;
    --md-sys-color-inverse-on-surface: #F1F5F9;
  }

  /* ──────────────────────────────────────────────────────────
     STATE LAYER HELPER
     Reusable pattern for interactive surface overlays.
     Apply .state-layer to any clickable container.
     The ::after pseudo-element provides hover/focus/pressed
     visual feedback without JavaScript.
     ────────────────────────────────────────────────────────── */
  .state-layer {
    position: relative;
    overflow: hidden;
  }

    .state-layer::after {
      content: "";
      position: absolute;
      inset: 0;
      background-color: currentColor;
      opacity: 0;
      transition: opacity 120ms ease;
      pointer-events: none;
      border-radius: inherit;
    }

    .state-layer:hover::after  { opacity: var(--md-sys-state-hover-opacity); }
    .state-layer:focus-visible::after { opacity: var(--md-sys-state-focus-opacity); }
    .state-layer:active::after { opacity: var(--md-sys-state-pressed-opacity); }
}
/* ============================================================
   tokens/typography.css — Dense Type Scale Tokens
   ------------------------------------------------------------
   M3 default sizes are mobile-first (16px body, 1.5 line-height).
   We override with a dense ops-UI scale: 13px body, tighter
   line-heights. Components reference these tokens, never raw px.

   Scale hierarchy:
     display   → dashboard stat numbers (28px, 600)
     headline  → section titles, drawer headers (16–20px)
     title     → card titles, column headers (12–15px, 600)
     body      → table cells, form text (12–14px, 400)
     label     → chips, metadata, table headers (11–13px, 500)
   ============================================================ */
@layer tokens {
  :root {
    /* Font families */
    --md-sys-typescale-font-family: "Inter", system-ui, -apple-system,
      "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    --md-sys-typescale-mono-family: "JetBrains Mono", "Fira Code",
      "Cascadia Code", ui-monospace, monospace;

    /* ── Display — for dashboard stat numbers only ── */
    --md-typescale-display-size:        28px;
    --md-typescale-display-weight:      600;
    --md-typescale-display-line-height: 1.2;
    --md-typescale-display-letter-spacing: -0.01em;

    /* ── Headline Large — page titles, major section headers ── */
    --md-typescale-headline-large-size:        20px;
    --md-typescale-headline-large-weight:      600;
    --md-typescale-headline-large-line-height: 1.3;

    /* ── Headline Small — drawer headers, sub-section titles ── */
    --md-typescale-headline-small-size:        16px;
    --md-typescale-headline-small-weight:      600;
    --md-typescale-headline-small-line-height: 1.3;

    /* ── Title Large — card titles, bold labels ── */
    --md-typescale-title-large-size:        15px;
    --md-typescale-title-large-weight:      600;
    --md-typescale-title-large-line-height: 1.3;

    /* ── Title Medium — drawer content headings ── */
    --md-typescale-title-medium-size:        13px;
    --md-typescale-title-medium-weight:      600;
    --md-typescale-title-medium-line-height: 1.3;

    /* ── Title Small — compact section labels ── */
    --md-typescale-title-small-size:        12px;
    --md-typescale-title-small-weight:      600;
    --md-typescale-title-small-line-height: 1.3;

    /* ── Body Large — emphasized table cells, form inputs ── */
    --md-typescale-body-large-size:        14px;
    --md-typescale-body-large-weight:      400;
    --md-typescale-body-large-line-height: 1.4;

    /* ── Body Medium — primary body text, table cells ──
       This is the base UI font size for the entire app */
    --md-typescale-body-medium-size:        13px;
    --md-typescale-body-medium-weight:      400;
    --md-typescale-body-medium-line-height: 1.4;

    /* ── Body Small — secondary text, helper text, timestamps ── */
    --md-typescale-body-small-size:        12px;
    --md-typescale-body-small-weight:      400;
    --md-typescale-body-small-line-height: 1.35;

    /* ── Label Large — nav items, primary chip text, button text ── */
    --md-typescale-label-large-size:        13px;
    --md-typescale-label-large-weight:      500;
    --md-typescale-label-large-line-height: 1.2;

    /* ── Label Medium — table column headers, metadata ── */
    --md-typescale-label-medium-size:        12px;
    --md-typescale-label-medium-weight:      500;
    --md-typescale-label-medium-line-height: 1.2;

    /* ── Label Small — chip text, secondary metadata ── */
    --md-typescale-label-small-size:        11px;
    --md-typescale-label-small-weight:      500;
    --md-typescale-label-small-line-height: 1.2;
  }
}
/* ============================================================
   tokens/spacing.css — Spacing & Density Scale Tokens
   ------------------------------------------------------------
   Base unit: 4px (M3 4dp grid system).
   Space tokens are multiples: --space-1=4px, --space-2=8px, etc.

   Density tokens override M3 default component sizes down to
   a "density -2" level suitable for a desktop ops tool.
   M3 defaults shown in comments for reference.
   ============================================================ */
@layer tokens {
  :root {
    /* ── 4px-grid spacing scale ── */
    --space-1:   4px;
    --space-2:   8px;
    --space-3:  12px;
    --space-4:  16px;
    --space-5:  20px;
    --space-6:  24px;
    --space-7:  28px;
    --space-8:  32px;
    --space-10: 40px;
    --space-12: 48px;

    /* ── Component density tokens ──
       M3 defaults shown in comments; we override for dense ops UI */

    /* Table rows: 34px vs M3 default 52px */
    --density-table-row-height:    34px;
    --density-table-header-height: 36px;

    /* Form fields: 36px vs M3 outlined default 56px */
    --density-form-field-height: 36px;

    /* Buttons: 32px vs M3 default 40px */
    --density-button-height: 32px;

    /* Chips: 24px vs M3 default 32px */
    --density-chip-height: 24px;

    /* Nav items: 40px vs M3 default 56px */
    --density-nav-item-height: 40px;

    /* Top bar: 48px vs M3 default 64px */
    --density-top-bar-height: 48px;

    /* Nav rail widths */
    --density-nav-rail-width:   56px;   /* icon-only (collapsed) */
    --density-nav-drawer-width: 220px;  /* with labels (expanded) */

    /* Detail drawer widths */
    --density-detail-drawer-width:      420px;  /* standard detail view */
    --density-detail-drawer-width-wide: 560px;  /* edit forms */

    /* ── Component internal padding ── */
    --density-table-cell-px:   12px;  /* horizontal padding in table cells */
    --density-table-cell-py:    0px;  /* vertical handled by row-height */
    --density-form-field-px:   12px;
    --density-form-field-py:    8px;
    --density-card-p:          16px;
    --density-section-gap:     16px;

    /* ── Border radius scale ── */
    --radius-xs:    2px;
    --radius-sm:    4px;
    --radius-md:    8px;
    --radius-lg:   12px;
    --radius-xl:   16px;
    --radius-full:  9999px;

    /* ── M3 shape corner aliases ──
       M3 uses semantic shape names; --radius-* are the structural tokens.
       These aliases let M3-named references resolve correctly. */
    --shape-corner-extra-small: var(--radius-sm);   /* 4px — M3 XS corner */
  }
}
/* ============================================================
   tokens/elevation.css — Shadow / Elevation Tokens
   ------------------------------------------------------------
   M3 uses 5 elevation levels. Higher = more prominent surface.
   In dark mode, M3 also tints elevated surfaces with the primary
   color (using a transparency overlay). We include the tint token
   for components that implement this behavior.

   Level usage guide:
     0 — flat: backgrounds, non-interactive surfaces
     1 — cards, input fields, chip containers
     2 — raised cards, dropdown menus, tooltips
     3 — FABs, side sheets, snackbars
     4 — navigation drawers, modal side panels
     5 — dialogs, modals (highest visual importance)
   ============================================================ */
@layer tokens {
  :root {
    /* Level 0 — flat */
    --md-sys-elevation-0: none;

    /* Level 1 — subtle depth: cards, inputs */
    --md-sys-elevation-1:
      0 1px 2px rgba(0, 0, 0, 0.12),
      0 1px 3px 1px rgba(0, 0, 0, 0.07);

    /* Level 2 — moderate depth: raised cards, menus */
    --md-sys-elevation-2:
      0 1px 2px rgba(0, 0, 0, 0.12),
      0 2px 6px 2px rgba(0, 0, 0, 0.07);

    /* Level 3 — pronounced: FAB, snackbars, side sheets */
    --md-sys-elevation-3:
      0 4px 8px 3px rgba(0, 0, 0, 0.10),
      0 1px 3px rgba(0, 0, 0, 0.10);

    /* Level 4 — heavy: navigation drawer panels */
    --md-sys-elevation-4:
      0 6px 10px 4px rgba(0, 0, 0, 0.10),
      0 2px 3px rgba(0, 0, 0, 0.10);

    /* Level 5 — maximum: dialogs, full-screen sheets */
    --md-sys-elevation-5:
      0 8px 12px 6px rgba(0, 0, 0, 0.10),
      0 4px 4px rgba(0, 0, 0, 0.10);

    /* M3 tint token — primary color applied as tint on dark surfaces */
    --md-sys-elevation-tint-primary: var(--md-sys-color-primary);
  }
}
/* ============================================================
   tokens/motion.css — Duration & Easing Tokens
   ------------------------------------------------------------
   M3 motion system uses named durations and easing curves.
   All transitions in the system use these tokens — never
   hardcoded values — so timing can be tuned globally.

   Duration guide:
     short1  50ms  — micro-interactions (icon swap, color change)
     short2 100ms  — hover state transitions
     medium1 200ms — element entrance/exit, snackbar appear
     medium2 300ms — panel slide (drawer open/close)
     long1   400ms — complex layout transitions

   Easing guide:
     standard   — default motion (most transitions)
     emphasized — important focus transitions
     decelerate — elements entering the screen (ease-out)
     accelerate — elements leaving the screen (ease-in)
   ============================================================ */
@layer tokens {
  :root {
    /* Durations */
    --md-sys-motion-duration-short1:  50ms;
    --md-sys-motion-duration-short2:  100ms;
    --md-sys-motion-duration-short3:  150ms;   /* hover/nav-link transitions */
    --md-sys-motion-duration-medium1: 200ms;
    --md-sys-motion-duration-medium2: 300ms;
    --md-sys-motion-duration-long1:   400ms;

    /* Easing curves */
    --md-sys-motion-easing-standard:   cubic-bezier(0.2, 0, 0, 1);
    --md-sys-motion-easing-emphasized: cubic-bezier(0.2, 0, 0, 1);
    --md-sys-motion-easing-decelerate: cubic-bezier(0, 0, 0, 1);
    --md-sys-motion-easing-accelerate: cubic-bezier(0.3, 0, 1, 1);

    /* Prefers-reduced-motion: respect user OS setting */
  }
    @media (prefers-reduced-motion: reduce) {
  :root {
      --md-sys-motion-duration-short1:  0ms;
      --md-sys-motion-duration-short2:  0ms;
      --md-sys-motion-duration-short3:  0ms;
      --md-sys-motion-duration-medium1: 0ms;
      --md-sys-motion-duration-medium2: 0ms;
      --md-sys-motion-duration-long1:   0ms;
  }
    }
}
/* --- Base (global resets and type application) --- */
/* ============================================================
   base/reset.css — Minimal CSS Reset
   ------------------------------------------------------------
   A modern, surgical reset rather than a full normalize.
   Preserves useful browser defaults (form elements, etc.) while
   eliminating the inconsistencies that create layout surprises.
   ============================================================ */
@layer reset {
  /* Universal box model — padding/border included in width/height */
  *,
  *::before,
  *::after {
    box-sizing: border-box;
  }

  /* Remove default margin/padding on common elements */
  body,
  h1, h2, h3, h4, h5, h6,
  p, ul, ol, li,
  figure, figcaption,
  blockquote, dl, dd {
    margin: 0;
    padding: 0;
  }

  /* Remove list styling — add it back explicitly where needed */
  ul, ol {
    list-style: none;
  }

  /* Make the [hidden] attribute actually hide.
     The UA sheet's `[hidden] { display: none }` is defeated whenever an author
     rule sets `display` on the same element (e.g. `.form-section { display: flex }`),
     because author styles outrank UA styles. That silently breaks any markup or
     JS that toggles the `hidden` attribute (Stimulus `el.hidden = true`, the
     commons new-post kind selector, etc.). `!important` wins regardless of which
     @layer the competing `display` lives in. normalize.css ships this same rule. */
  [hidden] {
    display: none !important;
  }

  /* HTML root: smooth scroll, prevent font size inflation on mobile */
  html {
    scroll-behavior: smooth;
    -webkit-text-size-adjust: 100%;
    -moz-text-size-adjust: 100%;
         text-size-adjust: 100%;
  }

  /* Body: full viewport height, no overflow */
  body {
    min-height: 100dvh;
    line-height: 1.4;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
  }

  /* Images and media: responsive by default */
  img, picture, video, canvas, svg {
    display: block;
    max-width: 100%;
  }

  /* Form controls inherit font from parent (browsers don't do this by default) */
  input, button, textarea, select {
    font: inherit;
  }

  /* Avoid text overflow on headings */
  h1, h2, h3, h4, h5, h6 {
    overflow-wrap: break-word;
  }

  /* Links: no underline by default (add back where semantically needed) */
  a {
    color: inherit;
    text-decoration: none;
  }

  /* Tables: collapse borders by default */
  table {
    border-collapse: collapse;
  }

  /* Remove button default browser styling */
  button {
    cursor: pointer;
    background: none;
    border: none;
    padding: 0;
  }

  /* Horizontal rule */
  hr {
    border: none;
    border-top: 1px solid var(--md-sys-color-outline-variant);
  }
}
/* ============================================================
   base/typography.css — Font Loading & Global Type Application
   ------------------------------------------------------------
   Self-hosted Inter woff2 files (400/500/600 weights).
   Files are in app/assets/fonts/ — Propshaft serves them from
   /assets/Inter-*.woff2 with fingerprinting.

   Applies type tokens from tokens/typography.css to the
   html/body element and common semantic elements.
   ============================================================ */
@layer base {

  /* ── Self-hosted Inter font faces ── */
  @font-face {
    font-family: "Inter";
    font-style: normal;
    font-weight: 400;
    font-display: swap;   /* show system font while Inter loads */
    src: url("/assets/Inter-Regular.woff2") format("woff2");
  }

  @font-face {
    font-family: "Inter";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("/assets/Inter-Medium.woff2") format("woff2");
  }

  @font-face {
    font-family: "Inter";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("/assets/Inter-SemiBold.woff2") format("woff2");
  }

  /* ── Global base styles ── */
  html {
    font-size: 16px;  /* root for rem calculations (we use px tokens) */
  }

  body {
    font-family: var(--md-sys-typescale-font-family);
    font-size: var(--md-typescale-body-medium-size);
    font-weight: var(--md-typescale-body-medium-weight);
    line-height: var(--md-typescale-body-medium-line-height);
    color: var(--md-sys-color-on-background);
    background-color: var(--md-sys-color-background);
  }

  /* ── Semantic heading defaults ── */
  h1 {
    font-size: var(--md-typescale-headline-large-size);
    font-weight: var(--md-typescale-headline-large-weight);
    line-height: var(--md-typescale-headline-large-line-height);
  }

  h2 {
    font-size: var(--md-typescale-headline-small-size);
    font-weight: var(--md-typescale-headline-small-weight);
    line-height: var(--md-typescale-headline-small-line-height);
  }

  h3 {
    font-size: var(--md-typescale-title-large-size);
    font-weight: var(--md-typescale-title-large-weight);
    line-height: var(--md-typescale-title-large-line-height);
  }

  h4, h5, h6 {
    font-size: var(--md-typescale-title-medium-size);
    font-weight: var(--md-typescale-title-medium-weight);
    line-height: var(--md-typescale-title-medium-line-height);
  }

  /* ── Links (in content areas, not UI chrome) ── */
  .prose a,
  .detail-drawer__content a {
    color: var(--md-sys-color-primary);
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 2px;
  }

    :is(.prose a,.detail-drawer__content a):hover {
      text-decoration-thickness: 2px;
    }

  /* ── Inline text link utility ──
     A real, explicit link affordance for anchors OUTSIDE prose / drawer
     content, where the global reset (reset.css) strips the default anchor
     colour + underline. Several surfaces already write class="link" expecting
     this style (devise sign-in footer links, portal/invoices, portal/projects,
     portal/dashboard, pages/contact); previously the class was a phantom with
     no definition, so those anchors rendered unstyled unless a parent rule
     happened to rescue them. Defining it here makes the class name truthful
     and gives every one of those links a consistent, accessible affordance. */
  .link {
    color: var(--md-sys-color-primary);
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 2px;
  }

    .link:hover {
      text-decoration-thickness: 2px;
    }

  /* ── Code/mono spans ── */
  code, pre, kbd {
    font-family: var(--md-sys-typescale-mono-family);
    font-size: 0.9em;
    background: var(--md-sys-color-surface-container);
    border-radius: var(--radius-xs);
    padding: 0 3px;
  }

  pre {
    padding: var(--space-3) var(--space-4);
    overflow-x: auto;
  }

  /* ── Screen-reader only utility (used in icon-only buttons, etc.) ── */
  .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;
  }
}
/* ============================================================
   base/focus.css — M3 Focus Ring (Global)
   ------------------------------------------------------------
   M3 specifies a 3dp focus ring with 3dp offset. We apply
   this globally via :focus-visible (keyboard/AT only — mouse
   clicks do NOT show the ring, which is correct UX).

   Never suppress :focus-visible — it is essential for keyboard
   and assistive technology users.
   ============================================================ */
@layer base {
  /* Remove default browser focus ring (we replace it with M3 ring) */
  :focus {
    outline: none;
  }

  /* Apply M3 focus ring on keyboard focus only */
  :focus-visible {
    outline: 3px solid var(--md-sys-color-primary);
    outline-offset: 3px;
    border-radius: var(--radius-sm);
  }

  /* Inside primary-colored containers (nav active state, filled buttons),
     use the on-primary color so the ring is visible */
  .bg-primary :focus-visible,
  .btn--filled:focus-visible,
  .btn--destructive:focus-visible {
    outline-color: var(--md-sys-color-on-primary);
  }

  /* Inside secondary containers */
  .btn--tonal:focus-visible {
    outline-color: var(--md-sys-color-on-secondary-container);
  }
}
/* --- Layout (app shell, nav, top bar, drawer) --- */
/* ============================================================
   layout/shell.css — App Shell CSS Grid
   ------------------------------------------------------------
   The root layout of the entire application. An IDE-style
   three-column grid:

     ┌─[top-bar: full-width, 48px]────────────────────────────┐
     │ [nav-rail│  main content area    │  detail drawer       ]│
     │ [56/220px│  flex-1 (scrollable)  │  420px (optional)    ]│
     └────────────────────────────────────────────────────────┘

   The detail drawer is ALWAYS in the grid (no overlay on
   desktop) — it simply has width: 0 when closed. When open,
   content shifts left. This is the "push" pattern, not overlay.

   The nav rail width is controlled by --nav-rail-current-width,
   a custom property toggled by the nav_drawer Stimulus controller.
   ============================================================ */
@layer layout {

  /* ── Root shell grid ── */
  .app-shell {
    display: grid;
    grid-template-rows: var(--density-top-bar-height) 1fr;
    grid-template-columns: var(--nav-rail-current-width, var(--density-nav-rail-width)) 1fr auto;
    grid-template-areas:
      "topbar  topbar  topbar"
      "nav     main    drawer";
    height: 100dvh;  /* dvh = dynamic viewport height, handles mobile browser chrome */
    overflow: hidden;
    background: var(--md-sys-color-background);
    color: var(--md-sys-color-on-background);
    font-family: var(--md-sys-typescale-font-family);
    font-size: var(--md-typescale-body-medium-size);
    line-height: var(--md-typescale-body-medium-line-height);

    /* Default: collapsed nav rail (icon-only, 56px) */
    --nav-rail-current-width: var(--density-nav-rail-width);

    /* Smooth nav expansion/collapse */
    transition: grid-template-columns var(--md-sys-motion-duration-medium2)
                var(--md-sys-motion-easing-standard);
  }

  /* Expanded nav state — toggled by Stimulus adding this class */
  .app-shell.nav-expanded {
    --nav-rail-current-width: var(--density-nav-drawer-width);
  }

  /* ── Grid area assignments ── */
  .app-top-bar {
    grid-area: topbar;
    position: sticky;
    top: 0;
    z-index: 100;
  }

  .app-nav {
    grid-area: nav;
    overflow: hidden;         /* hide labels when collapsed */
    overflow-y: auto;
    transition: width var(--md-sys-motion-duration-medium2)
                var(--md-sys-motion-easing-standard);
  }

  .app-main {
    grid-area: main;
    overflow-y: auto;
    min-width: 0;             /* prevent content from stretching grid */
  }

  /* Detail drawer — width:0 means hidden but STILL in the grid flow.
     position: relative anchors the absolutely-positioned resize handle. */
  .app-drawer {
    grid-area: drawer;
    position: relative;
    width: 0;
    overflow: hidden;
    transition: width var(--md-sys-motion-duration-medium2)
                var(--md-sys-motion-easing-standard);
  }

  /* .drawer-open class added by detail_drawer Stimulus controller.
     --drawer-user-width is set by drawer_resize_controller when the operator
     drags the resize handle (gmail-style adjustable reading pane). When the
     user has chosen a width it wins over BOTH the normal and wide defaults —
     an explicit preference beats per-view hints. */
  .app-drawer.drawer-open {
    width: var(--drawer-user-width, var(--density-detail-drawer-width));
  }

  /* Wide mode for edit forms / thread reading in drawer */
  .app-drawer.drawer-open.drawer-wide {
    width: var(--drawer-user-width, var(--density-detail-drawer-width-wide));
  }

  /* While dragging the resize handle: kill the width transition so the
     drawer tracks the pointer 1:1 instead of rubber-banding behind it. */
  .app-shell.drawer-resizing .app-drawer {
    transition: none;
  }

  /* Don't let the pointer select text while dragging. */
  .app-shell.drawer-resizing {
    -webkit-user-select: none;
       -moz-user-select: none;
            user-select: none;
    cursor: col-resize;
  }

  /* ── Signed-out layout: auth pages are full-bleed ──
     When no nav/drawer is rendered (Devise views), collapse grid */
  .app-shell--auth {
    grid-template-columns: 1fr;
    grid-template-areas:
      "topbar"
      "main";
  }

  .app-shell--auth .app-nav,
  .app-shell--auth .app-drawer {
    display: none;
  }

  /* ──────────────────────────────────────────────────────────
     TABLET BREAKPOINT: 768–1199px
     Detail drawer becomes a fixed overlay (no longer in grid flow)
     Nav rail stays persistent, icon-only.
     ────────────────────────────────────────────────────────── */
  @media (max-width: 1199px) {
    .app-shell {
      grid-template-columns: var(--density-nav-rail-width) 1fr;
      grid-template-areas:
        "topbar topbar"
        "nav    main";

      /* Override expansion — always icon-only on tablet */
    }
      .app-shell.nav-expanded {
        --nav-rail-current-width: var(--density-nav-rail-width);
        grid-template-columns: var(--density-nav-rail-width) 1fr;
      }

    /* Drawer slides over content as an overlay */
    .app-drawer {
      position: fixed;
      inset: var(--density-top-bar-height) 0 0 auto;
      width: 0;
      z-index: 200;
      box-shadow: var(--md-sys-elevation-4);
    }

    .app-drawer.drawer-open {
      width: min(var(--density-detail-drawer-width), 100vw);
    }
  }

  /* ──────────────────────────────────────────────────────────
     MOBILE BREAKPOINT: < 768px
     Nav rail is hidden by default, slides in on hamburger tap.
     Drawer is full-screen push.
     ────────────────────────────────────────────────────────── */
  @media (max-width: 767px) {
    .app-shell {
      grid-template-columns: 1fr;
      grid-template-areas:
        "topbar"
        "main";
    }

    /* Nav rail: hidden off-screen, slides in on open */
    .app-nav {
      position: fixed;
      inset: var(--density-top-bar-height) auto 0 0;
      width: var(--density-nav-drawer-width);
      transform: translateX(-100%);
      z-index: 300;
      transition: transform var(--md-sys-motion-duration-medium2)
                  var(--md-sys-motion-easing-standard);
      box-shadow: var(--md-sys-elevation-4);
    }

    /* .nav-open added by nav_drawer Stimulus controller on mobile */
    .app-nav.nav-open {
      transform: translateX(0);
    }

    /* Dark scrim behind open mobile nav */
    .app-nav.nav-open::before {
      content: "";
      position: fixed;
      inset: 0;
      background: var(--md-sys-color-scrim);
      z-index: -1;
    }

    /* Drawer: full-screen on mobile */
    .app-drawer,
    .app-drawer.drawer-open {
      position: fixed;
      inset: var(--density-top-bar-height) 0 0;
      width: 100vw;
    }

    .app-drawer {
      transform: translateX(100%);
      transition: transform var(--md-sys-motion-duration-medium2)
                  var(--md-sys-motion-easing-standard);
    }

    .app-drawer.drawer-open {
      transform: translateX(0);
      box-shadow: none;
    }
  }
}
/* ============================================================
   layout/nav-rail.css — Left Navigation Rail
   ------------------------------------------------------------
   The persistent left sidebar. Collapses to 56px (icon-only)
   or expands to 220px (with labels). State is persisted in
   localStorage by the nav_drawer Stimulus controller.

   HTML contract (see _nav_rail.html.erb):
     .nav-rail > .nav-rail__header > button.nav-rail__toggle
     .nav-rail > .nav-rail__items > li > a.nav-item
     .nav-item > .nav-item__icon (SVG, 20px)
     .nav-item > .nav-item__label (hidden when collapsed)
     .nav-item.active = current page
     .nav-item.nav-rail__link--disabled = placeholder route (#)
   ============================================================ */
@layer layout {

  /* ── Rail container ── */
  .nav-rail {
    background: var(--md-sys-color-surface-container);
    border-right: 1px solid var(--md-sys-color-outline-variant);
    display: flex;
    flex-direction: column;
    overflow: hidden;       /* hides labels when collapsed */
    overflow-y: auto;
    height: 100%;
  }

  /* ── Header: toggle button + wordmark ── */
  .nav-rail__header {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    height: var(--density-top-bar-height);   /* aligns with top bar */
    padding: 0 var(--space-2);
    flex-shrink: 0;
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
  }

  /* The wordmark is hidden when rail is collapsed */
  .nav-rail__wordmark {
    font-size: var(--md-typescale-title-medium-size);
    font-weight: 600;
    color: var(--md-sys-color-on-surface-variant);
    white-space: nowrap;
    opacity: 0;
    transition: opacity var(--md-sys-motion-duration-short2);
  }

  /* Show wordmark only when app-shell is expanded */
  .app-shell.nav-expanded .nav-rail__wordmark {
    opacity: 1;
  }

  /* ── Nav items list ── */
  .nav-rail__items {
    flex: 1;
    padding: var(--space-2) 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
  }

  /* ── Individual nav item ── */
  .nav-item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    height: var(--density-nav-item-height);
    padding: 0 var(--space-3);
    border-radius: var(--radius-full);
    margin: 0 var(--space-2);
    color: var(--md-sys-color-on-surface-variant);
    text-decoration: none;
    font-size: var(--md-typescale-label-large-size);
    font-weight: var(--md-typescale-label-large-weight);
    white-space: nowrap;
    overflow: hidden;
    /* State layer */
    position: relative;
    transition: background var(--md-sys-motion-duration-short2),
                color var(--md-sys-motion-duration-short2);
  }

  .nav-item:hover {
    background: color-mix(in srgb, var(--md-sys-color-on-surface-variant) 8%, transparent);
  }

  /* Active state: secondary container highlight */
  .nav-item.active,
  .nav-item[aria-current="page"] {
    background: var(--md-sys-color-secondary-container);
    color: var(--md-sys-color-on-secondary-container);
  }

  /* Disabled placeholder links (routes not yet implemented) */
  .nav-item.nav-rail__link--disabled {
    opacity: 0.45;
    cursor: not-allowed;
    pointer-events: none;

    /* Allow pointer-events for visual but not navigation */
  }
    .nav-item.nav-rail__link--disabled:hover {
      background: transparent;
    }

  /* ── Icon inside nav item ── */
  .nav-item__icon {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  /* ── Label text ── */
  .nav-item__label {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    /* Fade in/out with rail expansion */
    opacity: 1;
    transition: opacity var(--md-sys-motion-duration-short2);
  }

  /* ── Collapsed state: hide labels, center icons ── */
  .app-shell:not(.nav-expanded) .nav-item {
    justify-content: center;
    padding: 0;
    margin: 0 auto;
    width: calc(var(--density-nav-rail-width) - var(--space-4));
    border-radius: var(--radius-lg);
  }

  .app-shell:not(.nav-expanded) .nav-item__label {
    display: none;
  }

  /* ── Footer (user avatar, settings) ── */
  .nav-rail__footer {
    padding: var(--space-2);
    border-top: 1px solid var(--md-sys-color-outline-variant);
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex-shrink: 0;
  }

  /* ── Nav toggle button (hamburger) ── */
  .nav-rail__toggle {
    flex-shrink: 0;
  }

  /* ── Section divider within nav items ── */
  .nav-rail__divider {
    height: 1px;
    background: var(--md-sys-color-outline-variant);
    margin: var(--space-2) var(--space-3);
  }

  /* ── Mobile: show full labels when open ── */
  @media (max-width: 767px) {
    .app-nav.nav-open .nav-item {
      justify-content: flex-start;
      padding: 0 var(--space-3);
      margin: 0 var(--space-2);
      width: auto;
      border-radius: var(--radius-full);
    }

    .app-nav.nav-open .nav-item__label {
      display: block;
    }

    .app-nav.nav-open .nav-rail__wordmark {
      opacity: 1;
    }
  }
}
/* ============================================================
   layout/top-bar.css — Top Application Bar
   ------------------------------------------------------------
   48px persistent top bar across the full width. Contains:
     - Hamburger menu toggle (connects to nav_drawer controller)
     - App name / breadcrumb area
     - Global search input (combobox pattern, Phase 2)
     - Theme toggle button
     - User menu

   HTML contract: see _top_bar.html.erb
   ============================================================ */
@layer layout {

  /* ── Top bar container ── */
  .app-top-bar {
    background: var(--md-sys-color-surface);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: 0 var(--space-4);
    height: var(--density-top-bar-height);
    position: relative;
    z-index: 100;
    /* Subtle shadow to separate bar from content */
    box-shadow: 0 1px 0 var(--md-sys-color-outline-variant);
  }

  /* ── App title / branding in top bar ── */
  .top-bar__title {
    font-size: var(--md-typescale-title-medium-size);
    font-weight: 600;
    color: var(--md-sys-color-on-surface);
    white-space: nowrap;
    /* Hidden on desktop since nav rail has wordmark */
    display: none;
  }

  /* On the signed-out (auth) shell there is no nav rail, so the top bar
     is the only place the brand can live — show it at every width. */
  .app-shell--auth .top-bar__title {
    display: block;
  }

  @media (max-width: 767px) {
    .top-bar__title {
      display: block;
      flex: 1;
    }
  }

  /* ── Global search (combobox) ── */
  .top-bar__search {
    position: relative;
    flex: 1;
    max-width: 480px;
  }

  .top-bar__search-input {
    width: 100%;
    height: 32px;
    background: var(--md-sys-color-surface-container);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-full);
    padding: 0 var(--space-4) 0 var(--space-8);   /* left pad for search icon */
    font-size: var(--md-typescale-body-medium-size);
    color: var(--md-sys-color-on-surface);
    outline: none;
    transition: border-color var(--md-sys-motion-duration-short2),
                background var(--md-sys-motion-duration-short2);
  }

    .top-bar__search-input::-moz-placeholder {
      color: var(--md-sys-color-on-surface-variant);
    }

    .top-bar__search-input::placeholder {
      color: var(--md-sys-color-on-surface-variant);
    }

    .top-bar__search-input:focus {
      border-color: var(--md-sys-color-primary);
      background: var(--md-sys-color-surface);
      border-width: 2px;
    }

  /* Search icon inside input */
  .top-bar__search-icon {
    position: absolute;
    left: var(--space-3);
    top: 50%;
    transform: translateY(-50%);
    color: var(--md-sys-color-on-surface-variant);
    pointer-events: none;
    display: flex;
    align-items: center;
  }

  /* Search results dropdown */
  .top-bar__search-results {
    position: absolute;
    top: calc(100% + var(--space-1));
    left: 0;
    right: 0;
    background: var(--md-sys-color-surface);
    border-radius: var(--radius-md);
    box-shadow: var(--md-sys-elevation-3);
    border: 1px solid var(--md-sys-color-outline-variant);
    max-height: 360px;
    overflow-y: auto;
    z-index: 200;
  }

  /* ── Right-side actions group ── */
  .top-bar__actions {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: var(--space-2);
  }

  /* ── User menu ── */
  .top-bar__user-menu {
    position: relative;
  }

  .top-bar__user-btn {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    height: 32px;
    padding: 0 var(--space-2);
    border-radius: var(--radius-full);
    background: transparent;
    border: none;
    cursor: pointer;
    color: var(--md-sys-color-on-surface-variant);
    font-size: var(--md-typescale-label-medium-size);
    font-weight: 500;
    transition: background var(--md-sys-motion-duration-short2);
  }

    .top-bar__user-btn:hover {
      background: color-mix(in srgb, var(--md-sys-color-on-surface) 8%, transparent);
    }

  /* User avatar circle */
  .top-bar__avatar {
    width: 24px;
    height: 24px;
    border-radius: var(--radius-full);
    background: var(--md-sys-color-secondary-container);
    color: var(--md-sys-color-on-secondary-container);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 10px;
    font-weight: 600;
    flex-shrink: 0;
  }

  /* User dropdown menu */
  .top-bar__user-dropdown {
    position: absolute;
    top: calc(100% + var(--space-1));
    right: 0;
    min-width: 180px;
    background: var(--md-sys-color-surface);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-md);
    box-shadow: var(--md-sys-elevation-2);
    z-index: 200;
    overflow: hidden;
  }

    .top-bar__user-dropdown a,.top-bar__user-dropdown button {
      display: flex;
      align-items: center;
      gap: var(--space-3);
      width: 100%;
      padding: var(--space-2) var(--space-3);
      font-size: var(--md-typescale-label-large-size);
      color: var(--md-sys-color-on-surface);
      text-decoration: none;
      background: transparent;
      border: none;
      cursor: pointer;
      text-align: left;
      transition: background var(--md-sys-motion-duration-short1);
    }

      :is(.top-bar__user-dropdown a,.top-bar__user-dropdown button):hover {
        background: color-mix(in srgb, var(--md-sys-color-on-surface) 8%, transparent);
      }

    .top-bar__user-dropdown .user-dropdown__divider {
      height: 1px;
      background: var(--md-sys-color-outline-variant);
      margin: var(--space-1) 0;
    }

    .top-bar__user-dropdown .user-dropdown__email {
      font-size: var(--md-typescale-label-small-size);
      color: var(--md-sys-color-on-surface-variant);
      padding: var(--space-2) var(--space-3) var(--space-1);
      pointer-events: none;
    }

  /* ── Mobile: hide top-bar search entirely ──
     Per-page filter bars cover search needs on mobile for Phase 1.
     Phase 2+: replace this with a full-screen search overlay (global-search
     feature spec). Until then, showing a non-functional input wastes space. */
  @media (max-width: 767px) {
    .top-bar__search {
      display: none;
    }

    /* iOS auto-zoom prevention for the search input (active on desktop,
       also applied here defensively for any viewport that shows it) */
    .top-bar__search-input {
      font-size: 16px;
    }

    /* The user-account button is the primary mobile path to My profile /
       Notifications / Sign out. It is its own class (not .btn / .icon-btn), so
       the button.css 44px mobile floor does not reach it — give it one here. */
    .top-bar__user-btn {
      min-height: 44px;
      padding: 0 var(--space-3);
    }
  }
}
/* ============================================================
   layout/detail-drawer.css — Right Detail Drawer Panel
   ------------------------------------------------------------
   The detail drawer is the right panel in the IDE shell.
   It is opened/closed by the detail_drawer Stimulus controller.
   Content is loaded via Turbo Frame when a row is clicked.

   When the drawer is CLOSED (default), it has width: 0 and is
   completely hidden but still in the DOM (the Turbo Frame is
   there, ready to be targeted). The layout grid adjusts.

   When OPEN, .drawer-open class is added and the CSS width
   transition slides the drawer into view.

   HTML contract:
     .app-drawer > turbo-frame#detail_frame
       > .detail-drawer__inner
         > .detail-drawer__header
         > .detail-drawer__content
   ============================================================ */
@layer layout {

  /* ── Outer drawer shell (grid column container) ── */
  /* Width animation is defined in layout/shell.css (.app-drawer) */

  /* ── Inner content wrapper ──
     width: 100% (not a fixed px): the ASIDE's grid column is the single
     source of width truth (normal/wide/user-resized via --drawer-user-width).
     A fixed inner width desynced from the aside was the root of a clipping
     bug (content cut at the drawer edge when the wide class and the aside
     width disagreed), and it made direct-URL renders (/inbox/:id in the
     main column) a skinny 420px strip on a 1920px screen. Content rewraps
     briefly during the 200ms open animation — acceptable. */
  .detail-drawer__inner {
    display: flex;
    flex-direction: column;
    height: 100%;
    width: 100%;
    background: var(--md-sys-color-surface);
    border-left: 1px solid var(--md-sys-color-outline-variant);
    overflow: hidden;
  }

  /* ── Resize handle (gmail-style adjustable reading pane) ──
     A thin col-resize strip on the drawer's left edge. Wired to
     drawer_resize_controller (drag to resize, double-click to reset,
     width persisted in localStorage). Desktop only — tablet/mobile use
     overlay/full-screen drawers where resizing has no meaning. */
  .drawer-resize-handle {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
    width: 6px;
    cursor: col-resize;
    z-index: 5;
    /* Invisible until hover — the drawer border-left provides the seam. */
    background: transparent;
    transition: background var(--md-sys-motion-duration-short4)
                var(--md-sys-motion-easing-standard);
  }

  .drawer-resize-handle:hover,
  .app-shell.drawer-resizing .drawer-resize-handle {
    background: color-mix(in srgb, var(--md-sys-color-primary) 35%, transparent);
  }

  @media (max-width: 1199px) {
    .drawer-resize-handle { display: none; }
  }

  /* ── Drawer header: title + action buttons ── */
  .detail-drawer__header {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: 0 var(--space-4);
    height: 48px;
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    flex-shrink: 0;
    background: var(--md-sys-color-surface);
  }

  .detail-drawer__title {
    font-size: var(--md-typescale-title-large-size);
    font-weight: var(--md-typescale-title-large-weight);
    color: var(--md-sys-color-on-surface);
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Secondary action buttons in header (e.g. "Open full page") */
  .detail-drawer__header-actions {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    flex-shrink: 0;
  }

  /* Close button */
  .detail-drawer__close {
    flex-shrink: 0;
  }

  /* ── Scrollable content area ── */
  .detail-drawer__content {
    flex: 1;
    overflow-y: auto;
    padding: var(--space-4);
    /* Smooth scroll for jump-to-tab behavior */
    scroll-behavior: smooth;
  }

  /* No-padding variant for full-bleed content (tables, etc.) */
  .detail-drawer__content--flush {
    padding: 0;
  }

  /* ── Master-detail layout helper ──
     Used on resource index pages that have list + drawer.
     .master-detail-layout wraps both .master-panel and .app-drawer. */
  .master-detail-layout {
    display: contents;  /* participates in parent grid without adding a box */
  }

  .master-panel {
    height: 100%;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    min-width: 0;
  }

  /* ── Field groups within drawer content ── */
  .drawer-field-group {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding-bottom: var(--space-4);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    margin-bottom: var(--space-4);
  }

    .drawer-field-group:last-child {
      border-bottom: none;
      margin-bottom: 0;
    }

  /* Inline key-value pairs in drawer overview */
  .drawer-kv {
    display: grid;
    grid-template-columns: 120px 1fr;
    gap: var(--space-1) var(--space-3);
    font-size: var(--md-typescale-body-small-size);
  }

    .drawer-kv dt {
      color: var(--md-sys-color-on-surface-variant);
      font-weight: 500;
      padding-top: 1px;
    }

    .drawer-kv dd {
      color: var(--md-sys-color-on-surface);
      /* Long unbreakable values (emails, hostnames) must wrap rather than
         widen the drawer column. wrap (not ellipsis): drawer values like
         postal addresses are legitimately multi-line. */
      min-width: 0;
      overflow-wrap: anywhere;
    }

  /* ── Section headers within drawer content groups ──
     Semantic <h3> element with M3 "overline" style:
     label-small type size, 600 weight, wide letter-spacing, uppercase,
     on-surface-variant colour for visual hierarchy without heavy ink.
     Usage: <h3 class="drawer-section__title">CONTACTS</h3> */
  .drawer-section__title {
    font-size: var(--md-typescale-label-small-size);
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--md-sys-color-on-surface-variant);
  }

  /* ── Inline text link utility — used in drawer content across multiple resources
     (subscriptions recent-invoices, account links, etc.) ── */
  .text-link {
    color: var(--md-sys-color-primary);
    text-decoration: none;
  }

  .text-link:hover {
    text-decoration: underline;
  }

  /* ── Mobile: full-screen drawer needs a dismiss control ──
     __back is reserved for a future back-navigation pattern.
     Until that element exists in the templates, keep __close visible
     and ensure it meets the 44 × 44 px touch target minimum. */
  .detail-drawer__back {
    display: none;
  }

  @media (max-width: 767px) {
    .detail-drawer__inner {
      width: 100vw;
    }

    /* __close IS the dismiss control on mobile — keep it visible.
       (The old rule that hid it expected a __back element that hasn't
       been added yet. Hiding __close with no fallback = drawer trap.) */
    .detail-drawer__close {
      display: flex;   /* override any inherited display:none */
      min-width: 44px;
      min-height: 44px;
      align-items: center;
      justify-content: center;
    }
  }
}
/* --- Components (reusable UI building blocks) --- */
/* ── Avatar ──────────────────────────────────────────────────────────────────
   Shared component used by avatar_tag (ApplicationHelper): the top-bar chip,
   post bylines, and comment bylines. ONE rule so every avatar — image or
   initial-fallback — is a centred circle at the size the helper passes inline
   (width/height attributes on the <img>, inline style on the fallback span).

   The page-specific .top-bar__avatar (24px chip) and .profile-avatar (96px hero)
   keep their own sizing; this is the reusable base for the bylines + chip image. */
.avatar {
  border-radius: var(--radius-full);
  -o-object-fit: cover;
     object-fit: cover; /* image variant: fill the square crop without distortion */
  flex-shrink: 0;    /* never squashed by a tight flex byline row */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  vertical-align: middle;
}
/* Initial-circle fallback (no uploaded avatar). Same secondary-container look as
   the original .top-bar__avatar so the fallback reads as an avatar, not a chip.
   Size comes from the inline width/height the helper sets per call site. */
.avatar--initial {
  background: var(--md-sys-color-secondary-container);
  color: var(--md-sys-color-on-secondary-container);
  font-size: 0.75em;
  font-weight: 600;
  line-height: 1;
  text-transform: uppercase;
}
/* ============================================================
   components/button.css — M3 Button Variants
   ------------------------------------------------------------
   M3 button hierarchy maps to action importance:

     .btn.btn--filled       Primary CTA ("Save", "Create Invoice")
     .btn.btn--tonal        Secondary action ("Add Time Entry")
     .btn.btn--outlined     Neutral ("Cancel", "Export")
     .btn.btn--text         Low priority ("View full page", "Dismiss")
     .btn.btn--destructive  Danger (confirm dialogs only)

   All buttons use a state-layer ::after overlay for hover/press.
   Size modifier: .btn--sm reduces height to 28px.

   Icon-only variant: .icon-btn (32x32, no text).
   ============================================================ */
@layer components {

  /* ── Base button ── */
  .btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    height: var(--density-button-height);
    padding: 0 var(--space-4);
    border-radius: var(--radius-full);
    font-size: var(--md-typescale-label-large-size);
    font-weight: var(--md-typescale-label-large-weight);
    font-family: inherit;
    cursor: pointer;
    border: none;
    text-decoration: none;
    white-space: nowrap;
    -webkit-user-select: none;
       -moz-user-select: none;
            user-select: none;
    /* State layer */
    position: relative;
    overflow: hidden;
    transition: box-shadow var(--md-sys-motion-duration-short1);
    vertical-align: middle;

    /* State layer overlay via ::after */
  }
    .btn::after {
      content: "";
      position: absolute;
      inset: 0;
      background: currentColor;
      opacity: 0;
      transition: opacity var(--md-sys-motion-duration-short2);
      border-radius: inherit;
      pointer-events: none;
    }

    .btn:hover::after     { opacity: var(--md-sys-state-hover-opacity); }
    .btn:focus-visible::after { opacity: var(--md-sys-state-focus-opacity); }
    .btn:active::after    { opacity: var(--md-sys-state-pressed-opacity); }

    .btn[disabled],.btn:disabled {
      opacity: var(--md-sys-state-disabled-opacity);
      cursor: not-allowed;
      pointer-events: none;
    }

  /* ── Filled: primary action — use sparingly (1 per view) ── */
  .btn--filled {
    background: var(--md-sys-color-primary);
    color: var(--md-sys-color-on-primary);
  }

    .btn--filled:hover {
      box-shadow: var(--md-sys-elevation-1);
    }

  /* ── Tonal: secondary action — most common action button ── */
  .btn--tonal {
    background: var(--md-sys-color-secondary-container);
    color: var(--md-sys-color-on-secondary-container);
  }

    .btn--tonal:hover {
      box-shadow: var(--md-sys-elevation-1);
    }

  /* ── Outlined: neutral / cancel / export ── */
  .btn--outlined {
    background: transparent;
    color: var(--md-sys-color-primary);
    border: 1px solid var(--md-sys-color-outline);
  }

  /* ── Text: minimal visual weight, inline actions ── */
  .btn--text {
    background: transparent;
    color: var(--md-sys-color-primary);
    padding: 0 var(--space-3);  /* less horizontal padding */
  }

  /* ── Destructive: only in confirm dialogs ── */
  .btn--destructive {
    background: var(--md-sys-color-error);
    color: var(--md-sys-color-on-error);
  }

    .btn--destructive:hover {
      box-shadow: var(--md-sys-elevation-1);
    }

  /* ── Size modifier: small (28px) ── */
  .btn--sm {
    height: 28px;
    padding: 0 var(--space-3);
    font-size: var(--md-typescale-label-medium-size);
  }

  /* ── Size modifier: large (40px) ──
     Primary CTA on terminal/landing surfaces (the QR scan-landing pages, where
     the button is the last action in a phone flow). The base .btn would render at
     32px; this bumps height + horizontal padding so the CTA reads as the main
     action on tablet/desktop. On mobile the base .btn min-height: 44px rule below
     already governs the tap target, so no separate mobile override is needed. */
  .btn--lg {
    height: 40px;
    padding: 0 var(--space-6);
    font-size: var(--md-typescale-body-medium-size);
  }

  /* ── Icon inside button ── */
  .btn__icon {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
  }

  /* ── Icon-only button (no text, square) ── */
  .icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: var(--radius-sm);
    background: transparent;
    border: none;
    cursor: pointer;
    color: var(--md-sys-color-on-surface-variant);
    flex-shrink: 0;
    /* State layer */
    position: relative;
    overflow: hidden;
    transition: color var(--md-sys-motion-duration-short2);
  }

    .icon-btn::after {
      content: "";
      position: absolute;
      inset: 0;
      background: currentColor;
      opacity: 0;
      border-radius: inherit;
      pointer-events: none;
      transition: opacity var(--md-sys-motion-duration-short2);
    }

    .icon-btn:hover {
      color: var(--md-sys-color-on-surface);
    }

      .icon-btn:hover::after { opacity: var(--md-sys-state-hover-opacity); }

    .icon-btn:focus-visible::after { opacity: var(--md-sys-state-focus-opacity); }
    .icon-btn:active::after { opacity: var(--md-sys-state-pressed-opacity); }

    .icon-btn svg {
      width: 20px;
      height: 20px;
    }

  /* Rounded icon button variant */
  .icon-btn--rounded {
    border-radius: var(--radius-full);
  }

  /* ── Mobile touch targets ── */
  @media (max-width: 767px) {
    /* WCAG 2.5.5 / Apple HIG minimum touch target: 44 × 44 px.
       Visual height/padding stays the same — only min-height grows so
       the tap area is large enough without affecting desktop density. */
    .btn {
      min-height: 44px;
    }

    .btn--sm {
      min-height: 44px;
    }

    .icon-btn {
      min-width: 44px;
      min-height: 44px;
    }
  }
}
@layer components {
  /* A count badge inside a tonal button blends into the button surface —
     invert it so the count stays legible (used by the Inbox header CTA). */
  .btn--tonal .count-badge {
    background: var(--md-sys-color-on-secondary-container);
    color: var(--md-sys-color-secondary-container);
  }
}
/* ============================================================
   components/chip.css — Status Chips
   ------------------------------------------------------------
   Chips display categorical status values: pipeline stages,
   invoice status, task status.

   Tinted backgrounds use color-mix() (Chrome 111+, FF 113+,
   Safari 16.2+) — supported in all modern browsers.

   HTML contract:
     <span class="chip chip--stage-won">Won</span>
     <span class="chip chip--invoice-overdue">Overdue</span>
     <span class="chip chip--task-blocked">Blocked</span>
   ============================================================ */
@layer components {

  /* ── Base chip ── */
  .chip {
    display: inline-flex;
    align-items: center;
    height: var(--density-chip-height);
    padding: 0 var(--space-2);
    border-radius: var(--radius-full);
    font-size: var(--md-typescale-label-small-size);
    font-weight: var(--md-typescale-label-small-weight);
    white-space: nowrap;
    line-height: 1;
    letter-spacing: 0.01em;
  }

  /* ── Pipeline Stage chips ── */

  /* Prospect — earliest pipeline stage; shares the lead colour palette
     because it's essentially a "pre-lead" state. */
  .chip--stage-prospect {
    background: color-mix(in srgb, var(--color-stage-lead) 15%, transparent);
    color: var(--color-stage-lead);
  }

  .chip--stage-lead {
    background: color-mix(in srgb, var(--color-stage-lead) 15%, transparent);
    color: var(--color-stage-lead);
  }

  .chip--stage-qualified {
    background: color-mix(in srgb, var(--color-stage-qualified) 15%, transparent);
    color: var(--color-stage-qualified);
  }

  .chip--stage-proposal {
    background: color-mix(in srgb, var(--color-stage-proposal) 15%, transparent);
    color: var(--color-stage-proposal);
  }

  .chip--stage-negotiation {
    background: color-mix(in srgb, var(--color-stage-negotiation) 15%, transparent);
    color: var(--color-stage-negotiation);
  }

  .chip--stage-won {
    background: color-mix(in srgb, var(--color-stage-won) 15%, transparent);
    color: var(--color-stage-won);
  }

  .chip--stage-lost {
    background: color-mix(in srgb, var(--color-stage-lost) 15%, transparent);
    color: var(--color-stage-lost);
  }

  /* ── Invoice status chips ── */
  .chip--invoice-draft {
    background: color-mix(in srgb, var(--color-status-draft) 12%, transparent);
    color: var(--color-status-draft);
  }

  .chip--invoice-sent {
    background: color-mix(in srgb, var(--color-status-sent) 15%, transparent);
    color: var(--color-status-sent);
  }

  .chip--invoice-paid {
    background: color-mix(in srgb, var(--color-status-paid) 15%, transparent);
    color: var(--color-status-paid);
  }

  .chip--invoice-overdue {
    background: color-mix(in srgb, var(--color-status-overdue) 15%, transparent);
    color: var(--color-status-overdue);
  }

  /* void chip — a neutral dark state indicating a cancelled invoice */
  .chip--invoice-void {
    background: color-mix(in srgb, var(--md-sys-color-on-surface-variant) 12%, transparent);
    color: var(--md-sys-color-on-surface-variant);
    text-decoration: line-through;
  }

  /* invoiced chip — primary-container tint for billed time entries */
  .chip--invoiced {
    background: var(--md-sys-color-primary-container);
    color: var(--md-sys-color-on-primary-container);
  }

  /* ── Task status chips ── */
  .chip--task-todo {
    background: color-mix(in srgb, var(--color-task-todo) 12%, transparent);
    color: var(--color-task-todo);
  }

  .chip--task-in-progress {
    background: color-mix(in srgb, var(--color-task-in-progress) 15%, transparent);
    color: var(--color-task-in-progress);
  }

  .chip--task-done {
    background: color-mix(in srgb, var(--color-task-done) 12%, transparent);
    color: var(--color-task-done);
  }

  .chip--task-blocked {
    background: color-mix(in srgb, var(--color-task-blocked) 15%, transparent);
    color: var(--color-task-blocked);
  }

  /* ── Generic semantic chips ── */
  .chip--success {
    background: color-mix(in srgb, var(--color-stage-won) 12%, transparent);
    color: var(--color-stage-won);
  }

  .chip--warning {
    background: color-mix(in srgb, var(--md-sys-color-tertiary) 12%, transparent);
    color: var(--md-sys-color-tertiary);
  }

  .chip--error {
    background: color-mix(in srgb, var(--md-sys-color-error) 12%, transparent);
    color: var(--md-sys-color-error);
  }

  .chip--neutral {
    background: var(--md-sys-color-surface-container);
    color: var(--md-sys-color-on-surface-variant);
  }

  /* Primary-tinted chip — e.g. a deployed asset (actively in service). */
  .chip--primary {
    background: color-mix(in srgb, var(--md-sys-color-primary) 12%, transparent);
    color: var(--md-sys-color-primary);
  }

  /* Muted chip — retired/cancelled/inactive things. Lives here (not in a
     page CSS file) so every screen that shows these states gets it. */
  .chip--muted {
    background: color-mix(in srgb, var(--md-sys-color-on-surface) 6%, transparent);
    color: var(--md-sys-color-on-surface-variant);
  }

  /* ── Canonical count badge — chip-adjacent utility ──────────────────────
     Displays a numeric count next to a section title or heading.
     Canonical definition lives here (chip-adjacent pattern); page files
     that previously duplicated this (accounts.css, trainings.css) should
     defer to this definition. Reconciled to the accounts.css version:
     18px height, surface-container-high background, 10px font-size.
     ─────────────────────────────────────────────────────────────────────── */
  .count-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: var(--radius-full);
    background: var(--md-sys-color-surface-container-high);
    color: var(--md-sys-color-on-surface-variant);
    font-size: 10px;
    font-weight: 600;
    vertical-align: middle;
    margin-left: var(--space-1);
  }

  /* ── Filter chip — interactive anchor chip for folder/category filters ──
     Used in the mail portal folder rail: Inbox, Sent, Archived, All.
     This is an <a> element (not a <span>), so it gets full hover + current
     states. The state layer uses ::after so it doesn't affect text colour.
     [aria-current="true"] signals the active folder (filled/selected state).
     Height matches --density-chip-height (24px) for visual consistency
     with status chips.
     ─────────────────────────────────────────────────────────────────────── */
  .chip--filter {
    /* Layout — same as base .chip but anchor-appropriate */
    display: inline-flex;
    align-items: center;
    height: var(--density-chip-height);   /* 24px — matches all chips */
    padding: 0 var(--space-3);
    border-radius: var(--radius-full);
    gap: var(--space-1);
    font-size: var(--md-typescale-label-small-size);
    font-weight: var(--md-typescale-label-small-weight);
    white-space: nowrap;
    line-height: 1;
    letter-spacing: 0.01em;
    text-decoration: none;

    /* Default (unselected) appearance */
    background: transparent;
    color: var(--md-sys-color-on-surface-variant);
    border: 1px solid var(--md-sys-color-outline-variant);

    /* State layer pattern — ::after overlay for hover/focus, never hex colours */
    position: relative;
    overflow: hidden;
    cursor: pointer;
    transition: border-color var(--md-sys-motion-duration-short2),
                background var(--md-sys-motion-duration-short2),
                color var(--md-sys-motion-duration-short2);
  }

  /* State layer overlay (M3 pattern — tints with currentColor) */
  .chip--filter::after {
    content: "";
    position: absolute;
    inset: 0;
    background: currentColor;
    opacity: 0;
    transition: opacity var(--md-sys-motion-duration-short2);
    pointer-events: none;
    border-radius: inherit;
  }

  .chip--filter:hover::after {
    opacity: var(--md-sys-state-hover-opacity);   /* 0.08 */
  }

  .chip--filter:focus-visible {
    outline: 2px solid var(--md-sys-color-primary);
    outline-offset: 2px;
  }

  .chip--filter:focus-visible::after {
    opacity: var(--md-sys-state-focus-opacity);   /* 0.12 */
  }

  /* Selected/active state — primary-container filled (M3 "selected filter chip") */
  .chip--filter[aria-current="true"] {
    background: var(--md-sys-color-primary-container);
    color: var(--md-sys-color-on-primary-container);
    border-color: transparent;
    font-weight: 600;
  }

  /* ── Phase 7a: Proficiency chips for the skills matrix ── */

  /* Expert chip — a distinct strong tint to stand out from proficient (chip--success).
     Uses the tertiary (warm-accent) color so all four proficiency levels are visually
     distinct: neutral < primary < success < tertiary-strong.
     Lives in components/chip.css (not pages/skills.css) because proficiency chips
     appear in the matrix, the "mine" page, the drawer, and potentially trainings. */
  .chip--expert {
    background: color-mix(in srgb, var(--md-sys-color-tertiary) 20%, transparent);
    color: var(--md-sys-color-tertiary);
    font-weight: 700;   /* slightly bolder than the other levels — marks high expertise */
  }

  /* ── The Commons: post-kind chips ──
     One tint per post kind so the feed is scannable at a glance. Reuse the
     existing semantic colour roles so light + dark both work via tokens. */
  .chip--post-announcement {
    background: var(--md-sys-color-surface-container);
    color: var(--md-sys-color-on-surface-variant);
  }
  .chip--post-equipment {
    background: color-mix(in srgb, var(--md-sys-color-primary) 12%, transparent);
    color: var(--md-sys-color-primary);
  }
  .chip--post-skill {
    background: color-mix(in srgb, var(--md-sys-color-tertiary) 14%, transparent);
    color: var(--md-sys-color-tertiary);
  }

  /* ── Mobile touch targets (commons review) ──
     Filter chips (the /commons kind tabs) and chips used as navigation links
     (topic chips) are 24px tall by default — below the WCAG 2.5.5 / Apple HIG
     44px minimum. Grow the tap area on small screens without changing the
     desktop density. Matches the 767px breakpoint in components/button.css. */
  @media (max-width: 767px) {
    .chip--filter {
      min-height: 44px;
      padding: 0 var(--space-4);
    }

    a.chip--neutral {
      min-height: 44px;
    }
  }
}
/* ============================================================
   components/data-table.css — Dense Data Table
   ------------------------------------------------------------
   Used by: Accounts, Contacts, Opportunities, Projects,
            Invoices, Assets, Time Entries lists.

   Key density specs from the design system:
     Row height:    34px (vs M3 default 52px)
     Header height: 36px
     Cell padding:  12px horizontal, 0px vertical (row-height handles it)
     Font size:     13px body-medium for cells, 12px label-medium for headers

   Sorting is server-side via Turbo Frame links — no JS sort controller.
   aria-sort is set server-side in the view.

   HTML contract:
     .data-table-wrapper > table.data-table
       > thead > tr.data-table__header-row
         > th.data-table__th [.--sortable] [.--number] [.--actions]
       > tbody > tr.data-table__row [.row-selected]
         > td.data-table__td [.--number] [.--actions]
   ============================================================ */
@layer components {

  /* ── Scroll wrapper (handles overflow on small screens) ── */
  .data-table-wrapper {
    overflow-x: auto;
    flex: 1;
    -webkit-overflow-scrolling: touch;
  }

  /* ── Table ── */
  .data-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--md-typescale-body-medium-size);
    color: var(--md-sys-color-on-surface);
  }

  /* ── Header row ── */
  .data-table__header-row {
    position: sticky;
    top: 0;
    background: var(--md-sys-color-surface-container);
    z-index: 10;
  }

  /* ── Header cells ── */
  .data-table__th {
    height: var(--density-table-header-height);
    padding: 0 var(--density-table-cell-px);
    text-align: left;
    font-size: var(--md-typescale-label-medium-size);
    font-weight: var(--md-typescale-label-medium-weight);
    color: var(--md-sys-color-on-surface-variant);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    white-space: nowrap;
    -webkit-user-select: none;
       -moz-user-select: none;
            user-select: none;
    vertical-align: middle;
  }

  /* Sortable column headers */
  .data-table__th--sortable {
    cursor: pointer;
  }

    .data-table__th--sortable:hover {
      color: var(--md-sys-color-on-surface);
    }

  /* Number columns: right-aligned */
  .data-table__th--number {
    text-align: right;
  }

  /* Actions column: fixed width, centered */
  .data-table__th--actions {
    width: 48px;
    text-align: center;
  }

  /* Sort indicator arrows (set via aria-sort attribute server-side) */
  .sort-indicator {
    display: inline-block;
    width: 12px;
    font-size: 10px;
    margin-left: 2px;
    vertical-align: middle;
  }

  [aria-sort="ascending"]  .sort-indicator::after { content: "↑"; }
  [aria-sort="descending"] .sort-indicator::after { content: "↓"; }
  [aria-sort="none"]       .sort-indicator::after { content: "↕"; opacity: 0.4; }

  /* ── Body rows ── */
  .data-table__row {
    height: var(--density-table-row-height);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    transition: background var(--md-sys-motion-duration-short2);
    cursor: pointer;
  }

    .data-table__row:hover {
      background: color-mix(in srgb, var(--md-sys-color-primary) 6%, transparent);
    }

    /* Keyboard focus state for accessible row navigation */
    .data-table__row:focus-visible {
      outline: 2px solid var(--md-sys-color-primary);
      outline-offset: -2px;
    }

  /* Selected row state (set by row_select Stimulus controller) */
  .data-table__row.row-selected {
    background: var(--md-sys-color-primary-container);
    color: var(--md-sys-color-on-primary-container);
  }

    .data-table__row.row-selected:hover {
      background: color-mix(in srgb, var(--md-sys-color-primary-container) 90%, var(--md-sys-color-primary));
    }

    /* Chips tint themselves against the row background via color-mix —
       on the saturated selected-row color that produced an illegible
       muddy pill in dark mode (WCAG fail). Force a neutral chip surface
       here; the chip TEXT carries the semantic meaning. */
    .data-table__row.row-selected .chip {
      background: var(--md-sys-color-surface-container-high);
      color: var(--md-sys-color-on-surface);
    }

  /* ── Body cells ── */
  .data-table__td {
    padding: 0 var(--density-table-cell-px);
    height: var(--density-table-row-height);
    vertical-align: middle;
    color: var(--md-sys-color-on-surface);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 280px;

    /* Links within cells */
  }
    .data-table__td a {
      color: inherit;
      text-decoration: none;
    }

      :is(.data-table__td a):hover {
        color: var(--md-sys-color-primary);
        text-decoration: underline;
        text-decoration-thickness: 1px;
        text-underline-offset: 2px;
      }

  /* Right-aligned number columns (currency, counts) */
  .data-table__td--number {
    text-align: right;
    font-variant-numeric: tabular-nums;
  }

  /* Action cell (kebab menu) */
  .data-table__td--actions {
    width: 48px;
    text-align: center;
    overflow: visible;  /* allow dropdown to escape */
  }

  /* Muted/secondary text within cells */
  .data-table__td--muted {
    color: var(--md-sys-color-on-surface-variant);
    font-size: var(--md-typescale-body-small-size);
  }

  /* ── Compact variant for mobile ── */
  @media (max-width: 767px) {
    .data-table__th,
    .data-table__td {
      padding: 0 var(--space-2);  /* tighter on mobile */
    }

    .data-table__td {
      max-width: 160px;
    }

    /* Bump header font one step up (label-medium → label-large) so
       column labels don't disappear on small screens. */
    .data-table__th {
      font-size: var(--md-typescale-label-large-size);
    }

    /* Opt-in column hiding for narrow screens. Views tag their least
       important columns (e.g. secondary dates) so the columns that drive
       decisions — status chips, balances — stay on-screen at 390px
       instead of hanging past the right edge. */
    .data-table__th--hide-mobile,
    .data-table__td--hide-mobile {
      display: none;
    }
  }
}
/* ============================================================
   components/filter-bar.css — Filter / Search Bar
   ------------------------------------------------------------
   The filter bar appears at the top of list views (above the
   data table). It contains a text search input and optional
   dropdown filters for stage, status, date range, etc.

   The filter_bar Stimulus controller debounces text input
   (300ms) and submits the form targeting the list Turbo Frame,
   causing a server-side re-render without full page navigation.

   HTML contract:
     .filter-bar
       .filter-bar__search
         .filter-bar__search-icon (SVG)
         input.filter-bar__input
       form.filter-bar__form
         select.filter-bar__select
         .filter-bar__actions
           .btn.btn--outlined.btn--sm (Clear)
   ============================================================ */
@layer components {

  /* ── Filter bar container ── */
  .filter-bar {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    background: var(--md-sys-color-surface);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    flex-shrink: 0;
    flex-wrap: wrap;
  }

  /* ── Search field with icon ── */
  .filter-bar__search {
    position: relative;
    flex: 1;
    min-width: 180px;
    max-width: 320px;
  }

  .filter-bar__search-icon {
    position: absolute;
    left: var(--space-2);
    top: 50%;
    transform: translateY(-50%);
    color: var(--md-sys-color-on-surface-variant);
    display: flex;
    align-items: center;
    pointer-events: none;
  }

  .filter-bar__input {
    width: 100%;
    height: 30px;
    background: var(--md-sys-color-surface-container);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-full);
    padding: 0 var(--space-3) 0 var(--space-7);  /* room for icon */
    font-size: var(--md-typescale-body-medium-size);
    color: var(--md-sys-color-on-surface);
    outline: none;
    transition: border-color var(--md-sys-motion-duration-short2);
  }

    .filter-bar__input::-moz-placeholder {
      color: var(--md-sys-color-on-surface-variant);
    }

    .filter-bar__input::placeholder {
      color: var(--md-sys-color-on-surface-variant);
    }

    .filter-bar__input:focus {
      border-color: var(--md-sys-color-primary);
      background: var(--md-sys-color-surface);
    }

  /* ── Filter form (wraps selects) ── */
  .filter-bar__form {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
  }

  /* ── Dropdown filter selects ── */
  .filter-bar__select {
    height: 30px;
    padding: 0 var(--space-3);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-sm);
    background: var(--md-sys-color-surface);
    color: var(--md-sys-color-on-surface);
    font-size: var(--md-typescale-label-large-size);
    font-family: inherit;
    cursor: pointer;
    outline: none;
    transition: border-color var(--md-sys-motion-duration-short2);
  }

    .filter-bar__select:focus {
      border-color: var(--md-sys-color-primary);
    }

  /* ── Actions at end of filter bar ── */
  .filter-bar__actions {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-left: auto;
  }

  /* ── Active filter count badge ── */
  .filter-bar__active-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: var(--radius-full);
    background: var(--md-sys-color-primary);
    color: var(--md-sys-color-on-primary);
    font-size: 10px;
    font-weight: 600;
  }

  /* ── Mobile: stack on small screens ── */
  @media (max-width: 600px) {
    .filter-bar {
      padding: var(--space-2) var(--space-3);
    }

    .filter-bar__search {
      max-width: 100%;
      width: 100%;
    }
  }

  /* ── iOS auto-zoom + minimum tap height on mobile ── */
  @media (max-width: 767px) {
    /* font-size 16px prevents iOS Safari from auto-zooming on focus */
    .filter-bar__input,
    .filter-bar__select {
      font-size: 16px;
    }

    /* Comfortable tap height for filter controls */
    .filter-bar__input,
    .filter-bar__select {
      height: 40px;
    }
  }
}
/* ============================================================
   components/text-field.css — M3 Outlined Dense Text Field
   ------------------------------------------------------------
   Implements M3 outlined text field with floating label.
   Height: 36px (vs M3 default 56px) for dense ops UI.

   The floating label uses a pure CSS technique:
   - Label starts centered vertically (placeholder state)
   - When input is focused OR has content (:not(:placeholder-shown)),
     the label floats to the top border, scales down, and shows
     the primary color

   Note: placeholder=" " (a space) is required on the <input>
   to make :not(:placeholder-shown) work as a "has value" selector.

   HTML contract (auth-views agent must use this exactly):
     <div class="field">
       <input class="field__input" type="text" id="..." name="..."
              placeholder=" " required>
       <label class="field__label" for="...">Field name</label>
     </div>

   Error state: add .field--error to the .field wrapper.
   Disabled: add disabled attribute to input.
   ============================================================ */
@layer components {

  /* ── Field wrapper ── */
  .field {
    position: relative;
    display: flex;
    flex-direction: column;
    width: 100%;
  }

  /* ── Input ── */
  .field__input {
    height: var(--density-form-field-height);
    padding: 0 var(--density-form-field-px);
    padding-top: 12px;   /* makes room for floated label */
    font-size: var(--md-typescale-body-medium-size);
    font-family: inherit;
    color: var(--md-sys-color-on-surface);
    background: transparent;
    border: 1px solid var(--md-sys-color-outline);
    border-radius: var(--radius-sm);
    outline: none;
    width: 100%;
    transition: border-color var(--md-sys-motion-duration-short2),
                border-width var(--md-sys-motion-duration-short2);
  }

    .field__input::-moz-placeholder {
      /* Transparent — the floating label IS the placeholder */
      color: transparent;
    }

    .field__input::placeholder {
      /* Transparent — the floating label IS the placeholder */
      color: transparent;
    }

    .field__input:focus {
      border-color: var(--md-sys-color-primary);
      border-width: 2px;
      /* Compensate for extra border-width to avoid layout shift */
      padding: 0 calc(var(--density-form-field-px) - 1px);
      padding-top: 11px;
    }

    .field__input:disabled {
      opacity: var(--md-sys-state-disabled-opacity);
      cursor: not-allowed;
      background: var(--md-sys-color-surface-container);
    }

  /* ── Floating label ── */
  .field__label {
    position: absolute;
    left: var(--density-form-field-px);
    top: 50%;
    transform: translateY(-50%);
    font-size: var(--md-typescale-body-medium-size);
    color: var(--md-sys-color-on-surface-variant);
    pointer-events: none;
    transition:
      top var(--md-sys-motion-duration-short2) var(--md-sys-motion-easing-standard),
      font-size var(--md-sys-motion-duration-short2) var(--md-sys-motion-easing-standard),
      color var(--md-sys-motion-duration-short2);
    /* Label bg clips the border line behind the text */
    background: var(--md-sys-color-surface);
    padding: 0 4px;
    line-height: 1;
  }

  /* Float label: on focus or when input has content */
  .field__input:not(:-moz-placeholder) ~ .field__label {
    top: 0;
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-on-surface-variant);
  }
  .field__input:focus ~ .field__label,
  .field__input:not(:placeholder-shown) ~ .field__label {
    top: 0;
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-on-surface-variant);
  }

  /* Primary color on focus */
  .field__input:focus ~ .field__label {
    color: var(--md-sys-color-primary);
  }

  /* ── Helper / error text below field ── */
  .field__helper {
    margin-top: var(--space-1);
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-on-surface-variant);
    padding: 0 var(--density-form-field-px);
  }

  .field__error-text {
    margin-top: var(--space-1);
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-error);
    padding: 0 var(--density-form-field-px);
  }

  /* Alias for field__helper — "help-text" reads more clearly in ERB templates. */
  .field__help-text {
    margin-top: var(--space-1);
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-on-surface-variant);
    padding: 0 var(--density-form-field-px);
  }

  /* ── Error state ── */
  .field--error .field__input {
    border-color: var(--md-sys-color-error);
  }

    :is(.field--error .field__input):focus {
      border-color: var(--md-sys-color-error);
    }

  .field--error .field__input:not(:-moz-placeholder) ~ .field__label {
    color: var(--md-sys-color-error);
  }

  .field--error .field__input:focus ~ .field__label,
  .field--error .field__input:not(:placeholder-shown) ~ .field__label,
  .field--error .field__label {
    color: var(--md-sys-color-error);
  }

  /* ── Textarea variant ── */
  .field__input--textarea {
    height: auto;
    min-height: 80px;
    padding-top: 20px;
    resize: vertical;
  }

  /* ── Row layout for side-by-side fields ── */
  .field-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-4);
  }

  @media (max-width: 600px) {
    .field-row {
      grid-template-columns: 1fr;
    }
  }

  /* ── Mobile form field sizing ──
     iOS auto-zoom: font-size ≥ 16px prevents Safari from zooming on input focus.
     Touch height: 44px minimum per WCAG 2.5.5 / Apple HIG.
     Float-label padding-top is increased to match so the label doesn't overlap
     input text at the taller height. */
  @media (max-width: 767px) {
    .field__input {
      font-size: 16px;
      height: 44px;
      padding-top: 16px;   /* extra room so label floats clear of input text */
    }

    .field__input:focus {
      padding-top: 15px;   /* compensate for 2px border-width on focus */
    }
  }

  /* ── Form section: groups related fields ── */
  .form-section {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding-bottom: var(--space-4);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    margin-bottom: var(--space-4);
  }

    .form-section:last-child {
      border-bottom: none;
      margin-bottom: 0;
    }

  .form-section__title {
    font-size: var(--md-typescale-title-small-size);
    font-weight: var(--md-typescale-title-small-weight);
    color: var(--md-sys-color-on-surface-variant);
    text-transform: uppercase;
    letter-spacing: 0.06em;
  }

  /* Informational note within a form section (e.g. invite email explainer). */
  .form-section__note {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    background: var(--md-sys-color-surface-container-low);
    border-left: 3px solid var(--md-sys-color-primary);
    padding: var(--space-2) var(--space-3);
    border-radius: 0 var(--radius-sm, 4px) var(--radius-sm, 4px) 0;
  }
}
/* ============================================================
   components/select.css — Select / Dropdown Inputs
   ------------------------------------------------------------
   Styled select elements that match the text-field height
   and visual treatment. Uses the same .field wrapper pattern.

   HTML contract:
     <div class="field">
       <label class="field__label--static" for="...">Label</label>
       <select class="field__select" id="..." name="...">
         <option>...</option>
       </select>
     </div>

   Note: Select elements cannot use the floating label trick
   reliably, so we use a static positioned label.
   ============================================================ */
@layer components {

  /* ── Static label variant (for selects, checkboxes, radios) ── */
  .field__label--static {
    font-size: var(--md-typescale-label-medium-size);
    font-weight: var(--md-typescale-label-medium-weight);
    color: var(--md-sys-color-on-surface-variant);
    margin-bottom: var(--space-1);
    display: block;
  }

  /* ── Select input ── */
  .field__select {
    height: var(--density-form-field-height);
    padding: 0 var(--space-8) 0 var(--density-form-field-px);
    font-size: var(--md-typescale-body-medium-size);
    font-family: inherit;
    color: var(--md-sys-color-on-surface);
    background: var(--md-sys-color-surface);
    border: 1px solid var(--md-sys-color-outline);
    border-radius: var(--radius-sm);
    outline: none;
    width: 100%;
    cursor: pointer;
    /* Custom arrow */
    -moz-appearance: none;
         appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%238A9BB5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right var(--space-2) center;
    transition: border-color var(--md-sys-motion-duration-short2);
  }

    .field__select:focus {
      border-color: var(--md-sys-color-primary);
      border-width: 2px;
    }

    .field__select:disabled {
      opacity: var(--md-sys-state-disabled-opacity);
      cursor: not-allowed;
    }

  /* ── Checkbox / radio field ── */
  .field--checkbox {
    flex-direction: row;
    align-items: center;
    gap: var(--space-2);
    height: var(--density-form-field-height);
  }

    .field--checkbox .field__label--static {
      margin-bottom: 0;
      cursor: pointer;
    }

  .field__checkbox,
  .field__radio {
    width: 16px;
    height: 16px;
    accent-color: var(--md-sys-color-primary);
    cursor: pointer;
    flex-shrink: 0;
  }
}
/* ============================================================
   components/card.css — Cards (Stat, Content, Activity)
   ------------------------------------------------------------
   Cards are surface containers that group related content.

   Variants:
     .stat-card        — Dashboard KPI numbers
     .card             — Generic content card
     .card--elevated   — Raised card with shadow

   HTML contract (stat-card):
     <div class="stat-card">
       <div class="stat-card__label">Pipeline Value</div>
       <div class="stat-card__value">$84,500</div>
       <div class="stat-card__sub">12 opportunities</div>
     </div>
   ============================================================ */
@layer components {

  /* ── Generic content card ── */
  .card {
    background: var(--md-sys-color-surface);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-lg);
    padding: var(--density-card-p);
  }

  .card--elevated {
    border: none;
    box-shadow: var(--md-sys-elevation-1);
  }

  .card--interactive {
    cursor: pointer;
    transition: box-shadow var(--md-sys-motion-duration-short2),
                border-color var(--md-sys-motion-duration-short2);
  }

    .card--interactive:hover {
      box-shadow: var(--md-sys-elevation-2);
      border-color: var(--md-sys-color-outline);
    }

  .card__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: var(--space-3);
  }

  .card__title {
    font-size: var(--md-typescale-title-medium-size);
    font-weight: var(--md-typescale-title-medium-weight);
    color: var(--md-sys-color-on-surface);
  }

  .card__body {
    font-size: var(--md-typescale-body-medium-size);
    color: var(--md-sys-color-on-surface);
  }

  /* ── Stat card — dashboard KPI ── */
  .stat-card {
    background: var(--md-sys-color-surface);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-lg);
    padding: var(--density-card-p);
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
  }

  .stat-card__label {
    font-size: var(--md-typescale-label-medium-size);
    font-weight: var(--md-typescale-label-medium-weight);
    color: var(--md-sys-color-on-surface-variant);
    text-transform: uppercase;
    letter-spacing: 0.06em;
  }

  .stat-card__value {
    font-size: var(--md-typescale-display-size);
    font-weight: var(--md-typescale-display-weight);
    line-height: var(--md-typescale-display-line-height);
    color: var(--md-sys-color-on-surface);
    font-variant-numeric: tabular-nums;
    letter-spacing: var(--md-typescale-display-letter-spacing);
  }

  .stat-card__sub {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
  }

  /* Trend indicator within stat card */
  .stat-card__trend {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    font-size: var(--md-typescale-label-small-size);
    font-weight: 500;
  }

  .stat-card__trend--up   { color: var(--color-stage-won); }
  .stat-card__trend--down { color: var(--md-sys-color-error); }
  .stat-card__trend--flat { color: var(--md-sys-color-on-surface-variant); }

  /* ── Dashboard stats grid ── */
  .dashboard-stats {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: var(--space-4);
    padding: var(--space-4);
  }

  @media (max-width: 767px) {
    .dashboard-stats {
      grid-template-columns: repeat(2, 1fr);
      padding: var(--space-3);
    }
  }
}
/* ============================================================
   components/tabs.css — Horizontal Tab Navigation
   ------------------------------------------------------------
   Used within the detail drawer to switch between resource
   sections: Overview / Activity / Projects / Invoices / etc.

   Tab switching uses Turbo Frame links — active class is
   set server-side. No Stimulus controller required.

   HTML contract:
     div[role="tablist"].tabs
       a.tab [.tab--active]   (Turbo Frame link)
       a.tab
   ============================================================ */
@layer components {

  /* ── Tab list container ── */
  .tabs {
    display: flex;
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    overflow-x: auto;
    scrollbar-width: none;  /* hide scrollbar on Firefox */
    flex-shrink: 0;
  }

    .tabs::-webkit-scrollbar {
      display: none;  /* hide scrollbar on Chrome/Safari */
    }
  .tabs {

    /* Touch momentum scroll on iOS */
    -webkit-overflow-scrolling: touch;
  }

  /* ── Individual tab ── */
  .tab {
    padding: var(--space-2) var(--space-4);
    font-size: var(--md-typescale-label-large-size);
    font-weight: var(--md-typescale-label-large-weight);
    color: var(--md-sys-color-on-surface-variant);
    text-decoration: none;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;   /* overlap the container border */
    white-space: nowrap;
    flex-shrink: 0;
    transition:
      color var(--md-sys-motion-duration-short2),
      border-color var(--md-sys-motion-duration-short2);
  }

    .tab:hover {
      color: var(--md-sys-color-on-surface);
    }

  /* Active tab: primary underline indicator */
  .tab--active,
  .tab[aria-selected="true"] {
    color: var(--md-sys-color-primary);
    border-bottom-color: var(--md-sys-color-primary);
    font-weight: 600;
  }

  /* ── Compact tabs (tighter padding, used inside drawers) ── */
  .tabs--compact .tab {
    padding: var(--space-1) var(--space-3);
    font-size: var(--md-typescale-label-medium-size);
  }
}
/* ============================================================
   components/pipeline-board.css — Kanban Pipeline Board
   ------------------------------------------------------------
   The opportunity pipeline view: scrollable columns per stage,
   cards that open in the detail drawer when clicked.

   No drag-drop in Phase 1. Stage changes happen via a select
   field in the detail drawer.

   HTML contract:
     .pipeline-board
       .pipeline-col[data-stage="lead"]
         .pipeline-col__header
           .chip.chip--stage-lead
           .pipeline-col__count
           .pipeline-col__value
         .pipeline-col__cards
           article.pipeline-card.state-layer
             a (Turbo Frame link to opportunity_detail)
               .pipeline-card__name
               .pipeline-card__meta
               .pipeline-card__date [.overdue]
   ============================================================ */
@layer components {

  /* ── Board container: horizontal scrolling columns ── */
  .pipeline-board {
    display: flex;
    gap: var(--space-4);
    height: 100%;
    overflow-x: auto;
    padding: var(--space-4);
    align-items: flex-start;
    -webkit-overflow-scrolling: touch;
  }

  /* ── Individual stage column ── */
  .pipeline-col {
    flex: 1 0 240px;     /* grow to fill available width; min 240px so columns don't crush */
    max-width: 320px;    /* cap so wide screens don't stretch cards unreadably */
    display: flex;
    flex-direction: column;
    background: var(--md-sys-color-surface-container);
    border-radius: var(--radius-md);
    max-height: 100%;
    min-height: 120px;
  }

  /* Closed (won + lost) column — kept narrower than open-pipeline columns
     because it's a lower-priority archive column. Override the max-width only. */
  .pipeline-col--closed {
    max-width: 240px;
  }

  /* ── Column header ── */
  .pipeline-col__header {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-3);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    font-size: var(--md-typescale-label-medium-size);
    font-weight: 500;
    flex-shrink: 0;
    position: sticky;
    top: 0;
    background: var(--md-sys-color-surface-container);
    border-radius: var(--radius-md) var(--radius-md) 0 0;
    z-index: 1;
  }

  .pipeline-col__count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 20px;
    border-radius: var(--radius-full);
    background: var(--md-sys-color-surface-container-high);
    color: var(--md-sys-color-on-surface-variant);
    font-size: 11px;
    font-weight: 600;
    padding: 0 4px;
  }

  .pipeline-col__value {
    margin-left: auto;
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-on-surface-variant);
    font-variant-numeric: tabular-nums;
  }

  /* ── Empty column placeholder ── */
  .pipeline-col__empty {
    border: 1px dashed var(--md-sys-color-outline-variant);
    border-radius: var(--radius-sm);
    padding: var(--space-4);
    color: var(--md-sys-color-on-surface-variant);
    font-size: var(--md-typescale-body-small-size);
    text-align: center;
    margin: var(--space-2);
  }

  /* ── Cards list (scrollable within column) ── */
  .pipeline-col__cards {
    overflow-y: auto;
    padding: var(--space-2);
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    flex: 1;
  }

  /* ── Individual opportunity card ── */
  .pipeline-card {
    background: var(--md-sys-color-surface);
    border-radius: var(--radius-md);
    box-shadow: var(--md-sys-elevation-1);
    cursor: pointer;
    border: 1px solid var(--md-sys-color-outline-variant);
    transition: box-shadow var(--md-sys-motion-duration-short2),
                border-color var(--md-sys-motion-duration-short2);
  }

    .pipeline-card:hover {
      box-shadow: var(--md-sys-elevation-2);
      border-color: var(--md-sys-color-outline);
    }

    /* Links fill the card */
    .pipeline-card a {
      display: block;
      padding: var(--space-3);
      text-decoration: none;
      color: inherit;
    }

  /* Selected card state */
  .pipeline-card.card-selected {
    border-color: var(--md-sys-color-primary);
    box-shadow: 0 0 0 1px var(--md-sys-color-primary), var(--md-sys-elevation-1);
  }

  .pipeline-card__name {
    font-size: var(--md-typescale-body-medium-size);
    font-weight: 500;
    color: var(--md-sys-color-on-surface);
    margin-bottom: var(--space-1);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .pipeline-card__meta {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    gap: var(--space-2);
  }

  .pipeline-card__account {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .pipeline-card__value {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    font-weight: 500;
    color: var(--md-sys-color-on-surface);
  }

  .pipeline-card__date {
    font-size: 11px;
    color: var(--md-sys-color-on-surface-variant);
    margin-top: var(--space-1);
  }

  .pipeline-card__date.overdue {
    color: var(--md-sys-color-error);
    font-weight: 500;
  }

  /* ── Mobile: smaller columns, larger text ── */
  @media (max-width: 767px) {
    .pipeline-col {
      /* 175px: two columns almost fit at 390px, so the partial second
         column makes the horizontal scroll self-evident. */
      flex: 0 0 175px;
    }

    .pipeline-board {
      padding: var(--space-3);
      gap: var(--space-3);
    }

    /* Bump column header and value text so they're legible on small screens */
    .pipeline-col__header {
      font-size: var(--md-typescale-label-large-size);
    }

    .pipeline-col__value {
      font-size: var(--md-typescale-label-medium-size);
    }
  }
}
/* ============================================================
   components/timeline.css — Activity Feed / Timeline
   ------------------------------------------------------------
   Displays activity log within the detail drawer's Activity tab
   and on the dashboard recent activity section.

   HTML contract:
     div[role="feed"].timeline
       article.timeline-item
         .timeline-item__icon (SVG, 28x28 circle)
         .timeline-item__body
           .timeline-item__header
             span.timeline-item__actor  (user name)
             span.timeline-item__type   (action description)
             time.timeline-item__time   (relative time)
           p.timeline-item__content    (note text, optional)
   ============================================================ */
@layer components {

  /* ── Timeline container ── */
  .timeline {
    display: flex;
    flex-direction: column;
  }

  /* ── Individual activity item ── */
  .timeline-item {
    display: flex;
    gap: var(--space-3);
    padding: var(--space-3) 0;
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    font-size: var(--md-typescale-body-small-size);
  }

    .timeline-item:last-child {
      border-bottom: none;
    }

  /* ── Activity icon circle ── */
  .timeline-item__icon {
    width: 28px;
    height: 28px;
    flex-shrink: 0;
    background: var(--md-sys-color-surface-container);
    border-radius: var(--radius-full);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--md-sys-color-on-surface-variant);
    margin-top: 1px;
  }

    .timeline-item__icon svg {
      width: 14px;
      height: 14px;
    }

  /* Type-specific icon colors */
  .timeline-item__icon--note    { background: color-mix(in srgb, var(--md-sys-color-primary) 12%, transparent); color: var(--md-sys-color-primary); }
  .timeline-item__icon--email   { background: color-mix(in srgb, var(--color-stage-qualified) 12%, transparent); color: var(--color-stage-qualified); }
  .timeline-item__icon--call    { background: color-mix(in srgb, var(--color-stage-won) 12%, transparent); color: var(--color-stage-won); }
  .timeline-item__icon--task    { background: color-mix(in srgb, var(--md-sys-color-tertiary) 12%, transparent); color: var(--md-sys-color-tertiary); }
  .timeline-item__icon--invoice { background: color-mix(in srgb, var(--color-stage-negotiation) 12%, transparent); color: var(--color-stage-negotiation); }
  .timeline-item__icon--system  { background: var(--md-sys-color-surface-container); color: var(--md-sys-color-on-surface-variant); }

  /* ── Text body ── */
  .timeline-item__body {
    flex: 1;
    min-width: 0;
  }

  .timeline-item__header {
    display: flex;
    gap: var(--space-2);
    align-items: baseline;
    flex-wrap: wrap;
    margin-bottom: 2px;
  }

  .timeline-item__actor {
    font-weight: 500;
    color: var(--md-sys-color-on-surface);
  }

  .timeline-item__type {
    color: var(--md-sys-color-on-surface-variant);
  }

  .timeline-item__time {
    margin-left: auto;
    color: var(--md-sys-color-on-surface-variant);
    font-size: 11px;
    white-space: nowrap;
    flex-shrink: 0;
  }

  .timeline-item__content {
    color: var(--md-sys-color-on-surface);
    margin: 0;
    overflow-wrap: break-word;
    line-height: 1.45;
  }

  /* ── Timeline add-note form ── */
  .timeline-add {
    padding: var(--space-3) 0;
    display: flex;
    gap: var(--space-2);
    align-items: flex-start;
  }

  .timeline-add__input {
    flex: 1;
    min-height: 60px;
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--md-sys-color-outline);
    border-radius: var(--radius-sm);
    font-size: var(--md-typescale-body-medium-size);
    font-family: inherit;
    color: var(--md-sys-color-on-surface);
    background: var(--md-sys-color-surface);
    resize: vertical;
    outline: none;
  }

    .timeline-add__input:focus {
      border-color: var(--md-sys-color-primary);
      border-width: 2px;
    }
}
/* ============================================================
   components/snackbar.css — Snackbar / Toast Notifications
   ------------------------------------------------------------
   Snackbars appear at the bottom-center after actions.
   They are injected via Turbo Streams (turbo_stream.append
   to #snackbar-container).

   The snackbar_item Stimulus controller auto-dismisses after
   4000ms and handles manual dismiss.

   Layout note: #snackbar-container is OUTSIDE the .app-shell
   grid, in the <body>, with fixed positioning.

   HTML contract (injected by Turbo Stream):
     #snackbar-container.snackbar-container (in layout, always present)
       .snackbar [.snackbar--error | .snackbar--success]
         span (message text)
         button.btn.btn--text.btn--sm (Dismiss, optional)

   In the layout:
     <div id="snackbar-container" class="snackbar-container"
          aria-live="polite" aria-atomic="false"></div>
   ============================================================ */
@layer components {

  /* ── Container (fixed, outside app shell grid) ── */
  .snackbar-container {
    position: fixed;
    bottom: var(--space-6);
    left: 50%;
    transform: translateX(-50%);
    z-index: 500;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    pointer-events: none;   /* container doesn't block clicks */
    align-items: center;
  }

  /* ── Individual snackbar ── */
  .snackbar {
    /* M3 snackbar uses inverse surface for strong contrast */
    background: var(--md-sys-color-inverse-surface);
    color: var(--md-sys-color-inverse-on-surface);
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-sm);
    display: flex;
    align-items: center;
    gap: var(--space-4);
    min-width: 280px;
    max-width: 520px;
    font-size: var(--md-typescale-body-medium-size);
    box-shadow: var(--md-sys-elevation-3);
    pointer-events: auto;   /* snackbar IS clickable */
    /* Entrance animation */
    animation: snackbar-in var(--md-sys-motion-duration-medium1)
               var(--md-sys-motion-easing-decelerate);
  }

  /* Snackbar message text */
  .snackbar__message {
    flex: 1;
    min-width: 0;
  }

  /* ── Error variant (red background) ── */
  .snackbar--error {
    background: var(--md-sys-color-error);
    color: var(--md-sys-color-on-error);
  }

  /* ── Success variant (green-tinted) ── */
  .snackbar--success {
    background: var(--md-sys-color-inverse-surface);
    /* A green left border signals success without full green bg */
    border-left: 3px solid var(--color-stage-won);
  }

  /* ── Warning variant ── */
  .snackbar--warning {
    background: var(--md-sys-color-tertiary-container);
    color: var(--md-sys-color-on-tertiary-container);
  }

  /* ── Dismiss action button (inherits inverse surface colors) ── */
  .snackbar .btn--text {
    color: var(--md-sys-color-secondary);
    flex-shrink: 0;
    font-weight: 600;
  }

  .snackbar--error .btn--text {
    color: var(--md-sys-color-on-error);
  }

  /* ── Exit animation (added by snackbar_item controller before removal) ── */
  .snackbar.snackbar--exiting {
    animation: snackbar-out var(--md-sys-motion-duration-medium1)
               var(--md-sys-motion-easing-accelerate) forwards;
  }

  /* ── Keyframes ── */
  @keyframes snackbar-in {
    from {
      opacity: 0;
      transform: translateY(8px);
    }
    to {
      opacity: 1;
      transform: translateY(0);
    }
  }

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

  /* ── Mobile: full width ──
     Override the centered/translated desktop layout so the snackbar spans
     nearly the full screen width with space-4 gutters on each side. */
  @media (max-width: 600px) {
    .snackbar-container {
      left: var(--space-4);
      right: var(--space-4);
      transform: none;   /* cancel translateX(-50%) from desktop rule */
      bottom: var(--space-4);
    }

    .snackbar {
      min-width: unset;   /* remove 280px min-width; let it fill the container */
      width: 100%;
    }
  }
}
/* ============================================================
   components/dialog.css — Confirm Dialog (native <dialog>)
   ------------------------------------------------------------
   <dialog> is used ONLY for:
     1. Destructive confirmations ("Delete account?")
     2. Session expiry warnings
   Everything else uses drawers or dedicated pages (no modals).

   The confirm_dialog Stimulus controller calls showModal() /
   close() and traps focus inside the open dialog.

   HTML contract:
     <dialog class="confirm-dialog" id="confirm-dialog"
             data-controller="confirm-dialog"
             aria-modal="true"
             aria-labelledby="confirm-dialog-title"
             aria-describedby="confirm-dialog-desc">
       <div class="dialog__surface">
         <h2 class="dialog__title" id="confirm-dialog-title">...</h2>
         <p  class="dialog__body"  id="confirm-dialog-desc">...</p>
         <div class="dialog__actions">
           <button class="btn btn--text" ...>Cancel</button>
           <button class="btn btn--destructive" ...>Delete</button>
         </div>
       </div>
     </dialog>
   ============================================================ */
@layer components {

  /* ── Dialog element ── */
  .confirm-dialog {
    border: none;
    border-radius: var(--radius-xl);
    padding: 0;
    max-width: 400px;
    width: calc(100vw - var(--space-8));
    box-shadow: var(--md-sys-elevation-5);
    background: var(--md-sys-color-surface);
    color: var(--md-sys-color-on-surface);
    /* Animate in */
    opacity: 0;
    transform: scale(0.95);
    transition:
      opacity var(--md-sys-motion-duration-medium1) var(--md-sys-motion-easing-decelerate),
      transform var(--md-sys-motion-duration-medium1) var(--md-sys-motion-easing-decelerate);
  }

    .confirm-dialog[open] {
      opacity: 1;
      transform: scale(1);
    }

  /* Scrim backdrop */
  .confirm-dialog::backdrop {
    background: var(--md-sys-color-scrim);
    /* Backdrop animation not supported via CSS transitions in all browsers
       so we use opacity on the dialog itself instead */
  }

  /* ── Content surface inside dialog ── */
  .dialog__surface {
    padding: var(--space-6);
  }

  .dialog__title {
    font-size: var(--md-typescale-headline-small-size);
    font-weight: var(--md-typescale-headline-small-weight);
    color: var(--md-sys-color-on-surface);
    margin: 0 0 var(--space-3);
  }

  .dialog__body {
    font-size: var(--md-typescale-body-medium-size);
    color: var(--md-sys-color-on-surface-variant);
    line-height: 1.5;
    margin: 0 0 var(--space-6);
  }

  .dialog__actions {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-2);
  }

  /* ── Generic (non-destructive) dialog variant ── */
  .dialog {
    border: none;
    border-radius: var(--radius-xl);
    padding: 0;
    max-width: 560px;
    width: calc(100vw - var(--space-8));
    box-shadow: var(--md-sys-elevation-5);
    background: var(--md-sys-color-surface);
    color: var(--md-sys-color-on-surface);
  }

    .dialog::backdrop {
      background: var(--md-sys-color-scrim);
    }
}
/* ============================================================
   components/empty-state.css — Empty State Placeholder
   ------------------------------------------------------------
   Shown when a list or drawer panel has no content to display.

   HTML contract:
     .empty-state [.empty-state--drawer] [.empty-state--inline]
       .empty-state__icon (optional SVG)
       p.empty-state__title
       p.empty-state__sub (optional)
       a.btn.btn--filled (optional CTA)
   ============================================================ */
@layer components {

  /* ── Full-page empty state ── */
  .empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-3);
    padding: var(--space-12) var(--space-8);
    color: var(--md-sys-color-on-surface-variant);
    text-align: center;
    flex: 1;
    min-height: 200px;
  }

  /* ── Drawer variant (less padding) ── */
  .empty-state--drawer {
    padding: var(--space-8) var(--space-6);
    min-height: auto;
    justify-content: flex-start;
    padding-top: var(--space-12);
  }

  /* ── Inline variant (within table area, no flex: 1) ── */
  .empty-state--inline {
    flex: none;
    padding: var(--space-8) var(--space-4);
  }

  /* ── Compact variant (inside sidebar cards and small panels) ──
     Templates were already using this (dashboard my-tasks, node-health)
     before it was defined here — the fallthrough to the base rules' 200px
     min-height made small cards balloon into hollow boxes. Keep it tight. */
  .empty-state--compact {
    flex: none;
    min-height: 0;
    padding: var(--space-4) var(--space-3);
    gap: var(--space-2);
  }

  /* ── Body text (compact/inline empty states without a title) ──
     Also previously used in templates without a definition. */
  .empty-state__text {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    margin: 0;
    max-width: 320px;
    line-height: 1.5;
  }

  /* ── Icon ── */
  .empty-state__icon {
    width: 48px;
    height: 48px;
    color: var(--md-sys-color-outline);
  }

    .empty-state__icon svg {
      width: 100%;
      height: 100%;
    }

  /* ── Title ── */
  .empty-state__title {
    font-size: var(--md-typescale-title-medium-size);
    font-weight: 600;
    color: var(--md-sys-color-on-surface);
    margin: 0;
  }

  /* ── Subtitle / description ── */
  .empty-state__sub {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    margin: 0;
    max-width: 280px;
    line-height: 1.5;
  }
}
/* ============================================================
   components/auth.css — Authentication Page Layout
   ------------------------------------------------------------
   Centered card layout for login, magic-link, and password
   reset screens. Auth pages are full-bleed (no nav rail,
   no detail drawer).

   CLASS CONTRACT — the auth-views agent MUST use these exactly:

   Page:
     .auth-page               — full-screen centered container

   Card:
     .auth-card               — white surface card, centered
     .auth-card__logo         — logo/brand area at top
     .auth-card__title        — page title ("Sign in to bnkops")
     .auth-card__subtitle     — supporting text below title
     .auth-card__body         — form fields area
     .auth-card__actions      — button row (flex, gap)
     .auth-card__footer       — links below the card (forgot pw, etc.)

   Form fields (same as text-field.css contract):
     .field                   — field wrapper div
     .field__input            — <input> with placeholder=" " required
     .field__label            — floating label for input
     .field--error            — error state modifier on .field
     .field__error-text       — error message text

   Buttons (same as button.css contract):
     .btn.btn--filled         — primary submit button
     .btn.btn--outlined       — secondary action (Back, Cancel)
     .btn.btn--text           — tertiary link-style action

   Alert / notice messages (from Devise flash):
     .auth-alert              — generic alert
     .auth-alert--error       — error (red)
     .auth-alert--notice      — notice (blue/info)
     .auth-alert--success     — success (green)
   ============================================================ */
@layer components {

  /* ── Full-screen auth container ── */
  .auth-page {
    /* 100% of the app-main grid cell, NOT 100dvh: the cell is already
       viewport-height minus the top bar, so 100dvh overflows it by the
       top-bar height and pushes the card off vertical centre. */
    min-height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--md-sys-color-background);
    padding: var(--space-4);
  }

  /* ── Auth card ── */
  .auth-card {
    width: 100%;
    max-width: 400px;
    background: var(--md-sys-color-surface);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-xl);
    box-shadow: var(--md-sys-elevation-2);
    padding: var(--space-8);
    display: flex;
    flex-direction: column;
    gap: var(--space-6);
  }

  /* ── Logo / brand area ── */
  .auth-card__logo {
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .auth-card__brand {
    font-size: 22px;
    font-weight: 700;
    color: var(--md-sys-color-primary);
    letter-spacing: -0.02em;
  }

  /* ── Title ── */
  .auth-card__title {
    font-size: var(--md-typescale-headline-small-size);
    font-weight: var(--md-typescale-headline-small-weight);
    color: var(--md-sys-color-on-surface);
    text-align: center;
    margin: 0;
    line-height: 1.3;
  }

  .auth-card__subtitle {
    font-size: var(--md-typescale-body-medium-size);
    color: var(--md-sys-color-on-surface-variant);
    text-align: center;
    margin: 0;
    margin-top: calc(-1 * var(--space-4));   /* pull up closer to title */
    line-height: 1.5;
  }

  /* ── Form body: field stack ── */
  .auth-card__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
  }

  /* ── Form element itself ──
     A <form> is a direct child of .auth-card (which gaps its children with
     space-6). The form's own fields/actions need their own, tighter rhythm so
     the field sits comfortably above its submit button rather than crammed
     against it. */
  .auth-card__form {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
  }

  /* ── Remember-me inline checkbox ──
     The label wraps the checkbox so the whole row is clickable. Deliberately
     NOT .field__label (that class is position:absolute for floating labels). */
  .auth-card__remember {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--md-typescale-body-medium-size);
    color: var(--md-sys-color-on-surface-variant);
    cursor: pointer;
  }

    .auth-card__remember input[type="checkbox"] {
      margin: 0;
      flex: none;
    }

  /* ── Password fallback (admin break-glass) ──
     A <details> disclosure. Center the summary like the other secondary
     actions and give the revealed form room so it doesn't butt against the
     toggle. */
    .auth-card__password-fallback > summary {
      text-align: center;
      cursor: pointer;
      list-style: none; /* hide the default disclosure triangle */
    }

    .auth-card__password-fallback > summary::-webkit-details-marker {
      display: none;
    }

    .auth-card__password-fallback[open] > .auth-card__form {
      margin-top: var(--space-4);
    }

  .auth-card__field {
    /* Alias for .field — auth-views can use either */
    position: relative;
    display: flex;
    flex-direction: column;
    width: 100%;
  }

  /* ── Actions row ── */
  .auth-card__actions {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);

    /* Primary submit button full-width */
  }
    .auth-card__actions .btn--filled {
      width: 100%;
      justify-content: center;
      height: 40px;   /* slightly taller than standard 32px for auth prominence */
      font-size: var(--md-typescale-label-large-size);
    }

    .auth-card__actions .btn--outlined,.auth-card__actions .btn--text {
      width: 100%;
      justify-content: center;
    }

  /* ── Footer links (below card) ── */
  .auth-card__footer {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
  }

    .auth-card__footer a {
      color: var(--md-sys-color-primary);
      text-decoration: underline;
      text-decoration-thickness: 1px;
      text-underline-offset: 2px;
    }

  /* ── Alert / flash messages within auth card ── */
  .auth-alert {
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-sm);
    font-size: var(--md-typescale-body-medium-size);
    line-height: 1.4;
    border-width: 1px;
    border-style: solid;
  }

  .auth-alert--error {
    background: var(--md-sys-color-error-container);
    color: var(--md-sys-color-on-error-container);
    border-color: color-mix(in srgb, var(--md-sys-color-error) 30%, transparent);
  }

  .auth-alert--notice,
  .auth-alert--info {
    background: var(--md-sys-color-primary-container);
    color: var(--md-sys-color-on-primary-container);
    border-color: color-mix(in srgb, var(--md-sys-color-primary) 30%, transparent);
  }

  .auth-alert--success {
    background: color-mix(in srgb, var(--color-stage-won) 10%, transparent);
    color: color-mix(in srgb, var(--color-stage-won) 80%, var(--md-sys-color-on-surface));
    border-color: color-mix(in srgb, var(--color-stage-won) 30%, transparent);
  }

  /* ── Divider with text (e.g. "or") ── */
  .auth-divider {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-on-surface-variant);
  }

    .auth-divider::before,.auth-divider::after {
      content: "";
      flex: 1;
      height: 1px;
      background: var(--md-sys-color-outline-variant);
    }

  /* ── Magic link confirmation state ── */
  .auth-magic-sent {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-3);
    text-align: center;
    padding: var(--space-4) 0;
  }

  .auth-magic-sent__icon {
    width: 48px;
    height: 48px;
    background: var(--md-sys-color-primary-container);
    border-radius: var(--radius-full);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--md-sys-color-primary);
  }

  .auth-magic-sent__title {
    font-size: var(--md-typescale-title-large-size);
    font-weight: 600;
    color: var(--md-sys-color-on-surface);
    margin: 0;
  }

  .auth-magic-sent__desc {
    font-size: var(--md-typescale-body-medium-size);
    color: var(--md-sys-color-on-surface-variant);
    margin: 0;
    max-width: 300px;
    line-height: 1.5;
  }
}
/* ============================================================
   components/progress.css — Generic progress bar
   ------------------------------------------------------------
   Generalised from pages/trainings.css .training-progress*.
   trainings.css keeps its own .training-progress* selectors
   intact (they're used on the learner surface) — the lowest-risk
   option is to leave them untouched and note the duplication here,
   rather than refactor a working page file that other agents haven't
   touched. Duplication is explicit and documented.

   HTML contract:
     <div class="progress">
       <div class="progress__fill" style="width: 42%"></div>
     </div>
     <span class="progress__label">42%</span>

   Or the one-liner inline variant:
     <div class="training-progress">   ← trainings.css wrapper
       <div class="progress">
         <div class="progress__fill" style="width: 42%"></div>
       </div>
       <span class="progress__label">42%</span>
     </div>

   NOTE on trainings.css duplication:
     .training-progress__bar is functionally identical to .progress.
     .training-progress__fill is functionally identical to .progress__fill.
     .training-progress__label differs only in the body-small bg colour
     (none vs none) and the whitespace-nowrap rule which is also here.
     Future cleanup: replace .training-progress__bar with .progress in
     the training card partial; until then, both live peacefully.
   ============================================================ */
@layer components {

  /* ── Progress track ──
     Full-width horizontal bar. Width is set via a wrapper or parent
     flex item; to constrain width, place .progress inside a flex container
     with flex: 1 (same pattern as .training-progress__bar). */
  .progress {
    width: 100%;
    height: 6px;                                    /* same as training-progress__bar */
    background: var(--md-sys-color-surface-container-highest);
    border-radius: var(--radius-full);
    overflow: hidden;
  }

  /* ── Progress fill ──
     Width is set inline via style="width: N%" — the HTML contract
     keeps this in the template so it's driven by server-rendered data.
     Transition gives a smooth repaint when the value changes (e.g. Turbo
     stream update after a lesson completion). */
  .progress__fill {
    height: 100%;
    background: var(--md-sys-color-primary);
    border-radius: var(--radius-full);
    transition: width 0.3s ease;
  }

  /* ── Progress label ──
     Companion text (e.g. "3 / 7" or "42%"). Tabular-nums ensures
     digits align when multiple progress bars stack in a list.
     Place adjacent to .progress inside a flex row for alignment. */
  .progress__label {
    font-size: var(--md-typescale-label-small-size);   /* 11px */
    font-weight: var(--md-typescale-label-small-weight);
    color: var(--md-sys-color-on-surface-variant);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    line-height: 1.2;
  }

}
/* ============================================================
   components/reaction.css — mutual-aid reaction buttons.
   ------------------------------------------------------------
   A reusable pill-button row used on the commons card AND in the
   post drawer (per CLAUDE.md, buttons/chips live in components/,
   not in page files). Each button is ONE intent ("I can help" /
   "I want to learn" / "Interested") with a PER-INTENT count —
   never a summed "likes" total (mutual-aid rule). Tokens only,
   light + dark via tokens, >= 44px tap targets on mobile.
   ============================================================ */
@layer components {
  .reaction-row {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    /* The reaction-row contains <form> button_to forms; strip their margins. */
    align-items: center;
  }

  .reaction-row form { margin: 0; }

  .reaction-btn {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    /* 44px (WCAG 2.5.5) at every width — the commons is a touch-capable social
       surface, not a dense ops data table where 36px is acceptable. */
    min-height: 44px;
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-full);
    background: transparent;
    color: var(--md-sys-color-on-surface-variant);
    cursor: pointer;
    font-size: var(--md-typescale-label-medium-size);
    line-height: 1;
  }

  /* The current user's own active signal — filled tonal state. */
  .reaction-btn.is-active {
    background: var(--md-sys-color-primary-container);
    color: var(--md-sys-color-on-primary-container);
    border-color: transparent;
    font-weight: 600;
  }

  .reaction-btn:hover {
    background: var(--md-sys-color-surface-container-high);
  }

  .reaction-btn:focus-visible {
    outline: 2px solid var(--md-sys-color-primary);
    outline-offset: 2px;
  }

  /* Per-intent count bubble (hidden when zero — see _row.html.erb). */
  .reaction-btn__count {
    min-width: 18px;
    height: 18px;
    padding: 0 5px;
    border-radius: var(--radius-full);
    background: var(--md-sys-color-surface-container-high);
    color: var(--md-sys-color-on-surface);
    font-size: 10px;
    font-weight: 600;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  .reaction-btn.is-active .reaction-btn__count {
    background: var(--md-sys-color-primary);
    color: var(--md-sys-color-on-primary);
  }

  /* Mobile: also pin min-width so a short label (or a future emoji-only
     variant) can never wrap to a button narrower than 44px (WCAG 2.5.5 in
     BOTH axes). min-height is already 44px in the base rule above. */
  @media (max-width: 767px) {
    .reaction-btn { min-width: 44px; }
  }
}
/* --- Pages (page-specific layouts, sparse) --- */
/* ============================================================
   pages/dashboard.css — Dashboard Page Layout
   ------------------------------------------------------------
   Page-level layout for the main dashboard view.
   Component styles (stat-card, timeline) are in components/.
   ============================================================ */
@layer pages {

  /* ── Dashboard content wrapper ── */
  .dashboard {
    padding: var(--space-4);
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
  }

  /* ── Page header with title + actions ── */
  .page-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    flex-shrink: 0;
  }

  .page-header__title {
    font-size: var(--md-typescale-headline-small-size);
    font-weight: var(--md-typescale-headline-small-weight);
    color: var(--md-sys-color-on-surface);
    margin: 0;
  }

  .page-header__actions {
    display: flex;
    align-items: center;
    gap: var(--space-2);
  }

  /* Mobile: the title block and action buttons can't share 390px — let the
     actions drop to their own row instead of pushing buttons off-screen.
     (This header component is shared by every resource page.) */
  @media (max-width: 767px) {
    .page-header {
      flex-wrap: wrap;
    }

    .page-header__actions {
      width: 100%;
      justify-content: flex-end;
      flex-wrap: wrap;
    }
  }

  /* ── Two-column dashboard layout: stats left, feed right ── */
  .dashboard-layout {
    display: grid;
    grid-template-columns: 1fr 320px;
    gap: var(--space-4);
    /* stretch (not start): the activity card must fill the row height set
       by the sidebar, otherwise a short feed leaves a dead void below it —
       the row is as tall as the sidebar either way. */
    align-items: stretch;
    /* The layout is the page's outermost band below the stats — it needs
       its own horizontal padding (the stats grid carries its own). */
    padding: 0 var(--space-4) var(--space-4);
  }

  .dashboard-main {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    min-height: 0;
  }

  /* The activity card grows to fill the column; its feed (or empty state)
     grows inside it so the empty message centres in the real space. */
  .dashboard-main > .card {
    flex: 1;
    display: flex;
    flex-direction: column;
  }

  .dashboard-main > .card .empty-state,
  .dashboard-main > .card .timeline {
    flex: 1;
  }

  .dashboard-sidebar {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    position: sticky;
    top: var(--space-4);
  }

  /* Sidebar-card hint copy. A local class — the cards previously borrowed
     form-section__hint (a forms-layer class) with inline margin overrides. */
  .dashboard-hint {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    line-height: 1.5;
    margin: 0 0 var(--space-3);
  }

  .dashboard-hint--after {
    margin: var(--space-2) 0 0;
  }

  /* ── Section headers within dashboard ── */
  .section-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: var(--space-3);
  }

  .section-header__title {
    font-size: var(--md-typescale-title-medium-size);
    font-weight: 600;
    color: var(--md-sys-color-on-surface);
  }

  .section-header__link {
    font-size: var(--md-typescale-label-medium-size);
    color: var(--md-sys-color-primary);
    text-decoration: none;
  }

    .section-header__link:hover {
      text-decoration: underline;
    }

  /* ── Node health panel (lazy Turbo Frame) ── */
  /* The frame itself renders nothing until Kuma responds; no empty shell. */

  .node-health__list {
    display: flex;
    flex-direction: column;
    gap: 0;
    list-style: none;
    margin: 0;
    padding: 0;
  }

  .node-health__item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
  }

    .node-health__item:last-child { border-bottom: none; }

  .node-health__hostname {
    font-size: var(--md-typescale-body-medium-size);
    font-family: var(--md-sys-typescale-mono-family);
    color: var(--md-sys-color-on-surface);
    text-decoration: none;
  }

    .node-health__hostname:hover { text-decoration: underline; }

  /* Status chip with a coloured dot indicator */
  .node-health__chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    font-size: var(--md-typescale-label-small-size);
    text-transform: capitalize;
  }

  .node-health__dot {
    width: 8px;
    height: 8px;
    border-radius: var(--radius-full);
    flex-shrink: 0;
  }

  /* Dot colours by status */
  .node-health__chip--up      { color: var(--color-stage-won); }
  .node-health__chip--up      .node-health__dot { background: var(--color-stage-won); }

  .node-health__chip--down    { color: var(--md-sys-color-error); }
  .node-health__chip--down    .node-health__dot { background: var(--md-sys-color-error); }

  .node-health__chip--unknown { color: var(--md-sys-color-on-surface-variant); }
  .node-health__chip--unknown .node-health__dot { background: var(--md-sys-color-outline); }

  /* ── Stat card overdue state ── */
  /* Applied when @ar_overdue_count > 0; tints the AR card with the error container. */
  .stat-card--overdue {
    background: var(--md-sys-color-error-container);
    border-color: color-mix(in srgb, var(--md-sys-color-error) 30%, transparent);
  }

    .stat-card--overdue .stat-card__value {
      color: var(--md-sys-color-on-error-container);
    }

    .stat-card--overdue .stat-card__label {
      color: color-mix(in srgb, var(--md-sys-color-on-error-container) 70%, transparent);
    }

  /* Link within the AR stat card sub-text */
  .stat-card__overdue-text {
    color: var(--md-sys-color-error);
    font-weight: 500;
    text-decoration: none;
  }

    .stat-card__overdue-text:hover { text-decoration: underline; }

  /* ── My-tasks sidebar list ── */
  .task-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0;
  }

  .task-list__item {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
  }

    .task-list__item:last-child { border-bottom: none; }

  .task-list__main {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
  }

  .task-list__title {
    font-size: var(--md-typescale-body-medium-size);
    color: var(--md-sys-color-on-surface);
    text-decoration: none;
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

    .task-list__title:hover { text-decoration: underline; }

  .task-list__meta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
  }

  .task-list__context {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex: 1;
  }

  .task-list__due { white-space: nowrap; }
  .task-list__due--overdue { color: var(--md-sys-color-error); font-weight: 500; }

  /* ── Page header subtitle ── */
  .page-header__sub {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    margin: 2px 0 0;
  }

  /* ── Quick links ── */
  .quick-links__section {
    margin-bottom: var(--space-4);
  }

    .quick-links__section:last-child { margin-bottom: 0; }

  .quick-links__label {
    font-size: var(--md-typescale-label-small-size);
    font-weight: var(--md-typescale-label-small-weight);
    color: var(--md-sys-color-on-surface-variant);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    margin-bottom: var(--space-2);
  }

  .quick-links__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
  }

  .quick-link {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-sm);
    font-size: var(--md-typescale-body-medium-size);
    color: var(--md-sys-color-on-surface);
    text-decoration: none;
    transition: background var(--md-sys-motion-duration-short2);
  }

    .quick-link:hover {
      background: color-mix(in srgb, var(--md-sys-color-primary) 8%, transparent);
      color: var(--md-sys-color-primary);
    }

  .quick-link__icon  { font-size: 13px; flex-shrink: 0; }
  .quick-link__label { flex: 1; }
  .quick-link__external {
    font-size: 10px;
    color: var(--md-sys-color-on-surface-variant);
  }

  /* ── Timeline title line (subject link + type label) ── */
  .timeline-item__title {
    font-size: var(--md-typescale-body-small-size);
    font-weight: 500;
    color: var(--md-sys-color-on-surface);
  }

    .timeline-item__title a {
      color: var(--md-sys-color-primary);
      text-decoration: none;
    }

      :is(.timeline-item__title a):hover { text-decoration: underline; }

  .timeline-item__subject-type {
    font-weight: 400;
    color: var(--md-sys-color-on-surface-variant);
    font-size: var(--md-typescale-label-small-size);
  }

  /* ── Responsive: stack on narrow screens ── */
  @media (max-width: 1024px) {
    .dashboard-layout {
      grid-template-columns: 1fr;
    }

    .dashboard-sidebar {
      position: static;
    }
  }
}
/* ============================================================
   pages/accounts.css — Accounts (and similar resource) Pages
   ------------------------------------------------------------
   Page-level layout overrides for the master-detail resource
   pattern used by Accounts, Contacts, Projects, Invoices, Assets.
   Component styles (data-table, filter-bar, detail-drawer) live
   in components/ and layout/.
   ============================================================ */
@layer pages {

  /* ── Page header: title + primary action (used on all resource index pages) ── */
  .page-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-4) var(--space-4) var(--space-3);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    background: var(--md-sys-color-surface);
    flex-shrink: 0;
    gap: var(--space-3);
  }

  .page-header__title {
    font-size: var(--md-typescale-headline-small-size);
    font-weight: var(--md-typescale-headline-small-weight);
    line-height: var(--md-typescale-headline-small-line-height);
    color: var(--md-sys-color-on-surface);
    margin: 0;
  }

  .page-header__actions {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-shrink: 0;
  }

  /* ── Form actions bar (submit + cancel) ── */
  .form-actions {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding-top: var(--space-4);
    margin-top: var(--space-2);
  }

  /* ── Validation error summary above the form ── */
  .form-errors {
    background: color-mix(in srgb, var(--md-sys-color-error) 8%, transparent);
    border: 1px solid color-mix(in srgb, var(--md-sys-color-error) 30%, transparent);
    border-radius: var(--radius-sm);
    padding: var(--space-3) var(--space-4);
    margin-bottom: var(--space-4);
  }

  .form-errors__title {
    font-size: var(--md-typescale-label-large-size);
    font-weight: 600;
    color: var(--md-sys-color-error);
    margin: 0 0 var(--space-2);
  }

  .form-errors__list {
    margin: 0;
    padding-left: var(--space-4);
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-error);
  }

  .form-errors__list li + li {
    margin-top: var(--space-1);
  }

  /* ── Select field: inherits .field__input but needs specific overrides ── */
  .field__input--select {
    cursor: pointer;
    /* Ensure the label floats correctly on select (always has a value) */
  }

  /* Label for select fields — always floated since select always has a value */
  .field__label--select {
    top: 0;
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-on-surface-variant);
    transform: none;
  }

  /* ── Drawer list: compact list of related records inside the drawer ── */
  .drawer-list {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
  }

  .drawer-list__item {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    justify-content: space-between;
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    font-size: var(--md-typescale-body-small-size);
  }

    .drawer-list__item:last-child {
      border-bottom: none;
    }

  .drawer-list__primary {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--md-sys-color-on-surface);
    font-weight: 500;
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .drawer-list__meta {
    color: var(--md-sys-color-on-surface-variant);
    flex-shrink: 0;
  }

  /* ── Inline note display inside the drawer ── */
  .drawer-notes {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface);
    margin: 0;
    line-height: 1.6;
  }

    .drawer-notes p {
      margin: 0 0 var(--space-2);
    }

      :is(.drawer-notes p):last-child { margin-bottom: 0; }

  /* ── Muted text helpers ── */
  .text-muted {
    color: var(--md-sys-color-on-surface-variant);
  }

  .text-muted-sm {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    margin: 0;
  }

  /* ── Count badge — canonical definition promoted to components/chip.css ──
     (chip-adjacent; reconciled to the accounts.css version)
     The rule is no longer defined here to avoid duplication. */

  /* ── Sort link: Ransack sort_link renders a plain <a>, style it ── */
  .sort-link {
    color: inherit;
    text-decoration: none;
  }

    .sort-link:hover {
      color: var(--md-sys-color-on-surface);
    }

  /* ── Resource page container (fills .app-main) ── */
  .resource-page {
    display: flex;
    flex-direction: column;
    height: 100%;
    overflow: hidden;
  }

  /* ── Table area (below filter bar, above pagination) ── */
  .resource-table-area {
    flex: 1;
    overflow: hidden;
    display: flex;
    flex-direction: column;
  }

  /* ── Pagination bar (below table) ── */
  .pagination-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-4);
    border-top: 1px solid var(--md-sys-color-outline-variant);
    background: var(--md-sys-color-surface);
    flex-shrink: 0;
    font-size: var(--md-typescale-label-medium-size);
    color: var(--md-sys-color-on-surface-variant);
  }

  /* Pagy nav links */
  .pagination {
    display: flex;
    align-items: center;
    gap: var(--space-1);
  }

    .pagination a,.pagination span {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      min-width: 28px;
      /* min-height (not a hard height) so the mobile override below can grow
         the tap target to 44px (commons review — shared by every paginated
         view, including the commons feed). */
      min-height: 28px;
      padding: 0 var(--space-2);
      border-radius: var(--radius-sm);
      font-size: var(--md-typescale-label-medium-size);
      font-weight: 500;
      color: var(--md-sys-color-on-surface-variant);
      text-decoration: none;
      transition: background var(--md-sys-motion-duration-short2);
    }

    .pagination a:hover {
      background: color-mix(in srgb, var(--md-sys-color-on-surface) 8%, transparent);
      color: var(--md-sys-color-on-surface);
    }

    .pagination .active,.pagination [aria-current="page"] {
      background: var(--md-sys-color-primary);
      color: var(--md-sys-color-on-primary);
    }

    .pagination .disabled {
      opacity: var(--md-sys-state-disabled-opacity);
      pointer-events: none;
    }

  /* ── Mobile touch targets for pagination (commons review) ──
     The shared pagination bar (reused by the commons feed and every paginated
     resource) renders 28px links — below the WCAG 2.5.5 / Apple HIG 44px
     minimum. Grow them on small screens. Global fix, all paginated views. */
  @media (max-width: 767px) {
    .pagination a,
    .pagination span {
      min-width: 44px;
      min-height: 44px;
    }
  }
}
/* ============================================================
   pages/contacts.css — contacts page-specific styles
   Shared component styles live in components/ — only add styles
   here that are genuinely unique to this resource.
   ============================================================ */
@layer pages {
}
/* ============================================================
   pages/opportunities.css — Opportunities page-specific styles
   Shared component styles live in components/ — only add styles
   here that are genuinely unique to this resource.
   ============================================================ */
@layer pages {

  /* ── Board view: make the resource-page expand to fill the viewport ── */
  /* In board view we want the pipeline-board to scroll horizontally
     without the page-header area being included in that scroll. */
  .resource-page--board {
    display: flex;
    flex-direction: column;
    min-height: 0;
    height: 100%;
  }

  .resource-page--board .page-header {
    flex-shrink: 0;
  }

  .resource-page--board .pipeline-board {
    flex: 1;
    min-height: 0;
  }

  /* ── View toggle: board/table icon buttons in the page header ── */
  .view-toggle {
    display: flex;
    gap: var(--space-1);
    align-items: center;
    padding: var(--space-1);
    background: var(--md-sys-color-surface-container);
    border-radius: var(--radius-full);
  }

  /* Active state for the selected view toggle button */
  .view-toggle .view-toggle__btn--active {
    background: var(--md-sys-color-secondary-container);
    color: var(--md-sys-color-on-secondary-container);
  }

  /* ── Pipeline column: empty state message ── */
  .pipeline-col__empty {
    padding: var(--space-4) var(--space-2);
    text-align: center;
  }

  /* ── Closed column: slimmer width to save space ── */
  .pipeline-col--closed {
    flex: 0 0 200px;
  }

  /* ── Stage change inline form in the drawer ── */
  .drawer-field-group--stage {
    background: var(--md-sys-color-surface-container-low);
    border-radius: var(--radius-md);
    padding: var(--space-3);
    margin-bottom: var(--space-3);
  }

  /* Row with current chip + form side by side */
  .stage-change-row {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
  }

  .stage-current-chip {
    flex-shrink: 0;
  }

  /* The inline stage-change form: select + button side by side */
  .stage-change-form {
    display: flex;
    gap: var(--space-2);
    align-items: center;
    flex: 1;
    min-width: 0;
  }

  .stage-change-form__select {
    flex: 1;
    min-width: 0;
    /* Override the default field height for a compact inline form */
    height: 36px;
    padding-top: 0;
    padding-bottom: 0;
    font-size: var(--md-typescale-body-small-size);
  }

  /* ── Text error helper (overdue dates etc.) ── */
  .text-error {
    color: var(--md-sys-color-error);
  }

  /* ── Opportunity table: align close date column more compactly ── */
  .data-table .pipeline-stage-chip {
    display: inline-flex;
  }
}
/* ============================================================
   pages/projects.css — projects page-specific styles
   Shared component styles live in components/ — only add styles
   here that are genuinely unique to this resource.
   ============================================================ */
@layer pages {

  /* ── Project status chips ──────────────────────────────────────────────
     active    → chip--success  (defined in components/chip.css)
     on_hold   → chip--warning  (defined in components/chip.css)
     completed → chip--neutral  (defined in components/chip.css)
     cancelled → chip--muted    (defined here — very de-emphasised)
     ── */

  /* Muted chip: for cancelled / inactive states.
     Uses surface-container-highest so it reads as "filed away / not active".
     Text uses on-surface-variant at reduced opacity for further de-emphasis. */
  .chip--muted {
    background: var(--md-sys-color-surface-container-highest);
    color: color-mix(in srgb, var(--md-sys-color-on-surface-variant) 70%, transparent);
  }

}
/* ============================================================
   pages/tasks.css — tasks page-specific styles
   Shared component styles live in components/ — only add styles
   here that are genuinely unique to this resource.
   ============================================================ */
@layer pages {

  /* ── Priority chip colours ───────────────────────────────────────────
     Low and Normal use neutral tones. High uses error-container
     colours (red) per the UI spec — same as M3 error-container. */

  .chip--priority-low {
    background: color-mix(in srgb, var(--md-sys-color-on-surface-variant) 10%, transparent);
    color: var(--md-sys-color-on-surface-variant);
  }

  .chip--priority-normal {
    background: color-mix(in srgb, var(--md-sys-color-primary) 12%, transparent);
    color: var(--md-sys-color-primary);
  }

  /* High priority: error-container colour as background, on-error-container text */
  .chip--priority-high {
    background: var(--md-sys-color-error-container);
    color: var(--md-sys-color-on-error-container);
  }

  /* ── Overdue due-date highlight ──────────────────────────────────────
     Applied to <span> wrapping the date when it's past and task is open. */

  .task-due--overdue {
    color: var(--md-sys-color-error);
    font-weight: 500;
  }

  .task-due__overdue-badge {
    display: inline-flex;
    align-items: center;
    height: 18px;
    padding: 0 var(--space-1);
    border-radius: var(--radius-xs);
    background: var(--md-sys-color-error-container);
    color: var(--md-sys-color-on-error-container);
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-left: var(--space-1);
    vertical-align: middle;
  }

  /* ── Quick status-change form (in drawer) ────────────────────────────
     A compact horizontal form row: select + submit button side-by-side. */

  .quick-status-form__row {
    display: flex;
    gap: var(--space-2);
    align-items: center;
  }

  .quick-status-form__select {
    flex: 1;
    /* Override the full-width default from .field__input */
    width: auto;
  }

  /* ── Parent section error highlight ─────────────────────────────────
     When the at-least-one-parent validation fires, highlight the section
     header in error colour so it's obvious which section to fix. */

  .form-section--error .form-section__title {
    color: var(--md-sys-color-error);
  }

  /* Inline error message for the parent group */
  .field-group-error {
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    background: var(--md-sys-color-error-container);
    color: var(--md-sys-color-on-error-container);
    font-size: var(--md-typescale-body-small-size);
    margin-bottom: var(--space-3);
  }

  /* Small hint text in section title */
  .form-section__hint {
    font-weight: 400;
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    margin-left: var(--space-2);
  }

  /* ── Assigned-to-me toggle ───────────────────────────────────────────
     A label+checkbox combo that looks like a pill toggle in the filter bar. */

  .filter-bar__toggle {
    display: flex;
    align-items: center;
  }

  .filter-bar__toggle-label {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    font-size: var(--md-typescale-label-medium-size);
    font-weight: var(--md-typescale-label-medium-weight);
    color: var(--md-sys-color-on-surface-variant);
    cursor: pointer;
    -webkit-user-select: none;
       -moz-user-select: none;
            user-select: none;
  }

  .filter-bar__checkbox {
    accent-color: var(--md-sys-color-primary);
    width: 14px;
    height: 14px;
    cursor: pointer;
  }
}
/* ============================================================
   pages/activities.css — activities page-specific styles

   Shared timeline component styles live in components/timeline.css.
   This file only adds styles genuinely unique to the Activities
   index feed page (day group headers, feed wrapper, meta row).
   ============================================================ */
@layer pages {

  /* ── Feed wrapper — mirrors resource-table-area for consistent margins ── */
  .timeline-feed-area {
    flex: 1;
    overflow: auto;
    /* Same horizontal padding as the filter bar above (space-4) so the feed
       content and the filter controls share a left edge. */
    padding: var(--space-4);
  }

  /* ── Page container for the grouped timeline ──
     No max-width: entries are dense title+meta rows, not prose — capping
     the page left a ~580px void at 1440px. The body excerpt alone keeps
     a readable measure (see .timeline-item__body below). */
  .timeline-page {
    max-width: none;
  }

  /* Keep the excerpt text at a readable line length even on wide screens. */
  .timeline-item__body {
    max-width: 70ch;
  }

  /* ── Day group: header + items for one calendar day ── */
  .timeline-day-group {
    margin-bottom: var(--space-6);
  }

  /* ── Day header label ("Today", "Yesterday", "Jun 8, 2026") ── */
  .timeline-day-header {
    font-size: var(--md-typescale-label-medium-size, 12px);
    font-weight: 600;
    letter-spacing: 0.04em;
    /* NOTE: no text-transform here — test assertions match the raw string "Today",
       "Yesterday" so CSS uppercasing would break them. Keep title-case in the helper. */
    color: var(--md-sys-color-on-surface-variant);
    margin: 0 0 var(--space-2) 0;
    padding-bottom: var(--space-1);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
  }

  /* ── Title link inside a timeline item ── */
  .timeline-item__title-link {
    font-weight: 500;
    color: var(--md-sys-color-on-surface);
    text-decoration: none;
  }

    .timeline-item__title-link:hover {
      color: var(--md-sys-color-primary);
      text-decoration: underline;
    }

  /* ── Meta row: subject link + author ── */
  .timeline-item__meta {
    display: flex;
    gap: var(--space-3);
    margin-top: var(--space-1);
    font-size: 11px;
    color: var(--md-sys-color-on-surface-variant);
    flex-wrap: wrap;
    align-items: center;
  }

  /* "on Acme Corporation" subject label */
  .timeline-item__subject {
    display: flex;
    align-items: center;
    gap: var(--space-1);
  }

  /* Author email label */
  .timeline-item__author {
    color: var(--md-sys-color-on-surface-variant);
  }

  /* ── Readonly field (contextual subject display in form) ── */
  .field__input--readonly {
    background: var(--md-sys-color-surface-container-low, #f4f4f7);
    color: var(--md-sys-color-on-surface-variant);
    cursor: default;
    -webkit-user-select: none;
       -moz-user-select: none;
            user-select: none;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    border: 1px solid var(--md-sys-color-outline-variant);
    font-size: var(--md-typescale-body-medium-size);
    min-height: 44px;
    display: flex;
    align-items: center;
  }

  /* Float the label above readonly field (always shown) */
  .field__label--active {
    transform: translateY(-1.5rem) scale(0.75);
    color: var(--md-sys-color-on-surface-variant);
    font-size: var(--md-typescale-body-small-size);
    pointer-events: none;
  }
}
/* ============================================================
   pages/customer-portal.css — Customer Portal Pages
   ------------------------------------------------------------
   The customer portal (Phase 5) uses the same shell but with a
   simplified nav. Portal pages use standard resource components.

   Public (unauthenticated) marketing-page styles live in
   pages/public.css — they were moved there so the two surfaces
   can be edited independently.
   ============================================================ */
@layer pages {

  /* ── Portal uses same shell, but simplified nav ── */
  .portal-nav .nav-item--admin-only {
    display: none;
  }

  /* ── Portal content uses same page patterns ── */
  .portal-page {
    padding: var(--space-4);
    max-width: 960px;
    margin: 0 auto;
  }

  /* The shared .page-header carries padding + border for the full-bleed
     app shell; inside the padded, centred portal column that produces a
     double inset and an orphaned divider. Flatten it here. */
  .portal-page .page-header {
    padding-left: 0;
    padding-right: 0;
    border-bottom: none;
    margin-bottom: var(--space-2);
  }

  /* Stat cards: cap the card width and left-align the group. Stretching
     two cards across the full 960px makes each disproportionately wide. */
  .portal-page .dashboard-stats {
    grid-template-columns: repeat(auto-fill, minmax(200px, 240px));
    justify-content: start;
    padding-left: 0;
    padding-right: 0;
  }

  /* ── Section title (h2 above each portal table/card) ──
     Was previously unstyled — fell through to browser heading defaults. */
  .section-title {
    font-size: var(--md-typescale-title-medium-size);
    font-weight: 600;
    color: var(--md-sys-color-on-surface);
    margin: 0 0 var(--space-3);
  }

  /* Inside a .section-header flex row the wrapper carries the margin. */
  .section-title--flush {
    margin-bottom: 0;
  }

  /* ── Section rhythm: consistent spacing between sibling sections,
     replacing the per-section inline margin-top patchwork. ── */
  .portal-section + .portal-section {
    margin-top: var(--space-6);
  }

  /* Body copy inside portal sections (was inline-styled in the view). */
  .portal-section__note {
    margin: 0 0 var(--space-3);
    color: var(--md-sys-color-on-surface-variant);
    font-size: var(--md-typescale-body-medium-size);
  }
}
/* ============================================================
   pages/public.css — Public marketing page styles
   ------------------------------------------------------------
   Used by PagesController (GET /welcome, /about, /contact).
   Layout: public.html.erb. No authentication shell.
   These styles sit in the `pages` @layer (declared in application.css).
   ============================================================ */
@layer pages {

  /* ── Public layout wrapper — full viewport ── */
  .public-layout {
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
    background: var(--md-sys-color-background);
    color: var(--md-sys-color-on-background);
  }

  /* ── Public page body (moved here from customer-portal.css) ──
     NOTE: no min-height — the .public-layout flex column above already
     fills the viewport; forcing each page to 100dvh as well produced a
     dead half-screen on short pages (about, contact) before the footer. */
  .public-page {
    background: var(--md-sys-color-background);
    flex: 1;
  }

  .public-page__hero {
    padding: var(--space-12) var(--space-8) var(--space-10);
    text-align: center;
    /* Tonal band: gives the page a distinct top region — previously every
       section sat on the same background with no visual hierarchy. */
    background: var(--md-sys-color-surface-container);
  }

  /* Inner measure for hero copy (the band itself is full-bleed). */
  .public-page__hero > * {
    max-width: 640px;
    margin-left: auto;
    margin-right: auto;
  }

  .public-page__title {
    font-size: var(--md-typescale-display-size);
    font-weight: var(--md-typescale-display-weight);
    color: var(--md-sys-color-on-background);
    margin-bottom: var(--space-4);
  }

  .public-page__sub {
    font-size: var(--md-typescale-body-large-size);
    color: var(--md-sys-color-on-surface-variant);
    line-height: 1.6;
    margin-bottom: var(--space-6);
  }

  /* ── Section width presets — replace per-view inline max-widths ── */
  .public-section {
    margin: 0 auto;
    padding: var(--space-10) var(--space-6);
  }

  .public-section--narrow { max-width: 560px; }
  .public-section--medium { max-width: 720px; }

  .public-section__heading {
    font-size: var(--md-typescale-headline-small-size);
    font-weight: var(--md-typescale-headline-small-weight);
    text-align: center;
    margin-bottom: var(--space-6);
  }

  .public-section--center { text-align: center; }

  /* Hero CTA button row */
  .public-cta-row {
    display: flex;
    gap: var(--space-3);
    justify-content: center;
    flex-wrap: wrap;
  }

  /* Service cards grid + card copy (landing page) */
  .public-card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: var(--space-4);
  }

  .public-card-title {
    font-weight: 600;
    margin-bottom: var(--space-2);
  }

  .public-card-text {
    color: var(--md-sys-color-on-surface-variant);
    font-size: var(--md-typescale-body-medium-size);
    line-height: 1.5;
  }

  /* Longer-measure body copy (about page) */
  .public-prose {
    color: var(--md-sys-color-on-surface-variant);
    line-height: 1.7;
    margin-bottom: var(--space-4);
  }

    .public-prose:last-of-type { margin-bottom: var(--space-6); }

  /* Contact card internals */
  .public-contact-card {
    text-align: center;
    padding: var(--space-6);
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    align-items: center;
  }

  .public-contact-card__portal-note {
    font-size: var(--md-typescale-body-small-size);
    margin: 0;
  }

  /* ── Public top bar ─────────────────────────────────────────── */
  .public-top-bar {
    height: 56px;
    background: var(--md-sys-color-surface);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    position: sticky;
    top: 0;
    z-index: 10;
  }

  .public-top-bar__inner {
    max-width: 1140px;
    margin: 0 auto;
    height: 100%;
    padding: 0 var(--space-6);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
  }

  .public-top-bar__brand {
    font-size: var(--md-typescale-title-medium-size);
    font-weight: var(--md-typescale-title-medium-weight);
    color: var(--md-sys-color-on-surface);
    text-decoration: none;
    letter-spacing: 0.01em;
  }

    .public-top-bar__brand:hover {
      color: var(--md-sys-color-primary);
    }

  .public-top-bar__nav {
    display: flex;
    align-items: center;
    gap: var(--space-2);
  }

  .public-nav-link {
    font-size: var(--md-typescale-body-medium-size);
    color: var(--md-sys-color-on-surface-variant);
    text-decoration: none;
    padding: var(--space-1) var(--space-2);
    border-radius: var(--shape-corner-extra-small);
    transition: color var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard),
                background var(--md-sys-motion-duration-short3) var(--md-sys-motion-easing-standard);
  }

    .public-nav-link:hover {
      color: var(--md-sys-color-on-surface);
      background: var(--md-sys-color-surface-container);
    }

  /* ── Public main content area ── */
  .public-main {
    flex: 1;
    min-height: 0;
  }

  /* ── Public footer ── */
  .public-footer {
    background: var(--md-sys-color-surface-container);
    border-top: 1px solid var(--md-sys-color-outline-variant);
    padding: var(--space-4) 0;
  }

  .public-footer__inner {
    max-width: 1140px;
    margin: 0 auto;
    padding: 0 var(--space-6);
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    flex-wrap: wrap;
  }

  .public-footer__brand {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    font-weight: 500;
  }

  .public-footer__nav {
    display: flex;
    gap: var(--space-4);
    flex-wrap: wrap;
  }

  .public-footer-link {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    text-decoration: none;
  }

    .public-footer-link:hover {
      color: var(--md-sys-color-primary);
      text-decoration: underline;
    }

  /* ── Responsive: progressively shed nav links as width shrinks ──
     The top bar is a fixed-height (56px) single row with no wrap, so links
     must be HIDDEN (not wrapped) before they overflow. Two steps:

     1. ≤640px (tablet / landscape phone): drop the three lower-priority links
        — "What we do", "About", "Contact" — but keep "Training", "Marketplace"
        and the Sign-in button. Six items in an 8px-gap row crowd the bar well
        before desktop widths, so this intermediate step prevents the overflow
        gap between 481px and desktop. The dropped links remain reachable in the
        footer nav, so nothing becomes inaccessible.
     2. ≤480px (phone): hide ALL text links, leaving only the Sign-in button. */
  @media (max-width: 640px) {
    .public-nav-link--secondary {
      display: none;
    }
  }

  @media (max-width: 480px) {
    .public-top-bar__inner {
      padding: 0 var(--space-4);
    }

    .public-nav-link {
      /* Hide all remaining text links; only the Sign-in button stays. */
      display: none;
    }

    .public-footer__inner {
      flex-direction: column;
      gap: var(--space-2);
    }
  }
}
/* ============================================================
   pages/invoices.css — Invoices page styles
   ------------------------------------------------------------
   Invoice-specific layout and component overrides.
   Component styles (data-table, filter-bar, detail-drawer, chip)
   live in components/ and are NOT repeated here.

   CSS custom properties follow the existing token pattern.
   All rules go inside @layer pages to match the cascade order
   declared in application.postcss.css.
   ============================================================ */
@layer pages {

  /* ── Overdue due-date text color ── */
  .invoice-due--overdue {
    color: var(--color-status-overdue);
    font-weight: 500;
  }

  /* ── Title + chip group in the drawer header ── */
  /* Wraps the h2 title and status chip so the chip doesn't get clipped
     by the title's overflow:hidden. The header is already flex; this group
     is also flex so they sit on the same line. */
  .invoice-drawer-title-group {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex: 1;
    min-width: 0; /* allow title to shrink */
    overflow: hidden;
  }

  .invoice-drawer-title-group .detail-drawer__title {
    /* Override the flex:1 from layout/detail-drawer.css so the chip stays visible */
    flex: 0 1 auto;
    min-width: 0;
  }

  /* The chip sits outside the h2, so it never gets clipped */
  .invoice-drawer-title-group .chip {
    flex-shrink: 0;
  }

  /* ── Invoice workflow action bar — sits below the header in scrollable content ──
     Holds Send / Mark paid / Void / PDF / Delete workflow buttons that were moved
     out of the 48px header to prevent overflow when the invoice number is long. */
  .invoice-actions-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-4);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    flex-shrink: 0;
  }

  /* ── Email-to-client panel (progressive disclosure via disclosure-panel Stimulus controller) ──
     Sits below the workflow action bar, above the scrollable content.
     The toggle <button> is controlled by disclosure_panel_controller.js.
     The body section contains the recipient select + optional message form.

     Note: We use a Stimulus controller + plain <div> instead of <details>/<summary>
     because forms inside <details> elements don't always submit correctly when
     Turbo is active in Chrome. The Stimulus pattern is consistent with other
     toggle patterns in this codebase. */

  .invoice-email-panel {
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    flex-shrink: 0;
  }

  .invoice-email-panel__toggle {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-4);
    font-size: var(--md-typescale-label-large-size);
    font-weight: 500;
    color: var(--md-sys-color-primary);
    cursor: pointer;
    -webkit-user-select: none;
       -moz-user-select: none;
            user-select: none;
    /* Reset button defaults */
    background: none;
    border: none;
    width: 100%;
    text-align: left;
  }

  /* Add a simple chevron via CSS content that rotates when open */
  .invoice-email-panel__toggle::after {
    content: "▾";
    font-size: 0.85em;
    transition: transform 0.15s ease;
    margin-left: auto;
  }
  .invoice-email-panel__toggle[aria-expanded="true"]::after {
    transform: rotate(180deg);
  }

  .invoice-email-panel__toggle-hint {
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-on-surface-variant);
    font-weight: 400;
  }

  .invoice-email-panel__body {
    padding: var(--space-3) var(--space-4);
    background-color: var(--md-sys-color-surface-container-low, #f5f5f5);
  }

  .invoice-email-panel__form {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
  }

  .invoice-email-panel__fields {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
  }

  /* Recipient select takes full width */
  .invoice-email-panel__field--recipient,
  .invoice-email-panel__field--custom,
  .invoice-email-panel__field--message {
    width: 100%;
  }

  /* Textarea height */
  .field__input--textarea {
    min-height: 72px;
    resize: vertical;
  }

  .invoice-email-panel__actions {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-wrap: wrap;
  }

  .invoice-email-panel__hint {
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-on-surface-variant);
  }

  /* ── Invoice number link in the table ── */
  .invoice-number-link {
    font-weight: 500;
    color: var(--md-sys-color-primary);
    text-decoration: none;
  }
  .invoice-number-link:hover {
    text-decoration: underline;
  }

  /* ═══════════════════════════════════════════════════════════
     INVOICE BUILDER — line items table
     ═══════════════════════════════════════════════════════════ */

  /* Scroll wrapper: on a narrow drawer (375px → ~343px content) the four nowrap
     header cells plus wide money values (e.g. "$1,200.00" across three nowrap
     number columns) can force a min table width that exceeds the viewport. The
     wrapper lets the table scroll horizontally instead of silently clipping. */
  .invoice-lines-table-wrapper {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  .invoice-lines-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--md-typescale-body-small-size);
    margin-bottom: var(--space-3);
  }

  .invoice-lines-table__th {
    text-align: left;
    padding: var(--space-1) var(--space-2);
    font-size: var(--md-typescale-label-small-size);
    font-weight: 600;
    color: var(--md-sys-color-on-surface-variant);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    white-space: nowrap;
  }

  .invoice-lines-table__th--number {
    text-align: right;
  }

  .invoice-lines-table__th--action {
    width: 32px;
  }

  .invoice-lines-table__row {
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
  }

  .invoice-lines-table__row:last-child {
    border-bottom: none;
  }

  .invoice-lines-table__td {
    padding: var(--space-2);
    vertical-align: middle;
    font-size: var(--md-typescale-body-small-size);
  }

  .invoice-lines-table__td--number {
    text-align: right;
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }

  .invoice-lines-table__td--action {
    text-align: right;
    padding-right: var(--space-1);
    white-space: nowrap;
  }

  /* Description cell: description text + line-type chip inline */
  .invoice-line__desc {
    display: block;
    color: var(--md-sys-color-on-surface);
  }

  /* Discount total displayed with minus sign in muted color */
  .invoice-line__discount {
    color: var(--color-status-overdue);
    font-variant-numeric: tabular-nums;
  }

  /* Line type chips — small inline badge next to description */
  .chip--line-type {
    font-size: 0.65rem;
    height: 18px;
    padding: 0 var(--space-1);
    margin-left: var(--space-1);
    opacity: 0.75;
  }

  .chip--line-type-labour       { background: color-mix(in srgb, var(--color-status-sent) 12%, transparent); color: var(--color-status-sent); }
  .chip--line-type-pass_through { background: color-mix(in srgb, var(--md-sys-color-tertiary) 12%, transparent); color: var(--md-sys-color-tertiary); }
  .chip--line-type-product      { background: color-mix(in srgb, var(--color-stage-qualified) 12%, transparent); color: var(--color-stage-qualified); }
  .chip--line-type-discount     { background: color-mix(in srgb, var(--color-status-overdue) 12%, transparent); color: var(--color-status-overdue); }

  /* Icon-only button for remove actions */
  .btn--icon-only {
    padding: var(--space-1);
    min-width: unset;
    line-height: 1;
  }

  /* ═══════════════════════════════════════════════════════════
     ADD LINE ITEM FORM
     ═══════════════════════════════════════════════════════════ */

  .invoice-add-line {
    border-top: 1px solid var(--md-sys-color-outline-variant);
    padding-top: var(--space-3);
    margin-top: var(--space-2);
  }

  .invoice-add-line__title {
    font-size: var(--md-typescale-label-medium-size);
    font-weight: 600;
    color: var(--md-sys-color-on-surface-variant);
    margin: 0 0 var(--space-2);
  }

  .invoice-add-line__form {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
  }

  /* Field grid: description takes remaining width; qty/price/type are fixed */
  .invoice-add-line__fields {
    display: grid;
    grid-template-columns: 1fr 60px 90px 110px;
    gap: var(--space-2);
    align-items: end;
  }

  .invoice-add-line__field--desc  { grid-column: 1; }
  .invoice-add-line__field--qty   { grid-column: 2; }
  .invoice-add-line__field--price { grid-column: 3; }
  .invoice-add-line__field--type  { grid-column: 4; }

  /* Responsive: collapse add-line grid to 2 columns on very narrow drawers.
     480px fires within the 767px mobile range (intra-mobile grid collapse). */
  @media (max-width: 480px) {
    .invoice-add-line__fields {
      grid-template-columns: 1fr 1fr;
    }
    .invoice-add-line__field--desc { grid-column: 1 / -1; }
  }

  /* ═══════════════════════════════════════════════════════════
     IMPORT UNBILLED TIME SECTION
     ═══════════════════════════════════════════════════════════ */

  .invoice-import__project-row {
    display: flex;
    align-items: flex-end;
    gap: var(--space-2);
    margin-bottom: var(--space-3);
  }

  .invoice-import__project-select {
    flex: 1;
  }

  /* Entry checklist */
  .invoice-import__entries {
    list-style: none;
    padding: 0;
    margin: 0 0 var(--space-3);
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
  }

  .invoice-import__entry {
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-sm);
    padding: var(--space-2);
  }

  .invoice-import__entry-label {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    cursor: pointer;
  }

  .invoice-import__entry-desc {
    display: flex;
    flex-direction: column;
    gap: 2px;
    font-size: var(--md-typescale-body-small-size);
  }

  .invoice-import__entry-meta {
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-on-surface-variant);
  }

  /* ═══════════════════════════════════════════════════════════
     TOTALS SECTION
     ═══════════════════════════════════════════════════════════ */

  .invoice-totals {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    padding: var(--space-2) 0;
  }

  .invoice-totals__row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    font-size: var(--md-typescale-body-small-size);
    padding: var(--space-1) 0;
  }

  .invoice-totals__row dt {
    color: var(--md-sys-color-on-surface-variant);
  }

  .invoice-totals__row dd {
    font-variant-numeric: tabular-nums;
    margin: 0;
  }

  /* Discount row: muted text */
  .invoice-totals__row--discount dt,
  .invoice-totals__row--discount dd {
    color: var(--color-status-overdue);
  }

  /* Total row: slightly larger, separator line above */
  .invoice-totals__row--total {
    border-top: 1px solid var(--md-sys-color-outline-variant);
    margin-top: var(--space-1);
    padding-top: var(--space-2);
    font-size: var(--md-typescale-body-medium-size);
  }

  /* Balance due row */
  .invoice-totals__row--balance {
    border-top: 2px solid var(--md-sys-color-outline);
    margin-top: var(--space-1);
    padding-top: var(--space-2);
    font-size: var(--md-typescale-body-medium-size);
    font-weight: 600;
  }

  /* Settled balance: green text */
  .invoice-totals__balance--settled {
    color: var(--color-status-paid);
  }

  /* Outstanding balance: primary color */
  .invoice-totals__balance--owing {
    color: var(--md-sys-color-primary);
  }

  /* ═══════════════════════════════════════════════════════════
     RECORD PAYMENT FORM
     ═══════════════════════════════════════════════════════════ */

  .invoice-record-payment {
    border-top: 1px solid var(--md-sys-color-outline-variant);
    padding-top: var(--space-3);
    margin-top: var(--space-2);
  }

  .invoice-record-payment__form {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
  }

  .invoice-record-payment__fields {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-2);
  }

  /* Payment item in the payments list */
  .invoice-payment__item {
    align-items: baseline;
  }

  .invoice-payment__method {
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-on-surface-variant);
    margin-left: var(--space-1);
  }

  .invoice-payment__ref {
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-on-surface-variant);
    font-family: var(--font-mono, monospace);
  }

}
/* ============================================================
   pages/time_entries.css — Time Entries page styles
   Dense table with project/account stacking and invoiced chips.
   ============================================================ */
@layer pages {

  /* ── Column widths for the time entries table ── */

  /* Date column — fixed narrow since dates are short */
  .te-date-col {
    width: 110px;
    white-space: nowrap;
  }

  /* Description column — take remaining space, truncate overflow */
  .te-description-col {
    max-width: 180px;
  }

  /* Truncated description text inside the column */
  .te-description-truncate {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 100%;
  }

  /* ── Project cell stacking ── */
  /* The project cell shows project name on top and account name muted below.
     We achieve this with a flex column layout on the <td>. */
  .te-project-name {
    display: block;
    font-weight: 500;
    color: var(--md-sys-color-on-surface);
  }

  .te-account-muted {
    display: block;
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-on-surface-variant);
    margin-top: 1px;
  }

  /* ── Form: rate field hint text ── */
  .field__hint {
    display: block;
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-on-surface-variant);
    margin-top: var(--space-1);
  }

  /* ── Checkbox field layout ── */
  .field--checkbox {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) 0;
  }

  .field__checkbox {
    width: 16px;
    height: 16px;
    accent-color: var(--md-sys-color-primary);
    cursor: pointer;
  }

  .field__checkbox-label {
    font-size: var(--md-typescale-body-medium-size);
    color: var(--md-sys-color-on-surface);
    cursor: pointer;
  }
}
/* ============================================================
   pages/subscriptions.css — Subscriptions page styles
   Recurring billing management table.
   ============================================================ */
@layer pages {

  /* ── Subscription status chips ── */
  /* Active: chip--success is already defined in components/chip.css */
  /* Ended: chip--neutral is already defined */
  /* Inactive: chip--warning is already defined */

  /* ── Form layout ── */
  /* The sub-form class is the form's root class (mirrors .account-form pattern) */
  .sub-form .form-section {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
  }

}
/* ============================================================
   pages/assets.css — Assets (gear inventory) page styles
   ------------------------------------------------------------
   Extends the base resource-page pattern from pages/accounts.css.
   Component styles (data-table, filter-bar, detail-drawer, chip)
   live in components/ and layout/ — only page-specific overrides here.
   ============================================================ */
@layer pages {

  /* ── Asset specs block (preformatted in drawer) ── */
  .asset-specs {
    font-family: var(--font-mono, "JetBrains Mono", "Courier New", monospace);
    font-size: var(--md-typescale-label-small-size);
    line-height: 1.6;
    color: var(--md-sys-color-on-surface);
    background: var(--md-sys-color-surface-container-low);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-sm);
    padding: var(--space-3);
    margin: 0;
    white-space: pre-wrap;       /* wrap long lines rather than overflow */
    word-break: break-all;
    max-height: 260px;           /* cap height so specs don't dominate the drawer */
    overflow-y: auto;
  }

  /* ── Hostname code display (inline in drawer KV list) ── */
  .asset-hostname {
    font-family: var(--font-mono, "JetBrains Mono", "Courier New", monospace);
    font-size: var(--md-typescale-label-medium-size);
    background: var(--md-sys-color-surface-container-low);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-xs, 2px);
    padding: 1px 5px;
    color: var(--md-sys-color-on-surface);
  }

  /* ── Monospace textarea variant (specs field in form) ── */
  .field__input--monospace {
    font-family: var(--font-mono, "JetBrains Mono", "Courier New", monospace);
    font-size: var(--md-typescale-label-small-size);
  }

  /* ── Chip variants for asset status (if not already in components/chip.css) ── */
  /* chip--muted moved to components/chip.css (shared semantic chip). */

  /* ── Form section hint text (below section title) ── */
  .form-section__hint {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    margin: calc(-1 * var(--space-2)) 0 var(--space-3);
    line-height: 1.4;
  }

  /* ── Field hint text (below a specific field) ── */
  .field__hint {
    display: block;
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-on-surface-variant);
    margin-top: var(--space-1);
    padding: 0 var(--space-3);
  }

  /* ── Phase 10: Overdue date indicator ── */
  /* Used in the gear index Due column and in the show drawer custody section.
     Error color from M3 tokens so it adapts to light/dark theme. */
  .asset-due--overdue {
    color: var(--md-sys-color-error);
    font-weight: var(--md-typescale-label-medium-weight);
  }

  /* ── Phase 10: Rental rates section on scan page ── */
  .scan-page__rental-rates {
    margin-bottom: var(--space-4);
  }

  .scan-page__rental-rates .drawer-kv {
    margin-bottom: var(--space-2);
  }

  .scan-page__membership-note {
    padding: var(--space-3);
    background: var(--md-sys-color-surface-container-low);
    border-radius: var(--radius-sm);
    border-left: 3px solid var(--md-sys-color-primary);
    margin-top: var(--space-2);
  }

  .scan-page__rent-form {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    margin-top: var(--space-4);
  }

  .scan-page__rent-note {
    margin-top: var(--space-3);
    font-size: var(--md-typescale-body-small-size);
  }

  /* ── Phase 10: Form subsection (rental rates / node fieldset revealed area) ── */
  /* The reveal panel holds a stack of floating-label `.field`s. Without an
     explicit column-flex + gap (the same contract `.form-section` uses), the
     fields collapse to zero-gap block boxes and the floated label of the SECOND
     field renders on top of the bottom border/hint of the FIRST — the visible
     "overlap" on the rental-rate reveal. The gap is the load-bearing fix. */
  .form-subsection {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
    padding: var(--space-4);
    background: var(--md-sys-color-surface-container-low);
    border-radius: var(--radius-sm);
    border: 1px solid var(--md-sys-color-outline-variant);
  }

  /* ── Phase 1 (commons): gear photo surfaces ──────────────────────────────
     All tokens-only; mirror the commons .post-card__photo treatment. */

  /* Form: current-photo thumbnail grid + per-photo remove control. */
  .asset-form__photos {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: var(--space-3);
    margin-bottom: var(--space-3);
  }

  .asset-form__photo-item {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    cursor: pointer;
  }

  .asset-form__photo-thumb {
    width: 100%;
    aspect-ratio: 1 / 1;
    -o-object-fit: cover;
       object-fit: cover;
    border-radius: var(--radius-md);
    background: var(--md-sys-color-surface-container);
  }

  .asset-form__photo-remove {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-on-surface-variant);
    min-height: 44px;          /* tap target (Apple HIG min) — staff use tablets */
    padding: 0 var(--space-2); /* side clearance for the touch zone */
  }

  /* Admin detail drawer: photo gallery. */
  .asset-detail__photos {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: var(--space-2);
  }

  .asset-detail__photo {
    width: 100%;
    aspect-ratio: 1 / 1;
    -o-object-fit: cover;
       object-fit: cover;
    border-radius: var(--radius-md);
    background: var(--md-sys-color-surface-container);
  }

  /* Gear scan page: larger lead photos. */
  .scan-page__photos {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-3);
    margin-top: var(--space-3);
    flex-basis: 100%;          /* always start on its own row after the chips */
  }

  .scan-page__photo {
    max-width: 100%;
    width: min(320px, 100%);
    aspect-ratio: 4 / 3;       /* cap portrait shots so they don't push the form off-screen */
    border-radius: var(--radius-sm);
    -o-object-fit: cover;
       object-fit: cover;
    background: var(--md-sys-color-surface-container);
  }

  /* ── Gear package form: item multi-select (Phase 2) ─────────────────────── */
  .gear-package-items {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    max-height: 18rem;
    overflow-y: auto;
    padding: var(--space-2);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-sm);
    background: var(--md-sys-color-surface-container-lowest);
  }

  .gear-package-items__option {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    cursor: pointer;
    padding: var(--space-1) var(--space-2);
    /* 44px tap-target minimum (WCAG 2.5.5 / Apple HIG) — the admin shell must be
       usable on tablets, where these multi-select rows are touched. The label
       wraps a 16px checkbox + 8px padding (~24px) without this floor. Keeps the
       flex centering and does not stretch single-line labels beyond 44px. */
    min-height: 44px;
    border-radius: var(--radius-xs);

    /* :focus-within mirrors :hover so a keyboard user tabbing the checkbox inside
       the label sees the whole row highlight (the global focus ring lands on the
       checkbox alone). Equivalent row-level feedback for pointer and keyboard. */
  }
    .gear-package-items__option:hover,.gear-package-items__option:focus-within {
      background: var(--md-sys-color-surface-container);
    }

  .gear-package-items__label {
    line-height: 1.3;
  }
}
/* ============================================================
   pages/trainings.css — LMS: Staff authoring + Learner surfaces
   ------------------------------------------------------------
   Covers:
     - Staff authoring: /trainings (table, drawer, lesson editor)
     - Learner surface: /learn (training cards, lesson page)

   Design system tokens are used throughout (--space-*, --md-sys-color-*,
   --md-typescale-*) to stay consistent with the rest of the app.
   ============================================================ */
@layer pages {

  /* ── Lesson editor layout ────────────────────────────────────────────── */

  /* Full-page lesson editor: gives breathing room for the tall textarea. */
  .lesson-editor-page {
    padding: var(--space-4) var(--space-6);
    max-width: 1200px;
    margin: 0 auto;
  }

  /* Two-column layout: textarea on left, preview on right.
     Switches to single-column on narrow screens. */
  .lesson-editor-layout {
    display: grid;
    /* 60/40: the textarea is where the work happens; a half-width empty
       preview column reads as a void before any content is rendered. */
    grid-template-columns: 3fr 2fr;
    gap: var(--space-4);
    align-items: start;
  }

  @media (max-width: 900px) {
    .lesson-editor-layout {
      grid-template-columns: 1fr;
    }
  }

  .lesson-editor__input-col {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
  }

  /* Taller monospace textarea for the markdown body. */
  .lesson-markdown-textarea {
    font-family: var(--font-mono, "JetBrains Mono", "Courier New", monospace);
    font-size: var(--md-typescale-label-small-size);
    line-height: 1.6;
    min-height: 400px;
    resize: vertical;
  }

  /* Preview pane header: "Preview" label + submit button side by side. */
  .lesson-preview-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding-bottom: var(--space-2);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    margin-bottom: var(--space-3);
  }

  .lesson-preview-header__label {
    font-size: var(--md-typescale-label-medium-size);
    font-weight: var(--md-typescale-label-medium-weight);
    color: var(--md-sys-color-on-surface-variant);
    text-transform: uppercase;
    letter-spacing: 0.06em;
  }

  /* Preview form: button only, no visible wrapping box. */
  .lesson-preview-form {
    display: contents; /* let the button sit inline with the label */
  }

  /* Placeholder shown before the operator clicks Preview. */
  .lesson-preview-placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    /* Match the textarea's min-height so the two columns read as one
       coherent editing surface instead of a tall input next to a stub. */
    min-height: 400px;
    border: 2px dashed var(--md-sys-color-outline-variant);
    border-radius: var(--radius-md);
    color: var(--md-sys-color-on-surface-variant);
  }

  /* The rendered preview — same prose styling as the learner lesson view. */
  .lesson-preview-rendered {
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-md);
    padding: var(--space-4);
    background: var(--md-sys-color-surface-container-lowest);
    min-height: 400px;
  }

  /* ── Lesson content (rendered Markdown) ─────────────────────────────── */

  /* Applied to both the learner view and the editor preview. */
  .lesson-content {
    max-width: 720px;
    color: var(--md-sys-color-on-surface);
    line-height: 1.7;
  }

  /* Prose typography for rendered Markdown. */
  .lesson-content h1,
  .lesson-content h2,
  .lesson-content h3,
  .lesson-content h4 {
    font-weight: 600;
    color: var(--md-sys-color-on-surface);
    margin-top: var(--space-6);
    margin-bottom: var(--space-2);
  }

  .lesson-content h2 { font-size: var(--md-typescale-title-large-size); }
  .lesson-content h3 { font-size: var(--md-typescale-title-medium-size); }

  .lesson-content p {
    margin-bottom: var(--space-4);
  }

  .lesson-content ul,
  .lesson-content ol {
    padding-left: var(--space-6);
    margin-bottom: var(--space-4);
  }

  /* Explicit markers: the base reset strips browser defaults, which left
     rendered-Markdown lists with no bullets/numbers at all. */
  .lesson-content ul { list-style: disc; }
  .lesson-content ol { list-style: decimal; }

  .lesson-content li { margin-bottom: var(--space-1); }

  .lesson-content code {
    font-family: var(--font-mono, "JetBrains Mono", "Courier New", monospace);
    font-size: 0.9em;
    background: var(--md-sys-color-surface-container-low);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-xs, 2px);
    padding: 1px 5px;
  }

  .lesson-content pre {
    background: var(--md-sys-color-surface-container-low);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-sm);
    padding: var(--space-4);
    overflow-x: auto;
    margin-bottom: var(--space-4);
  }

  .lesson-content pre code {
    background: none;
    border: none;
    padding: 0;
    font-size: var(--md-typescale-label-small-size);
  }

  .lesson-content table {
    border-collapse: collapse;
    width: 100%;
    margin-bottom: var(--space-4);
    font-size: var(--md-typescale-body-small-size);
  }

  .lesson-content th,
  .lesson-content td {
    border: 1px solid var(--md-sys-color-outline-variant);
    padding: var(--space-2) var(--space-3);
    text-align: left;
  }

  .lesson-content th {
    background: var(--md-sys-color-surface-container);
    font-weight: 600;
  }

  .lesson-content img {
    max-width: 100%;
    height: auto;
    border-radius: var(--radius-sm);
    border: 1px solid var(--md-sys-color-outline-variant);
    margin: var(--space-3) 0;
  }

  .lesson-content a {
    color: var(--md-sys-color-primary);
    text-decoration: underline;
  }

  /* ── Image snippets panel (in the lesson editor) ─────────────────────── */

  .lesson-image-list {
    margin-top: var(--space-6);
    padding-top: var(--space-6);
    border-top: 1px solid var(--md-sys-color-outline-variant);
  }

  .lesson-image-snippets {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-4);
  }

  .lesson-image-snippet-item {
    background: var(--md-sys-color-surface-container-low);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-md);
    padding: var(--space-3);
    max-width: 240px;
  }

  .lesson-image-thumbnail {
    margin-bottom: var(--space-2);
  }

  .lesson-image-snippet-item__name {
    margin: 0 0 var(--space-1);
    word-break: break-all;
  }

  .lesson-image-snippet-item__snippet {
    font-family: var(--font-mono, "JetBrains Mono", monospace);
    font-size: var(--md-typescale-label-small-size);
    background: var(--md-sys-color-surface-container);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-xs);
    padding: var(--space-2);
    white-space: pre-wrap;
    word-break: break-all;
    margin: 0;
    cursor: text; /* visual cue: selectable for copy */
    -webkit-user-select: all;
       -moz-user-select: all;
            user-select: all; /* click selects all for easy copy */
  }

  /* ── Training drawer — lesson row with position + reorder buttons ────── */

  .training-lesson-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
  }

  .training-lesson-row__pos {
    color: var(--md-sys-color-on-surface-variant);
    font-size: var(--md-typescale-label-small-size);
    min-width: 1.5ch;
    text-align: right;
  }

  .training-lesson-row__actions {
    display: flex;
    gap: 2px;
    flex-shrink: 0;
  }

  /* Inline form wrapper (for the reorder button forms). */
  .inline-form {
    display: inline;
  }

  /* ── Grant management row ─────────────────────────────────────────────── */

  .training-grant-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
  }

  /* Grant add form — select + button side by side. */
  .grant-add-form {
    margin-top: var(--space-3);
  }

  /* ── Learner surface — /learn pages ──────────────────────────────────── */

  /* Common page wrapper for the learn surface.
     1100px (not 900px): the catalogue is a card grid, not prose — the
     tighter prose measure lives on .learn-lesson-page below. */
  .learn-page {
    padding: var(--space-4) var(--space-6);
    max-width: 1100px;
    margin: 0 auto;
  }

  /* Breadcrumb trail above the training/lesson header. */
  .learn-breadcrumb {
    font-size: var(--md-typescale-label-medium-size);
    color: var(--md-sys-color-on-surface-variant);
    margin-bottom: var(--space-4);
  }

  /* ── Training catalogue — card grid ─────────────────────────────────── */

  .training-card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: var(--space-4);
    margin-top: var(--space-4);
  }

  .training-card-grid--compact {
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  }

  .training-card {
    background: var(--md-sys-color-surface);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-lg);
    padding: var(--density-card-p);
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    transition: box-shadow var(--md-sys-motion-duration-short2);
  }

    .training-card:hover {
      box-shadow: var(--md-sys-elevation-2);
    }

  .training-card--compact {
    padding: var(--space-3);
  }

  .training-card__header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-2);
  }

  .training-card__title {
    font-size: var(--md-typescale-title-medium-size);
    font-weight: var(--md-typescale-title-medium-weight);
    color: var(--md-sys-color-primary);
    text-decoration: none;
    flex: 1;
  }

    .training-card__title:hover {
      text-decoration: underline;
    }

  .training-card__summary {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    line-height: 1.5;
    flex: 1;
  }

  .training-card__summary--truncate {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  .training-card__meta {
    margin: 0;
  }

  .training-card__cta {
    margin-top: var(--space-2);
    align-self: flex-start;
  }

  /* ── Progress bar ────────────────────────────────────────────────────── */

  .training-progress {
    display: flex;
    align-items: center;
    gap: var(--space-2);
  }

  .training-progress__bar {
    flex: 1;
    height: 6px;
    background: var(--md-sys-color-surface-container);
    border-radius: var(--radius-full);
    overflow: hidden;
  }

  .training-progress__fill {
    height: 100%;
    background: var(--md-sys-color-primary);
    border-radius: var(--radius-full);
    transition: width 0.3s ease;
  }

  .training-progress__label {
    white-space: nowrap;
    font-size: var(--md-typescale-label-small-size);
  }

  /* ── Training show — lesson list ─────────────────────────────────────── */

  .learn-training-header {
    margin-bottom: var(--space-6);
    padding-bottom: var(--space-4);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
  }

  .learn-training-header__meta {
    margin-bottom: var(--space-2);
  }

  .learn-training-header__summary {
    font-size: var(--md-typescale-body-large-size);
    color: var(--md-sys-color-on-surface-variant);
    line-height: 1.6;
    margin: var(--space-3) 0;
  }

  .learn-lesson-list {
    margin-top: var(--space-4);
  }

  .learn-lesson-list__title {
    font-size: var(--md-typescale-title-large-size);
    font-weight: 600;
    margin-bottom: var(--space-3);
  }

  .learn-lesson-ol {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
  }

  .learn-lesson-item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    background: var(--md-sys-color-surface);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-md);
    transition: border-color var(--md-sys-motion-duration-short2);
  }

    .learn-lesson-item:hover {
      border-color: var(--md-sys-color-primary);
    }

  .learn-lesson-item--completed {
    background: color-mix(in srgb, var(--md-sys-color-primary) 5%, transparent);
    border-color: color-mix(in srgb, var(--md-sys-color-primary) 30%, transparent);
  }

  .learn-lesson-item__check {
    flex-shrink: 0;
    display: flex;
    align-items: center;
  }

  .learn-check-icon--done {
    color: var(--md-sys-color-primary);
  }

  .learn-check-icon--todo {
    color: var(--md-sys-color-outline);
  }

  .learn-lesson-item__content {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 2px;
  }

  .learn-lesson-item__title {
    font-size: var(--md-typescale-body-medium-size);
    color: var(--md-sys-color-primary);
    text-decoration: none;
  }

    .learn-lesson-item__title:hover { text-decoration: underline; }

  .learn-lesson-item__title--completed {
    color: var(--md-sys-color-on-surface-variant);
  }

  .learn-lesson-item__meta {
    font-size: var(--md-typescale-label-small-size);
  }

  .learn-lesson-item__number {
    font-size: var(--md-typescale-label-small-size);
    min-width: 2ch;
    text-align: right;
    flex-shrink: 0;
  }

  /* ── Lesson page ─────────────────────────────────────────────────────── */

  .learn-lesson-page {
    max-width: 760px;
  }

  .learn-lesson-header {
    margin-bottom: var(--space-6);
  }

  .learn-lesson-header__position {
    margin-bottom: var(--space-1);
  }

  .learn-lesson-header__badge {
    margin-top: var(--space-2);
  }

  /* ── Video embed (responsive 16:9) ───────────────────────────────────── */

  .learn-video-wrapper {
    position: relative;
    width: 100%;
    padding-bottom: 56.25%; /* 16:9 aspect ratio */
    margin-bottom: var(--space-6);
    border-radius: var(--radius-md);
    overflow: hidden;
    background: var(--md-sys-color-surface-container-lowest);
  }

  .learn-video-iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: none;
  }

  /* ── Completion area ─────────────────────────────────────────────────── */

  .learn-completion-area {
    margin: var(--space-8) 0 var(--space-6);
    padding: var(--space-6);
    background: var(--md-sys-color-surface-container-lowest);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-lg);
    display: flex;
    align-items: center;
    gap: var(--space-4);
  }

  .learn-completion-done {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-wrap: wrap;
  }

  .learn-completion-done__badge {
    font-size: var(--md-typescale-label-medium-size);
  }

  /* ── Prev/Next navigation ─────────────────────────────────────────────── */

  .learn-lesson-nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    margin-top: var(--space-6);
    padding-top: var(--space-6);
    border-top: 1px solid var(--md-sys-color-outline-variant);
    flex-wrap: wrap;
  }

  .learn-lesson-nav__spacer {
    flex: 1;
  }

  .learn-lesson-nav__prev,
  .learn-lesson-nav__next {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    max-width: 45%;
    min-width: 0;   /* allow flex shrink below intrinsic width */
    white-space: nowrap;
    overflow: hidden;
  }

  /* text-overflow must be on a block child — it has no effect on flex items
     directly. Wrap the lesson title text in .learn-lesson-nav__label. */
  .learn-lesson-nav__label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
  }

  /* ── Sign-in nudge (for public visitors) ─────────────────────────────── */

  .learn-signin-nudge {
    margin-top: var(--space-4);
    padding: var(--space-3) var(--space-4);
    background: var(--md-sys-color-surface-container-lowest);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-md);
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
  }

  /* ── btn--xs — extra-small button variant (for reorder buttons) ─────── */
  /* If btn--xs doesn't exist in components/button.css, define it here.
     It's small enough to not require its own component file. */
  .btn--xs {
    height: calc(var(--density-chip-height, 24px) + 2px);
    padding: 0 var(--space-2);
    font-size: var(--md-typescale-label-small-size);
    min-width: 2rem;
  }

  /* ── Drawer count badge — canonical definition promoted to components/chip.css ──
     (chip-adjacent; reconciled to accounts.css version — 18px height, 10px font)
     The rule is no longer defined here to avoid duplication. */

  /* ── text-muted helpers (may already exist, safe to redefine) ─────────── */
  .text-muted-sm {
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-on-surface-variant);
  }

}
/* ============================================================
   pages/skills.css — Phase 7a+7b Skills Matrix & Mine
   ------------------------------------------------------------
   Covers three surfaces:
     1. /skills/matrix  — the co-op's shared skills grid
     2. /skills/mine    — a member's self-assessment page
     3. /skills         — staff taxonomy management (uses accounts patterns)

   Design system tokens are used throughout (--space-*, --md-sys-color-*,
   --md-typescale-*). Chip classes for proficiency levels are added to
   components/chip.css (chip--expert is new).
   ============================================================ */
@layer pages {

  /* ================================================================
     1. Skills Matrix — /skills/matrix
     ================================================================ */

  /* ── Legend row above the matrix ── */
  .skills-matrix__legend {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-4);
    flex-wrap: wrap;
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
  }

  .skills-matrix__legend-label {
    font-weight: 600;
    color: var(--md-sys-color-on-surface);
  }

  .skills-matrix__legend-sep {
    color: var(--md-sys-color-outline);
    margin: 0 var(--space-1);
  }

  .skills-matrix__icon-legend {
    display: inline-flex;
    align-items: center;
    gap: 4px;
  }

  .skills-matrix__icon-desc {
    font-size: var(--md-typescale-body-small-size);
  }

  /* ── Scrollable wrapper (horizontal scroll for many skills) ── */
  .skills-matrix__scroll-wrapper {
    /* Span the content area: without an explicit width the small matrix
       floats as an island in the top-left of a wide page. */
    width: 100%;
    overflow-x: auto;
    padding: var(--space-4);
    /* Smooth horizontal scrolling on touch devices */
    -webkit-overflow-scrolling: touch;
  }

  /* ── The dense matrix table ── */
  .skills-matrix__table {
    border-collapse: collapse;
    font-size: var(--md-typescale-body-small-size);
    /* Don't let the table collapse narrower than its content */
    min-width: -moz-max-content;
    min-width: max-content;
  }

  /* ── Header rows ── */
  .skills-matrix__header-row {
    background: var(--md-sys-color-surface-container);
  }

  .skills-matrix__th {
    padding: var(--space-2) var(--space-3);
    font-weight: 600;
    color: var(--md-sys-color-on-surface);
    text-align: left;
    white-space: nowrap;
    border-bottom: 2px solid var(--md-sys-color-outline-variant);
    border-right: 1px solid var(--md-sys-color-outline-variant);
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--md-sys-color-surface-container);
  }

    .skills-matrix__th:last-child { border-right: none; }

  /* Category group header — spans multiple skill columns */
  .skills-matrix__th--category {
    text-align: center;
    font-size: var(--md-typescale-label-small-size);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--md-sys-color-on-surface-variant);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    border-right: 2px solid var(--md-sys-color-outline-variant);
  }

  /* Individual skill column header — rotated for density */
  .skills-matrix__th--skill {
    padding: var(--space-3) var(--space-2);
    /* 140px (was 80px): the old cap truncated ordinary names ("Linux
       Sysadmin" → "Linux Sy…"). Revisit vertical-rl headers if the
       matrix ever outgrows the viewport. */
    max-width: 140px;
    overflow: hidden;
  }

  .skills-matrix__skill-label {
    display: block;
    /* Rotate headers for very dense matrices (optional — can remove for readability) */
    /* writing-mode: vertical-rl; transform: rotate(180deg); */
    font-size: var(--md-typescale-label-small-size);
    font-weight: 500;
    color: var(--md-sys-color-on-surface);
    /* Wrap rather than truncate at today's column counts. */
    white-space: normal;
    word-break: break-word;
    max-width: 140px;
  }

  /* Gap indicator dot (red/orange dot on skill headers with no proficient member) */
  .skills-matrix__gap-dot {
    color: var(--md-sys-color-error);
    font-size: 8px;
    vertical-align: super;
  }

  /* ── Sticky first column (member name) ── */
  .skills-matrix__sticky-col {
    position: sticky;
    left: 0;
    z-index: 3;
    background: var(--md-sys-color-surface-container);
    border-right: 2px solid var(--md-sys-color-outline-variant);
    min-width: 120px;
    max-width: 180px;
  }

  .skills-matrix__th--member {
    /* Sticky in both axes: top: 0 (already set on .skills-matrix__th) + left: 0 */
    z-index: 4;  /* above both the sticky header row and sticky column */
  }

  /* ── Data rows ── */
  .skills-matrix__row {
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
  }

    .skills-matrix__row:last-child { border-bottom: none; }
    .skills-matrix__row:nth-child(even) { background: color-mix(in srgb, var(--md-sys-color-surface-container) 30%, transparent); }
    .skills-matrix__row:hover { background: color-mix(in srgb, var(--md-sys-color-primary) 5%, transparent); }

  /* ── Data cells ── */
  .skills-matrix__td {
    padding: var(--space-2) var(--space-2);
    border-right: 1px solid var(--md-sys-color-outline-variant);
    vertical-align: middle;
  }

    .skills-matrix__td:last-child { border-right: none; }

  /* Member name cell (sticky first column) */
  .skills-matrix__td--member {
    background: var(--md-sys-color-surface);
    font-size: var(--md-typescale-body-small-size);
    font-weight: 500;
    color: var(--md-sys-color-on-surface);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;

    /* Inherit even-row tint from parent row */
  }
    .skills-matrix__row:nth-child(even) .skills-matrix__td--member {
      background: color-mix(in srgb, var(--md-sys-color-surface-container) 30%, transparent);
    }
    .skills-matrix__row:hover .skills-matrix__td--member {
      background: color-mix(in srgb, var(--md-sys-color-primary) 5%, transparent);
    }

  /* Proficiency cell — contains chip + optional icons */
  .skills-matrix__td--cell {
    text-align: center;
    min-width: 52px;
    padding: var(--space-1) var(--space-2);
  }

  /* Teach/learn icon within a cell */
  .skills-matrix__icon {
    display: inline-flex;
    align-items: center;
    margin-left: 2px;
    vertical-align: middle;
  }

  .skills-matrix__icon--teach {
    color: var(--md-sys-color-primary);
    opacity: 0.8;
  }

  .skills-matrix__icon--learn {
    color: var(--md-sys-color-tertiary);
    opacity: 0.8;
  }

  /* ── Footnote below the matrix ── */
  .skills-matrix__footnote {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    padding: var(--space-3) var(--space-4);
    text-align: center;
    border-top: 1px solid var(--md-sys-color-outline-variant);
  }

  /* ================================================================
     2. Skills Mine — /skills/mine
     ================================================================ */

  /* ── Section containers ── */
  .skills-mine__section {
    padding: var(--space-4);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
  }

    .skills-mine__section:last-child { border-bottom: none; }

  .skills-mine__section-title {
    font-size: var(--md-typescale-title-medium-size);
    font-weight: var(--md-typescale-title-medium-weight);
    color: var(--md-sys-color-on-surface);
    margin-bottom: var(--space-3);
  }

  /* ── Category group header ── */
  .skills-mine__category-group {
    margin-bottom: var(--space-4);
  }

    .skills-mine__category-group:last-child { margin-bottom: 0; }

  .skills-mine__category-label {
    font-size: var(--md-typescale-label-medium-size);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--md-sys-color-on-surface-variant);
    margin-bottom: var(--space-2);
  }

  /* ── List of user skill rows ── */
  .skills-mine__list {
    display: flex;
    flex-direction: column;
    gap: 0;
  }

  /* ── Individual skill row ── */
  .skills-mine__row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-md);
    border: 1px solid var(--md-sys-color-outline-variant);
    margin-bottom: var(--space-2);
    background: var(--md-sys-color-surface);
    flex-wrap: wrap;
  }

    .skills-mine__row:last-child { margin-bottom: 0; }
    .skills-mine__row:hover { border-color: var(--md-sys-color-outline); }

  .skills-mine__row-name {
    flex: 1;
    min-width: 140px;
    display: flex;
    align-items: center;
    gap: var(--space-2);
  }

  .skills-mine__skill-name {
    font-size: var(--md-typescale-body-medium-size);
    font-weight: 500;
    color: var(--md-sys-color-on-surface);
  }

  .skills-mine__evidence-badge {
    font-size: var(--md-typescale-label-small-size);
  }

  .skills-mine__row-proficiency {
    flex-shrink: 0;
  }

  .skills-mine__row-signals {
    display: flex;
    align-items: center;
    gap: var(--space-1);
  }

  .skills-mine__signal {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: var(--md-typescale-label-small-size);
  }

  .skills-mine__row-notes {
    width: 100%;
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    padding-left: var(--space-2);
    font-style: italic;
  }

  .skills-mine__row-actions {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    flex-shrink: 0;
    margin-left: auto;
  }

  .skills-mine__remove-btn {
    color: var(--md-sys-color-error);
  }

    .skills-mine__remove-btn:hover { background: color-mix(in srgb, var(--md-sys-color-error) 8%, transparent); }

  /* ── Add skill form ── */
  .skills-mine__add-form {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    max-width: 520px;
  }

  /* ── Suggest skill form ── */
  .skills-mine__suggest-form {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    max-width: 520px;
  }

  /* ── Checkboxes (teach/learn) ── */
  .skills-mine__checkboxes {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
  }

  .skills-mine__checkbox-label {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    cursor: pointer;
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface);
  }

  .skills-mine__checkbox {
    flex-shrink: 0;
    width: 16px;
    height: 16px;
    margin-top: 2px;
    accent-color: var(--md-sys-color-primary);
    cursor: pointer;
  }

  /* ── Inline edit form wrapper ── */
  .skills-mine__edit-form-wrapper {
    padding: var(--space-3);
    border: 1px solid var(--md-sys-color-primary);
    border-radius: var(--radius-md);
    background: color-mix(in srgb, var(--md-sys-color-primary) 4%, transparent);
  }

  .skills-mine__edit-form-title {
    font-size: var(--md-typescale-body-medium-size);
    color: var(--md-sys-color-on-surface);
    margin-bottom: var(--space-3);
  }

  .skills-mine__edit-form {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    max-width: 480px;
  }

  /* ── Empty state (inline, within a section) ── */
  .empty-state--inline {
    padding: var(--space-4);
    text-align: left;
    border: none;
  }

  /* ── Onboarding nudge card — shown on dashboard when zero skills ── */
  .skills-nudge-card {
    /* Uses .card base styles; this adds the call-to-action phrasing style */
    border-left: 4px solid var(--md-sys-color-primary);
  }

  .skills-nudge-card__headline {
    font-size: var(--md-typescale-title-medium-size);
    font-weight: var(--md-typescale-title-medium-weight);
    color: var(--md-sys-color-on-surface);
    margin-bottom: var(--space-1);
  }

  .skills-nudge-card__body {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    margin-bottom: var(--space-3);
    line-height: 1.5;
  }

  /* ── Gap panel — staff dashboard sidebar ── */
  .skills-gap-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0;
  }

  .skills-gap-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    gap: var(--space-2);
  }

    .skills-gap-item:last-child { border-bottom: none; }

  .skills-gap-item__name {
    font-size: var(--md-typescale-body-medium-size);
    color: var(--md-sys-color-on-surface);
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .skills-gap-item__category {
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-on-surface-variant);
    white-space: nowrap;
  }

  /* ── Training skill add form (inside training drawer) ── */
  .training-skill-add-form {
    margin-top: var(--space-3);
  }

}
/* ============================================================
   pages/gear-library.css — Phase 8: Gear library styles
   ------------------------------------------------------------
   Covers:
     1. Scan page  (/gear/scan/:token)
     2. Label sheet (/gear/labels) including @media print rules
     3. Custody chain (inline in asset drawer + scan page)
     4. QR block in the drawer
   ============================================================ */
@layer pages {

  /* ──────────────────────────────────────────────────────────
     1. SCAN PAGE — /gear/scan/:token
     Mixed audience: public (minimal) and signed-in (full).
     Centered card layout, max-width for readability on mobile.
     ────────────────────────────────────────────────────────── */

  .scan-page {
    /* Left-aligned (not centred): this page renders inside the desktop app
       shell too, and a centred 640px island reads as misplaced there. On a
       phone (the primary audience) the width cap fills the screen anyway. */
    max-width: 720px;
    margin: 0;
    padding: var(--space-6) var(--space-4);
  }

  /* Page header: hostname big + chips inline */
  .scan-page__header {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin-bottom: var(--space-5);
  }

  .scan-page__hostname {
    font-family: var(--font-mono, "JetBrains Mono", "Courier New", monospace);
    font-size: var(--md-typescale-headline-small-size);
    font-weight: 600;
    color: var(--md-sys-color-on-surface);
    margin: 0;
    word-break: break-all;
    /* Full width so chips wrap below on mobile */
    flex-basis: 100%;
  }

  /* Section card */
  .scan-page__section {
    background: var(--md-sys-color-surface-container-low);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-md);
    padding: var(--space-4);
    margin-bottom: var(--space-4);
  }

  .scan-page__section-title {
    font-size: var(--md-typescale-label-large-size);
    font-weight: 600;
    color: var(--md-sys-color-on-surface);
    margin: 0 0 var(--space-3) 0;
    text-transform: uppercase;
    letter-spacing: 0.05em;
  }

  /* Current holder display */
  .scan-page__holder-card {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    align-items: baseline;
  }

  .scan-page__holder-name {
    font-family: var(--font-mono, "JetBrains Mono", monospace);
    font-size: var(--md-typescale-body-large-size);
    font-weight: 600;
    color: var(--md-sys-color-primary);
  }

  .scan-page__holder-since {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
  }

  .scan-page__no-holder {
    color: var(--md-sys-color-on-surface-variant);
    font-size: var(--md-typescale-body-medium-size);
    margin: 0;
  }

  /* Required trainings list */
  .scan-page__training-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
  }

  .scan-page__training-item {
    display: flex;
    align-items: center;
    gap: var(--space-2);
  }

  .scan-page__training-check {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: var(--md-sys-color-secondary-container);
    color: var(--md-sys-color-on-secondary-container);
    flex-shrink: 0;
  }

  .scan-page__training-incomplete {
    flex-shrink: 0;
  }

  /* Gate warning — shown when trainings are incomplete */
  .scan-page__gate-warning {
    color: var(--md-sys-color-error);
    font-size: var(--md-typescale-body-small-size);
    margin-bottom: var(--space-3);
    padding: var(--space-2) var(--space-3);
    background: var(--md-sys-color-error-container);
    color: var(--md-sys-color-on-error-container);
    border-radius: var(--radius-sm);
  }

  /* Claim form */
  .scan-page__claim-form {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
  }

  .scan-page__note-field {
    margin-bottom: 0;
  }

  .scan-page__claim-btn {
    align-self: flex-start;
  }

  /* Already holder state */
  .scan-page__already-holder {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
  }

  .scan-page__already-holder-text {
    color: var(--md-sys-color-on-surface-variant);
    font-size: var(--md-typescale-body-medium-size);
    margin: 0;
  }

  /* Alert (gate failure or error) */
  .scan-page__alert {
    background: var(--md-sys-color-error-container);
    color: var(--md-sys-color-on-error-container);
    border-radius: var(--radius-sm);
    padding: var(--space-3) var(--space-4);
    margin-bottom: var(--space-4);
    font-size: var(--md-typescale-body-medium-size);
    line-height: 1.5;
  }

  /* Flash (success) — appears as a banner above the page */
  .scan-page__flash {
    margin-bottom: var(--space-4);
    position: relative;
  }

  /* Custody chain on scan page */
  .scan-page__custody-chain {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
  }

  .scan-page__custody-item {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-2);
    align-items: baseline;
    padding: var(--space-1) 0;
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
  }

  .scan-page__custody-item:last-child {
    border-bottom: none;
  }

  .scan-page__custody-holder {
    font-family: var(--font-mono, "JetBrains Mono", monospace);
    font-size: var(--md-typescale-body-small-size);
    font-weight: 600;
    color: var(--md-sys-color-on-surface);
  }

  .scan-page__custody-time {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
  }

  .scan-page__custody-note {
    font-size: var(--md-typescale-body-small-size);
  }

  .scan-page__custody-overflow {
    font-size: var(--md-typescale-body-small-size);
    margin-top: var(--space-2);
  }

  .scan-page__no-history {
    font-size: var(--md-typescale-body-medium-size);
  }

  /* Return / check-in section (Phase 1; Phase 5 adds the condition form) */
  .scan-page__return-form {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    margin-bottom: var(--space-2);
  }

  /* Condition note/photo inputs (Phase 5) on the claim + return forms. The .field
     contract already styles the controls; this just keeps the file input from
     stretching oddly and gives the block a consistent rhythm with its siblings. */
  .condition-field {
    margin-bottom: 0;
  }

  .scan-page__return-btn {
    align-self: flex-start;
    margin-bottom: var(--space-2);
  }

  .scan-page__return-hint {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    margin: 0;
  }

  /* Borrow request section */
  .scan-page__borrow-form {
    margin-bottom: var(--space-2);
  }

  .scan-page__borrow-hint {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    margin: 0;
  }

  .scan-page__borrow-unavailable {
    font-size: var(--md-typescale-body-medium-size);
    margin: 0;
  }

  /* Staff links section */
  .scan-page__section--staff-links {
    background: transparent;
    border: none;
    padding: var(--space-2) 0;
    display: flex;
    gap: var(--space-4);
  }

  .scan-page__asset-link {
    display: inline;
  }

  /* Signed-out minimal view */
  .scan-page__signed-out {
    text-align: center;
    padding: var(--space-8) var(--space-4);
  }

  .scan-page__signed-out-msg {
    font-size: var(--md-typescale-body-large-size);
    color: var(--md-sys-color-on-surface-variant);
    margin-bottom: var(--space-5);
  }

  .scan-page__signin-btn {
    font-size: var(--md-typescale-label-large-size);
  }

  /* ──────────────────────────────────────────────────────────
     2. CUSTODY CHAIN IN DRAWER (_show_frame)
     ────────────────────────────────────────────────────────── */

  .drawer-custody-chain {
    list-style: none;
    padding: 0;
    margin: var(--space-2) 0 0 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
  }

  .drawer-custody-item {
    display: flex;
    flex-wrap: wrap;
    gap: 0 var(--space-2);
    align-items: baseline;
    padding: var(--space-1) 0;
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    font-size: var(--md-typescale-body-small-size);
  }

  .drawer-custody-item:last-child {
    border-bottom: none;
  }

  .drawer-custody-item__holder {
    font-family: var(--font-mono, "JetBrains Mono", monospace);
    font-weight: 600;
    color: var(--md-sys-color-on-surface);
  }

  .drawer-custody-item__time {
    color: var(--md-sys-color-on-surface-variant);
  }

  .drawer-custody-item__note {
    font-size: var(--md-typescale-label-small-size);
  }

  .drawer-custody-overflow {
    font-size: var(--md-typescale-label-small-size);
    margin-top: var(--space-2);
  }

  /* ──────────────────────────────────────────────────────────
     3. QR BLOCK IN DRAWER
     ────────────────────────────────────────────────────────── */

  .drawer-qr-block {
    display: flex;
    gap: var(--space-4);
    align-items: flex-start;
  }

  /* The SVG QR code itself */
  .drawer-qr-block__qr {
    flex-shrink: 0;
    /* QR codes must be square; let the SVG scale naturally */
    width: 80px;
    height: 80px;
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-xs, 2px);
    background: #fff;  /* QR codes need a white background to scan */
    padding: 2px;
    overflow: hidden;
  }

  .drawer-qr-block__qr svg {
    display: block;
    width: 100%;
    height: 100%;
  }

  .drawer-qr-block__meta {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
  }

  .drawer-qr-block__url code {
    font-family: var(--font-mono, "JetBrains Mono", monospace);
    font-size: var(--md-typescale-label-small-size);
    word-break: break-all;
    color: var(--md-sys-color-on-surface-variant);
  }

  /* ──────────────────────────────────────────────────────────
     4. LABELS PAGE — /gear/labels
     Screen styles for the label grid + print overrides
     ────────────────────────────────────────────────────────── */

  .labels-page {
    padding: var(--space-4);
  }

  .labels-page__instructions {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    margin-bottom: var(--space-4);
  }

  /* Label grid — screen view: 3-column on desktop, 2-column on tablet, 1 on mobile */
  .labels-grid {
    display: grid;
    /* Cap cell width: with few assets, 1fr cells stretch absurdly wide and
       the sheet looks broken. Print keeps its own fixed 3-column rule. */
    grid-template-columns: repeat(auto-fill, minmax(160px, 220px));
    gap: var(--space-4);
  }

  /* Individual label cell */
  .labels-grid__cell {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-3);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-sm);
    background: var(--md-sys-color-surface-container-low);
    text-align: center;
  }

  /* QR code in label grid */
  .labels-grid__qr {
    width: 120px;
    height: 120px;
    background: #fff;
    padding: 4px;
    border: 1px solid var(--md-sys-color-outline-variant);
    flex-shrink: 0;
  }

  .labels-grid__qr svg {
    display: block;
    width: 100%;
    height: 100%;
  }

  .labels-grid__hostname {
    font-family: var(--font-mono, "JetBrains Mono", monospace);
    font-size: var(--md-typescale-label-medium-size);
    font-weight: 600;
    color: var(--md-sys-color-on-surface);
    word-break: break-all;
  }

  .labels-grid__node-id {
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-on-surface-variant);
  }

  .labels-grid__scan-url {
    /* Screen-legible size; print overrides this down to 5pt below. */
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-on-surface-variant);
    word-break: break-all;
    font-family: var(--font-mono, monospace);
  }

  /* ── PRINT STYLES ─────────────────────────────────────────────────────────
     @media print strips the app chrome and renders a clean label grid.
     Labels should be printable on:
       - Standard label sheets (Avery 5160 etc.)
       - Regular A4/Letter cut into labels
     ──────────────────────────────────────────────────────────────────────── */

  @media print {
    /* Hide everything that isn't the labels grid */
    .no-print,
    .app-top-bar,
    .app-nav,
    nav,
    header,
    .detail-drawer,
    .page-header,
    .labels-page__header,
    .labels-page__instructions {
      display: none !important;
    }

    /* Reset page margins for clean label printing */
    @page {
      margin: 0.5cm;
    }

    body {
      background: white;
      color: black;
    }

    .labels-page {
      padding: 0;
    }

    /* Print grid: 3 columns at 6cm each (fits A4/Letter landscape) */
    .labels-grid {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: 4mm;
    }

    /* Each label cell: fixed size, black border */
    .labels-grid__cell {
      border: 1px solid #000;
      border-radius: 2mm;
      padding: 3mm;
      -moz-column-break-inside: avoid;
           break-inside: avoid;  /* Don't split a label across pages */
      page-break-inside: avoid;
      background: white;
    }

    /* QR code: slightly larger for easier scanning from print */
    .labels-grid__qr {
      width: 30mm;
      height: 30mm;
    }

    .labels-grid__hostname {
      font-size: 8pt;
      color: black;
    }

    .labels-grid__node-id {
      font-size: 7pt;
      color: #333;
    }

    .labels-grid__scan-url {
      font-size: 5pt;
      color: #666;
    }
  }

}
/* ============================================================
   pages/ledger.css — Phase 9: Community hour ledger styles
   ------------------------------------------------------------
   Covers:
     1. Balance hero (MY balance prominent card at top)
     2. Ledger layout (2-column: main + sidebar)
     3. Ledger stats (co-op overview dl)
     4. Ledger recent list (compact exchange entries)
     5. Zero-sum invariant check badge
     6. Ledger amount color chips (credit / debit / gift)
   ============================================================ */
@layer pages {

  /* ─────────────────────────────────────────────────────────
     1. BALANCE HERO — MY BALANCE PROMINENTLY DISPLAYED
     ───────────────────────────────────────────────────────── */

  /* Sidebar/inline note copy (replaces borrowed form-section__hint). */
  .ledger-note {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    line-height: 1.5;
    margin: 0;
  }

  .ledger-note--above { margin-top: var(--space-3); }
  .ledger-note--below { margin-bottom: var(--space-3); }

  .ledger-balance-hero {
    background: var(--md-sys-color-surface-container);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--md-sys-shape-corner-medium);
    padding: var(--space-6) var(--space-8);
    margin-bottom: var(--space-6);
    /* Row layout: figure left, explanation right — a column layout left
       the wide panel two-thirds empty beside the number. Wraps on mobile. */
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-8);
  }

  .ledger-balance-hero__figure {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    flex-shrink: 0;
  }

  .ledger-balance-hero__label {
    font-size: var(--md-typescale-label-medium-size);
    font-weight: var(--md-typescale-label-medium-weight);
    letter-spacing: var(--md-typescale-label-medium-tracking);
    color: var(--md-sys-color-on-surface-variant);
    text-transform: uppercase;
  }

  .ledger-balance-hero__value {
    font-size: var(--md-typescale-display-medium-size, 2.8rem);
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: -0.02em;
  }

  .ledger-balance-hero__value--credit {
    color: var(--md-sys-color-tertiary, #0d6e0d);
  }

  .ledger-balance-hero__value--debit {
    color: var(--md-sys-color-error);
  }

  .ledger-balance-hero__value--even {
    color: var(--md-sys-color-on-surface-variant);
  }

  .ledger-balance-hero__explain {
    flex: 1;
    min-width: 280px;
    font-size: var(--md-typescale-body-medium-size);
    color: var(--md-sys-color-on-surface-variant);
    max-width: 56ch;
  }

  /* ─────────────────────────────────────────────────────────
     2. LEDGER LAYOUT — two-column grid
     ───────────────────────────────────────────────────────── */

  .ledger-layout {
    display: grid;
    grid-template-columns: 1fr 320px;
    gap: var(--space-6);
    align-items: start;
  }

  /* Stack columns below 960px */
  @media (max-width: 960px) {
    .ledger-layout {
      grid-template-columns: 1fr;
    }
  }

  /* Sidebar stacks its cards vertically */
  .ledger-sidebar {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
  }

  /* Main stacks its cards vertically */
  .ledger-main {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
  }

  /* ─────────────────────────────────────────────────────────
     3. LEDGER STATS — co-op overview definition list
     ───────────────────────────────────────────────────────── */

  .ledger-stats {
    display: flex;
    flex-direction: column;
    gap: 0;
    margin: 0;
    padding: 0;
  }

  .ledger-stats__row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
  }

    .ledger-stats__row:last-child {
      border-bottom: none;
    }

  .ledger-stats__label {
    font-size: var(--md-typescale-body-medium-size);
    color: var(--md-sys-color-on-surface-variant);
  }

  .ledger-stats__value {
    font-size: var(--md-typescale-body-medium-size);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--md-sys-color-on-surface);
  }

  /* ─────────────────────────────────────────────────────────
     4. LEDGER RECENT LIST — compact exchange entry list
     ───────────────────────────────────────────────────────── */

  .ledger-recent-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0;
  }

  .ledger-recent-item {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: var(--space-2);
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    font-size: var(--md-typescale-body-small-size);
  }

    .ledger-recent-item:last-child {
      border-bottom: none;
    }

  .ledger-recent-item__who {
    color: var(--md-sys-color-on-surface);
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .ledger-recent-item__when {
    flex-shrink: 0;
    white-space: nowrap;
  }

  /* ─────────────────────────────────────────────────────────
     5. ZERO-SUM INVARIANT CHECK BADGE
     ───────────────────────────────────────────────────────── */

  .ledger-invariant-check {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-4);
    border-radius: var(--md-sys-shape-corner-small);
    font-size: var(--md-typescale-body-medium-size);
    font-weight: 600;
    margin-top: var(--space-4);
  }

  .ledger-invariant-check--ok {
    background: color-mix(in srgb, var(--md-sys-color-tertiary-container, #d1fad1) 40%, transparent);
    color: var(--md-sys-color-on-tertiary-container, #0d4f0d);
    border: 1px solid color-mix(in srgb, var(--md-sys-color-tertiary, #1a7a1a) 30%, transparent);
  }

  .ledger-invariant-check--error {
    background: var(--md-sys-color-error-container);
    color: var(--md-sys-color-on-error-container);
    border: 1px solid var(--md-sys-color-error);
  }

  .ledger-invariant-check__icon {
    font-size: 1.2em;
    line-height: 1;
  }

  /* ─────────────────────────────────────────────────────────
     6. LEDGER AMOUNT COLOR ANNOTATIONS
     Used in the data-table cells for credit / debit / gift amounts.
     ───────────────────────────────────────────────────────── */

  /* Credit (I gave work): green/tertiary color */
  .ledger-amount--credit {
    color: var(--md-sys-color-tertiary, #1a7a1a);
    font-weight: 600;
  }

  /* Debit (I received work): muted warning/neutral */
  .ledger-amount--debit {
    color: var(--md-sys-color-error);
    font-weight: 600;
  }

  /* Gift (aide hours): accent/primary color — celebrating contribution */
  .ledger-amount--gift {
    color: var(--md-sys-color-primary);
    font-weight: 600;
  }

}
/* ============================================================
   pages/users.css — Members administration page styles
   Dense table, role chips, invite form.
   Most layout is inherited from components/data-table.css
   and components/text-field.css; this file holds only
   user-page-specific overrides.
   ============================================================ */
@layer pages {

  /* Email is the first column and would otherwise absorb all spare table
     width, stretching rows into hard-to-scan lines. Cap it; ellipsis comes
     from the base .data-table__td rules. */
  .data-table[aria-label="Members"] .data-table__td:first-child,
  .data-table[aria-label="Members"] .data-table__th:first-child {
    max-width: 340px;
    width: 340px;
  }

  /* The user form shares the same .user-form class as the accounts pattern.
     No special overrides needed — field density comes from .form-section. */
  .user-form {
    display: flex;
    flex-direction: column;
    gap: 0;  /* form-section handles gap/padding internally */
  }
}
/* ============================================================
   pages/search.css — Global search results page + combobox dropdown
   ------------------------------------------------------------
   Styles for:
     - .search-page       : full /search results page shell
     - .search-results    : grouped results container
     - .search-group      : one model-type section (type header + rows)
     - .search-result     : a single result row
     - .search-result__link, .search-result__primary, .search-result__meta
     - .search-group__see-all : "See all…" link at bottom of capped group

   The dropdown panel itself (.top-bar__search-results) is styled in
   layout/top-bar.css; the combobox Stimulus controller handles visibility.
   ============================================================ */
@layer pages {

  /* ── Full results page ── */
  .search-page {
    padding: var(--space-4);
    max-width: 800px;
  }

    .search-page .page-header {
      margin-bottom: var(--space-5);
    }

  /* ── Grouped results wrapper ── */
  .search-results {
    display: flex;
    flex-direction: column;
    /* space-7 (was space-5): inter-group spacing must read clearly wider
       than the intra-group row spacing or the groups blur together. */
    gap: var(--space-7);
  }

  /* ── One result group (per model type) ── */
    /* Type heading */
    .search-group .search-group__type {
      font-size: var(--md-typescale-label-small-size);
      font-weight: 600;
      color: var(--md-sys-color-on-surface-variant);
      text-transform: uppercase;
      letter-spacing: 0.06em;
      margin-bottom: var(--space-2);
      padding-bottom: var(--space-1);
      border-bottom: 1px solid var(--md-sys-color-outline-variant);
    }

    /* Result list */
    .search-group .search-group__list {
      list-style: none;
      padding: 0;
      margin: 0;
    }

    /* See-all link */
    .search-group .search-group__see-all {
      display: inline-block;
      margin-top: var(--space-2);
      font-size: var(--md-typescale-label-medium-size);
      color: var(--md-sys-color-primary);
      text-decoration: none;
    }

      :is(.search-group .search-group__see-all):hover {
        text-decoration: underline;
      }

  /* ── Single result row ── */
  .search-result {
    border-radius: var(--radius-sm);
    overflow: hidden;
  }

    .search-result + .search-result {
      border-top: 1px solid var(--md-sys-color-outline-variant);
    }

  /* Result link — the whole row is clickable */
  .search-result__link {
    display: flex;
    align-items: baseline;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-3);
    text-decoration: none;
    color: inherit;
    border-radius: var(--radius-sm);
    transition: background var(--md-sys-motion-duration-short1);
  }

    .search-result__link:hover,.search-result__link:focus-visible {
      background: color-mix(in srgb, var(--md-sys-color-on-surface) 8%, transparent);
      outline: none;
    }

    /* Focus ring for keyboard nav */
    .search-result__link:focus-visible {
      outline: 2px solid var(--md-sys-color-primary);
      outline-offset: -2px;
    }

  .search-result__primary {
    font-size: var(--md-typescale-body-medium-size);
    font-weight: 500;
    color: var(--md-sys-color-on-surface);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    flex: 1;
  }

  .search-result__meta {
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-on-surface-variant);
    white-space: nowrap;
    flex-shrink: 0;
  }

  /* ── Dropdown variant ─────────────────────────────────────────────────────
     When results appear inside the combobox dropdown (not the full page),
     the wrapping .top-bar__search-results panel is already styled in
     layout/top-bar.css. We tweak the group headers to be more compact
     inside the constrained dropdown height. */
    .top-bar__search-results .search-group__type {
      font-size: 10px;
      padding: var(--space-2) var(--space-3) var(--space-1);
      margin-bottom: 0;
      background: var(--md-sys-color-surface-container-low);
      border-bottom: 1px solid var(--md-sys-color-outline-variant);
    }

    .top-bar__search-results .search-group__list {
      padding: var(--space-1) 0;
    }

    .top-bar__search-results .search-result + .search-result {
      border-top: none;
    }

    .top-bar__search-results .search-result__link {
      padding: var(--space-2) var(--space-3);
      border-radius: 0;
    }

    .top-bar__search-results .search-group__see-all {
      display: block;
      padding: var(--space-1) var(--space-3) var(--space-2);
      font-size: var(--md-typescale-label-small-size);
      border-top: 1px solid var(--md-sys-color-outline-variant);
    }

    /* Compact empty / loading state inside the dropdown */
    .top-bar__search-results .empty-state {
      padding: var(--space-4) var(--space-3);
    }

    .top-bar__search-results .empty-state__message {
      font-size: var(--md-typescale-body-small-size);
      color: var(--md-sys-color-on-surface-variant);
      text-align: center;
    }

    .top-bar__search-results .empty-state__sub {
      display: none; /* too verbose in the dropdown */
    }
}
/* ============================================================
   pages/email_suggestions.css — email review queue styles

   Styles specific to /email_suggestions: the inline convert form
   (details/summary expand), the email address code display, and
   the subject truncation. The data table shell is shared from
   components/data-table.css.
   ============================================================ */
@layer pages {

  /* ── Email address display ── */
  .email-addr {
    font-family: var(--md-sys-typescale-body-medium-font, monospace);
    font-size: 0.85em;
    background: var(--md-sys-color-surface-container, #f4f4f7);
    padding: 2px 6px;
    border-radius: 4px;
    color: var(--md-sys-color-on-surface);
    white-space: nowrap;
  }

  /* ── Subject line: truncate long subjects ── */
  .email-suggestions__subject {
    display: block;
    max-width: 280px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--md-sys-color-on-surface-variant);
    font-size: 0.85em;
  }

  /* ── Actions cell: horizontal flex layout ── */
  .email-suggestions__actions {
    display: flex;
    align-items: flex-start;
    gap: var(--space-2);
    flex-wrap: wrap;
  }

  /* ── Convert details/summary expand ── */
  .email-suggestions__convert-details {
    position: relative;
  }

  /* Style the <summary> like a tonal button */
  .email-suggestions__convert-details > summary {
    cursor: pointer;
    list-style: none;         /* remove the default triangle */
    -webkit-user-select: none;
       -moz-user-select: none;
            user-select: none;
  }

  /* Remove default triangle in webkit */
  .email-suggestions__convert-details > summary::-webkit-details-marker {
    display: none;
  }

  /* Expanded form panel */
  .email-suggestions__convert-form {
    position: absolute;
    z-index: 100;
    top: calc(100% + var(--space-1));
    left: 0;
    min-width: 280px;
    background: var(--md-sys-color-surface-container-high, #ece6f0);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-md, 8px);
    padding: var(--space-4);
    box-shadow: var(--md-sys-elevation-2, 0 2px 8px rgba(0,0,0,0.15));
  }

  /* Stack fields vertically */
  .email-suggestions__form-fields {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
  }

  /* Submit button row — right-aligned */
  .email-suggestions__form-actions {
    display: flex;
    justify-content: flex-end;
    padding-top: var(--space-2);
  }

  /* ── Alert (contact error) ── */
  .alert {
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-md, 8px);
    margin-bottom: var(--space-4);
    font-size: var(--md-typescale-body-medium-size);
  }

  .alert--error {
    background: color-mix(in srgb, var(--md-sys-color-error, #b3261e) 12%, transparent);
    border: 1px solid var(--md-sys-color-error, #b3261e);
    color: var(--md-sys-color-on-error-container, #410e0b);
  }

    .alert--error ul {
      margin: var(--space-1) 0 0 var(--space-4);
      padding: 0;
    }

  /* ── Chip warning variant (pending badge) ── */
  .chip--warning {
    background: color-mix(in srgb, var(--md-sys-color-tertiary, #7d5260) 16%, transparent);
    color: var(--md-sys-color-on-tertiary-container, #31111d);
    border-color: var(--md-sys-color-tertiary, #7d5260);
  }

}
/* ============================================================
   pages/marketplace.css — Phase 7c/10 Unified Marketplace
   ------------------------------------------------------------
   The co-op's offering surface: two shelves on one page.

   Surfaces:
     /marketplace — public index (equipment + skills shelves)

   Design notes:
     - Dense card grid, same M3 tokens as the rest of the system
     - Equipment cards: name, type badge, rate(s), action button
     - Skill cards: name, category badge, teach flag, member count, form panel
     - Public layout or app shell depending on auth (resolve_layout pattern)
     - The form panel (skill request) is hidden/shown by marketplace-request
       Stimulus controller — no page navigation, no Turbo Frame for the form
       itself (form submits redirect to full page, avoiding morphing complexity)
   ============================================================ */
@layer pages {

  /* ── Page wrapper ────────────────────────────────────────── */
  .marketplace-page {
    max-width: 1160px;
    padding: var(--space-6) var(--space-6) var(--space-12);
    margin: 0 auto;
  }

  /* When rendered inside the app shell (signed-in), the shell already
     provides margin on the left for the nav rail. On the public layout,
     the page centres itself inside .public-main. */

  /* ── Page header ─────────────────────────────────────────── */
  .marketplace-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: var(--space-4);
    margin-bottom: var(--space-8);
    flex-wrap: wrap;
  }

  .marketplace-header__title {
    font-size: var(--md-typescale-headline-medium-size);
    font-weight: var(--md-typescale-headline-medium-weight);
    color: var(--md-sys-color-on-background);
    margin: 0 0 var(--space-2);
  }

  .marketplace-header__intro {
    font-size: var(--md-typescale-body-medium-size);
    color: var(--md-sys-color-on-surface-variant);
    margin: 0;
    max-width: 60ch;
  }

  .marketplace-header__cta {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-shrink: 0;
  }

  .marketplace-header__cta-hint {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    /* Single line: mid-phrase wraps ("rates &" / "request forms") at
       intermediate widths read as broken copy. */
    white-space: nowrap;
  }

  /* ── Shelf sections ──────────────────────────────────────── */
  .marketplace-shelf {
    margin-bottom: var(--space-10);
  }

  .marketplace-shelf__header {
    margin-bottom: var(--space-5);
    padding-bottom: var(--space-3);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
  }

  .marketplace-shelf__title {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--md-typescale-title-large-size);
    font-weight: var(--md-typescale-title-large-weight);
    color: var(--md-sys-color-on-surface);
    margin: 0 0 var(--space-2);
  }

  .marketplace-shelf__icon {
    display: flex;
    align-items: center;
    color: var(--md-sys-color-primary);
  }

  .marketplace-shelf__desc {
    font-size: var(--md-typescale-body-medium-size);
    color: var(--md-sys-color-on-surface-variant);
    margin: 0;
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-wrap: wrap;
  }

  .marketplace-shelf__member-note {
    /* Inline with the description text */
  }

  /* ── Empty state ─────────────────────────────────────────── */
  .marketplace-empty {
    padding: var(--space-8) var(--space-6);
    text-align: center;
    color: var(--md-sys-color-on-surface-variant);
    background: var(--md-sys-color-surface-variant);
    border-radius: var(--radius-md);
    font-size: var(--md-typescale-body-medium-size);
  }

  /* ── Card grid ───────────────────────────────────────────── */
  .marketplace-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: var(--space-4);
  }

  /* A card with its request form open claims the whole grid row, so the
     expanded form doesn't stretch every sibling card in the same row into
     a tall hollow shell. :has() is supported in all browsers we target. */
  .marketplace-card:has(.marketplace-request-form:not([hidden])) {
    grid-column: 1 / -1;
  }

  /* ── Individual cards ────────────────────────────────────── */
  .marketplace-card {
    background: var(--md-sys-color-surface);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-md);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    transition: box-shadow 0.15s ease, border-color 0.15s ease;
  }

    .marketplace-card:hover {
      box-shadow: var(--elevation-2);
      border-color: var(--md-sys-color-outline);
    }

  /* Lead gear photo — full-bleed top, clipped by the card's overflow:hidden +
     border-radius. Fixed aspect so a sparse card reads as a product card. */
  .marketplace-card__photo {
    display: block;
    width: 100%;
    aspect-ratio: 5 / 3;
    -o-object-fit: cover;
       object-fit: cover;
    background: var(--md-sys-color-surface-container);
  }

  .marketplace-card__body {
    padding: var(--space-4);
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
  }

  .marketplace-card__type-badge {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
  }

  .marketplace-card__name {
    font-size: var(--md-typescale-title-medium-size);
    font-weight: var(--md-typescale-title-medium-weight);
    color: var(--md-sys-color-on-surface);
    margin: 0;
  }

  .marketplace-card__desc {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    margin: 0;
    /* Clamp description to 3 lines to keep card heights uniform */
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }

  /* ── Equipment card rates ────────────────────────────────── */
  .marketplace-card__rates {
    margin-top: auto;
    padding-top: var(--space-2);
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
  }

  .marketplace-card__rate {
    display: flex;
    align-items: baseline;
    gap: var(--space-2);
    font-size: var(--md-typescale-body-medium-size);
  }

  .marketplace-card__rate-label {
    color: var(--md-sys-color-on-surface-variant);
    font-size: var(--md-typescale-body-small-size);
    min-width: 60px;
  }

  .marketplace-card__rate-value {
    font-weight: 600;
    color: var(--md-sys-color-on-surface);
  }

  /* Member rate highlighted */
  .marketplace-card__rate--member .marketplace-card__rate-label {
    color: var(--md-sys-color-primary);
    font-weight: 600;
  }
  .marketplace-card__rate--member .marketplace-card__rate-value {
    color: var(--md-sys-color-primary);
    font-size: var(--md-typescale-title-medium-size);
  }

  /* Standard rate shown smaller when member rate is active */
  .marketplace-card__rate--standard .marketplace-card__rate-value {
    text-decoration: line-through;
    color: var(--md-sys-color-on-surface-variant);
    font-weight: 400;
  }

  /* ── Skills card specifics ───────────────────────────────── */
  .marketplace-card__teach-badge {
    /* chip--accent already handles colour; just ensure vertical alignment */
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
  }

  .marketplace-card__meta {
    margin-top: auto;
    padding-top: var(--space-2);
  }

  .marketplace-card__offering-count {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
  }

  /* ── Card footer (action area) ───────────────────────────── */
  .marketplace-card__footer {
    padding: var(--space-3) var(--space-4);
    border-top: 1px solid var(--md-sys-color-outline-variant);
    background: var(--md-sys-color-surface-variant);
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
  }

  .marketplace-card__action {
    align-self: flex-start;
  }

  /* ── Skill request form panel ────────────────────────────── */
  /* Hidden by default; shown by marketplace-request Stimulus controller */
  .marketplace-request-form {
    width: 100%;
  }

  .skill-request-form {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding-top: var(--space-2);
  }

  .skill-request-form .form-field--row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--space-3);
  }

  .skill-request-form .form-actions {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
  }

  /* ── My-skills marketplace toggle ───────────────────────────
     On /skills/mine, each row gets a small toggle button.
     Active state: teal/accent to show "currently listed". */
  .skills-mine__marketplace-toggle {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
    gap: var(--space-1);
    display: inline-flex;
    align-items: center;
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius-sm);
    border: 1px solid var(--md-sys-color-outline-variant);
    background: transparent;
    cursor: pointer;
    transition: background-color 0.15s, color 0.15s, border-color 0.15s;
  }

    .skills-mine__marketplace-toggle:hover {
      background: var(--md-sys-color-secondary-container);
      border-color: var(--md-sys-color-secondary);
      color: var(--md-sys-color-on-secondary-container);
    }

  .skills-mine__marketplace-toggle--active {
    background: var(--md-sys-color-tertiary-container);
    border-color: var(--md-sys-color-tertiary);
    color: var(--md-sys-color-on-tertiary-container);
    font-weight: 600;
  }

    .skills-mine__marketplace-toggle--active:hover {
      background: var(--md-sys-color-error-container);
      border-color: var(--md-sys-color-error);
      color: var(--md-sys-color-on-error-container);
    }

  /* ── Chip accent (for teach badge) ────────────────────────── */
  /* chip--accent is new — maps to tertiary M3 colour role */
  .chip--accent {
    background: var(--md-sys-color-tertiary-container);
    color: var(--md-sys-color-on-tertiary-container);
  }

  /* ── Responsive ─────────────────────────────────────────── */
  @media (max-width: 600px) {
    .marketplace-page {
      padding: var(--space-4) var(--space-4) var(--space-10);
    }

    .marketplace-grid {
      grid-template-columns: 1fr;
    }

    .marketplace-header {
      flex-direction: column;
      gap: var(--space-3);
    }

    .marketplace-header__cta {
      flex-direction: column;
      align-items: flex-start;
    }

    .skill-request-form .form-field--row {
      grid-template-columns: 1fr;
    }

    /* Public-facing package item names are read by prospective renters on phones,
       not staff scanning a dashboard — bump from body-small (12px) to body-medium
       (13px) on mobile so item names stay legible outdoors. Desktop density is
       unchanged (the base rule below keeps 12px). */
    .marketplace-card__items {
      font-size: var(--md-typescale-body-medium-size);
    }
  }

  /* ── Equipment packages shelf (Phase 2) ─────────────────────
     Reuses .marketplace-card / .marketplace-card__rate*. The
     items-inside list + the sum/override note are the only new bits. */
  .marketplace-card__items {
    list-style: none;
    margin: var(--space-2) 0 var(--space-3);
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
  }

  .marketplace-card__items li {
    line-height: 1.3;
  }

  .marketplace-card__rate-note {
    margin-top: var(--space-2);
    align-self: flex-start;
  }
}
/* ── Thread action toolbar — own row below the drawer header.
     Six actions next to the title squeezed it to zero width; gmail splits
     subject and actions the same way. ── */
.thread-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    /* space-2 (not space-1): at 4px the Reply/Reply-all/Forward cluster
       read as one dense mass — gmail uses ~8px between toolbar buttons. */
    gap: var(--space-2);
    padding: var(--space-2) var(--space-4);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    flex-shrink: 0;
    background: var(--md-sys-color-surface);
  }
/* Pushes triage flags to the right of the compose actions. */
.thread-actions__spacer {
    flex: 1;
  }
/* ============================================================
   pages/inbox.css — Mail portal (Inbox / Sent / Archived / Compose)
   ------------------------------------------------------------
   Dense, 13px body, M3 tokens throughout.
   No hex colours — tokens only, so light/dark just works.

   Class inventory (parallel-agent contract — names are exact):
     Row emphasis:
       .email-row--unread
       .email-row__from
       .email-row__subject
       .email-row__status
       .email-row__dot
       .email-row__direction

     Thread reader (drawer):
       .thread-msg
       .thread-msg__summary
       .thread-msg__meta
       .thread-msg__body
       .thread-msg--expanded
       .thread-attachments
       .thread-attachment

     Sanitized body container:
       .email-body
       .email-html-frame

     Compose form:
       .compose-form
       .compose-form__row
       .compose-form__textarea
       .compose-toolbar
       .compose-form__preview
       .merge-hint-bar
       .merge-hint-btn
   ============================================================ */
@layer pages {

  /* ── Unread row emphasis ───────────────────────────────────────────────
     Apply .email-row--unread to <tr> elements. Only the From and Subject
     cells get bold — status dot, date chip, direction glyph stay normal
     weight so they don't visually compete.
     ────────────────────────────────────────────────────────────────────── */
  .email-row--unread .email-row__from,
  .email-row--unread .email-row__subject {
    font-weight: 600;
  }

  /* ── Status column — leading narrow cell ───────────────────────────────
     28px wide, centered. Holds the unread dot or a blank when read.
     ────────────────────────────────────────────────────────────────────── */
  .email-row__status {
    width: 28px;
    text-align: center;
    padding: 0 var(--space-1);
    vertical-align: middle;
  }

  /* Unread indicator dot — 6px primary-coloured circle */
  .email-row__dot {
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: var(--radius-full);
    background: var(--md-sys-color-primary);
    vertical-align: middle;
    /* No margin: the status cell itself is centered. */
  }

  /* Direction glyph — ← for received, → for sent.
     Rendered as text via the ERB template; styled muted. */
  .email-row__direction {
    color: var(--md-sys-color-on-surface-variant);
    font-size: var(--md-typescale-label-small-size);
    font-weight: 400;
    line-height: 1;
  }

  /* ── Thread reader (detail drawer) ────────────────────────────────────
     Each collapsed/expanded email in a thread is a <details> element with
     class .thread-msg.
     ────────────────────────────────────────────────────────────────────── */

  /* Thread message wrapper — border between messages */
  .thread-msg {
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    padding: var(--space-3) 0;
  }

    .thread-msg:last-child {
      border-bottom: none;
    }

  /* Highlighted when expanded */
  .thread-msg--expanded {
    background: var(--md-sys-color-surface-container-low);
    border-radius: var(--radius-sm);
    padding: var(--space-3);
    margin: 0 calc(-1 * var(--space-3));  /* bleed into drawer padding */
  }

  /* The <summary> row — click to expand/collapse the message.
     list-style: none removes the default triangle in Firefox.
     ::-webkit-details-marker removes it in Chrome/Safari. */
  .thread-msg__summary {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    cursor: pointer;
    list-style: none;
    font-size: var(--md-typescale-body-small-size);   /* 12px = body-small */
    color: var(--md-sys-color-on-surface);
    -webkit-user-select: none;
       -moz-user-select: none;
            user-select: none;
  }

    .thread-msg__summary::-webkit-details-marker {
      display: none;
    }

    /* Remove default outline on summary (we handle focus-visible below) */
    .thread-msg__summary:focus {
      outline: none;
    }

    .thread-msg__summary:focus-visible {
      outline: 2px solid var(--md-sys-color-primary);
      outline-offset: 2px;
      border-radius: var(--radius-xs);
    }

  /* Meta section of the summary row — timestamp + size, pushed right */
  .thread-msg__meta {
    margin-left: auto;
    color: var(--md-sys-color-on-surface-variant);
    font-size: var(--md-typescale-label-small-size);   /* 11px */
    font-weight: var(--md-typescale-label-small-weight);
    white-space: nowrap;
    flex-shrink: 0;
  }

  /* Message body — revealed when <details> is open */
  .thread-msg__body {
    padding-top: var(--space-2);
    font-size: var(--md-typescale-body-medium-size);   /* 13px */
    color: var(--md-sys-color-on-surface);
    line-height: 1.55;
  }

  /* ── Attachments ────────────────────────────────────────────────────── */

  /* Attachment list — flex wrap row of chips */
  .thread-attachments {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-top: var(--space-2);
  }

  /* Individual attachment chip — outlined, chip-like, small */
  .thread-attachment {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-sm);
    font-size: var(--md-typescale-label-small-size);   /* 11px */
    font-weight: var(--md-typescale-label-small-weight);
    padding: 2px 8px;
    color: var(--md-sys-color-on-surface-variant);
    background: transparent;
    text-decoration: none;
    cursor: pointer;
    transition: background var(--md-sys-motion-duration-short2),
                border-color var(--md-sys-motion-duration-short2);
  }

    .thread-attachment:hover {
      background: color-mix(in srgb, var(--md-sys-color-on-surface) 6%, transparent);
      border-color: var(--md-sys-color-outline);
    }

  /* ── Sanitized body container ──────────────────────────────────────────
     Wraps sanitized HTML email body content. The base reset strips browser
     defaults (list markers, blockquote indentation, etc.) so we re-apply
     prose-appropriate styles scoped to this container.
     max-width: 70ch — optimal readable line length.
     ────────────────────────────────────────────────────────────────────── */
  .email-body {
    max-width: 70ch;
    font-size: var(--md-typescale-body-medium-size);
    line-height: 1.6;
    color: var(--md-sys-color-on-surface);
    overflow-wrap: break-word;
    word-wrap: break-word;   /* legacy alias; belt-and-suspenders */

    /* Blockquote — muted left-border style (common in email quoting) */
  }
    .email-body blockquote {
      border-left: 3px solid var(--md-sys-color-outline-variant);
      margin: var(--space-3) 0;
      padding: var(--space-1) var(--space-3);
      color: var(--md-sys-color-on-surface-variant);
    }

    /* Preformatted text / code blocks — light surface bg */
    .email-body pre {
      background: var(--md-sys-color-surface-container-low);
      border: 1px solid var(--md-sys-color-outline-variant);
      border-radius: var(--radius-sm);
      padding: var(--space-3);
      overflow-x: auto;
      font-family: var(--font-mono, "JetBrains Mono", "Courier New", monospace);
      font-size: var(--md-typescale-label-small-size);
      margin: var(--space-3) 0;
    }

    .email-body code {
      font-family: var(--font-mono, "JetBrains Mono", "Courier New", monospace);
      font-size: 0.9em;
      background: var(--md-sys-color-surface-container-low);
      border: 1px solid var(--md-sys-color-outline-variant);
      border-radius: var(--radius-xs);
      padding: 1px 4px;
    }

    /* Reset pre > code so the outer pre styles take precedence */
    .email-body pre code {
      background: none;
      border: none;
      padding: 0;
    }

    /* Restore list markers (base reset strips them) */
    .email-body ul {
      list-style: disc;
      padding-left: var(--space-6);
      margin: var(--space-2) 0;
    }

    .email-body ol {
      list-style: decimal;
      padding-left: var(--space-6);
      margin: var(--space-2) 0;
    }

    .email-body li {
      margin-bottom: var(--space-1);
    }

    /* Paragraph spacing */
    .email-body p {
      margin: 0 0 var(--space-3);
    }
      :is(.email-body p):last-child { margin-bottom: 0; }

    /* Links in email body */
    .email-body a {
      color: var(--md-sys-color-primary);
      text-decoration: underline;
    }

  /* ── HTML email iframe ─────────────────────────────────────────────────
     For HTML emails rendered in an <iframe> (sandboxed, CSP-safe).
     White background is intentional — HTML emails assume a white canvas
     and may have dark text hardcoded. Border token keeps it within M3 system.
     ────────────────────────────────────────────────────────────────────── */
  .email-html-frame {
    width: 100%;
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-sm);
    min-height: 360px;
    background: white;   /* intentional: HTML email assumption, not theme */
    display: block;
  }

  /* ── Compose form ─────────────────────────────────────────────────────
     Vertical stack: toolbar → header fields → body textarea → preview.
     ────────────────────────────────────────────────────────────────────── */

  /* Outer wrapper — flex column, gaps between major sections */
  .compose-form {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
  }

  /* Toolbar row — format controls or action buttons above the body */
  .compose-toolbar {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    padding-bottom: var(--space-2);
    flex-wrap: wrap;
  }

  /* Header field row — 2-column grid: 64px label | 1fr field input.
     align-items: center keeps the label baseline-aligned with the input. */
  .compose-form__row {
    display: grid;
    grid-template-columns: 64px 1fr;
    align-items: center;
    gap: var(--space-2);

    /* Label column — muted, right-aligned for visual neatness */
  }
    .compose-form__row > label,.compose-form__row > .compose-form__label {
      font-size: var(--md-typescale-label-small-size);
      font-weight: var(--md-typescale-label-small-weight);
      color: var(--md-sys-color-on-surface-variant);
      text-align: right;
      white-space: nowrap;
    }

  /* Body textarea — regular (not monospace) font, resizable, tall */
  .compose-form__textarea {
    font-family: var(--md-sys-typescale-font-family, inherit);
    font-size: var(--md-typescale-body-medium-size);
    line-height: 1.55;
    min-height: 220px;
    resize: vertical;
    /* Field styling is inherited from .field__input or the reset;
       explicit properties here ensure textarea-specific overrides. */
    width: 100%;
    padding: var(--density-form-field-py) var(--density-form-field-px);
    border: 1px solid var(--md-sys-color-outline);
    border-radius: var(--radius-sm);
    background: var(--md-sys-color-surface);
    color: var(--md-sys-color-on-surface);
    outline: none;
  }

    .compose-form__textarea:focus {
      border-color: var(--md-sys-color-primary);
      border-width: 2px;
      /* Compensate for the 1px border growth so layout doesn't shift */
      padding: calc(var(--density-form-field-py) - 1px) calc(var(--density-form-field-px) - 1px);
    }

  /* Preview pane — dashed border signals "not real content" */
  .compose-form__preview {
    border: 1px dashed var(--md-sys-color-outline-variant);
    border-radius: var(--radius-md);
    padding: var(--space-4);
    min-height: 220px;
    font-size: var(--md-typescale-body-medium-size);
    color: var(--md-sys-color-on-surface);
    line-height: 1.55;
    background: var(--md-sys-color-surface-container-low);
  }

  /* ── Merge hint bar ────────────────────────────────────────────────────
     Row of clickable variable tokens (e.g. {{first_name}}) that insert
     merge fields into the compose textarea. Muted, label-small, flex-wrap.
     ────────────────────────────────────────────────────────────────────── */
  .merge-hint-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-1);
    font-size: var(--md-typescale-label-small-size);
    color: var(--md-sys-color-on-surface-variant);
  }

  /* Merge field button — tonal, tiny, clickable token
     secondary-container bg (tonal pattern), not a border. */
  .merge-hint-btn {
    display: inline-flex;
    align-items: center;
    background: var(--md-sys-color-secondary-container);
    color: var(--md-sys-color-on-secondary-container);
    border: none;
    border-radius: var(--radius-sm);
    padding: 2px 8px;
    font-size: var(--md-typescale-label-small-size);
    font-weight: var(--md-typescale-label-small-weight);
    font-family: inherit;
    cursor: pointer;
    line-height: 1.4;
    white-space: nowrap;
    /* State layer via ::after for hover */
    position: relative;
    overflow: hidden;
    transition: background var(--md-sys-motion-duration-short2);
  }

    .merge-hint-btn::after {
      content: "";
      position: absolute;
      inset: 0;
      background: currentColor;
      opacity: 0;
      transition: opacity var(--md-sys-motion-duration-short2);
      pointer-events: none;
      border-radius: inherit;
    }

    .merge-hint-btn:hover::after {
      opacity: var(--md-sys-state-hover-opacity);   /* 0.08 */
    }

    .merge-hint-btn:focus-visible {
      outline: 2px solid var(--md-sys-color-primary);
      outline-offset: 2px;
    }

  /* ── Mobile — ≤767px ────────────────────────────────────────────────────
     compose-form__row collapses to single column (label above field).
     ────────────────────────────────────────────────────────────────────── */
  @media (max-width: 767px) {
    .compose-form__row {
      grid-template-columns: 1fr;

      /* Right-align on desktop → left-align on mobile */
    }
      .compose-form__row > label,.compose-form__row > .compose-form__label {
        text-align: left;
      }
  }

}
@layer pages {
  /* ── Post-critique polish (2026-06-12 review vs gmail/hubspot) ── */

  /* Collapsed thread snippet: truncate defensively in CSS — the Ruby-side
     truncate(80) helps but the from-name width is variable, and a resized-
     down drawer would overflow the summary line otherwise. */
  .thread-msg__snippet {
    flex: 1;
    min-width: 0; /* flex children refuse to shrink below content without this */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--md-sys-color-on-surface-variant);
  }

  /* Hairline between the toolbar's compose cluster and the triage cluster —
     two semantic groups, one visual signal (gmail uses the same divider). */
  .thread-actions__separator {
    width: 1px;
    height: 16px;
    background: var(--md-sys-color-outline-variant);
    flex-shrink: 0;
    align-self: center;
  }

  /* Seam between an expanded message's summary bar and its envelope/body. */
  .thread-msg__body {
    border-top: 1px solid var(--md-sys-color-outline-variant);
  }

  /* Direct-URL reading page: cap the measure and center (the drawer path
     never renders this wrapper — Turbo extracts only the frame). */
  .email-reading-page {
    max-width: 800px;
    margin: var(--space-6) auto;
    padding: 0 var(--space-4);
  }

  .email-reading-page .detail-drawer__inner {
    border-left: none;
  }

  /* Compose form: native control styling so the select and file input sit
     flush with the styled text fields instead of OS chrome. */
  .compose-form__select {
    height: var(--density-form-field-height, 36px);
    padding: 0 var(--space-3);
    border: 1px solid var(--md-sys-color-outline);
    border-radius: var(--radius-sm);
    background: var(--md-sys-color-surface);
    color: var(--md-sys-color-on-surface);
    font-size: var(--md-typescale-body-medium-size);
  }

  .compose-form__file {
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
  }

  /* ::file-selector-button styles the native button INSIDE the file input —
     keeps the browser's filename display (no JS) while matching .btn. */
  .compose-form__file::file-selector-button {
    height: 28px;
    padding: 0 var(--space-3);
    margin-right: var(--space-2);
    border: 1px solid var(--md-sys-color-outline);
    border-radius: var(--radius-full);
    background: transparent;
    color: var(--md-sys-color-primary);
    font-size: var(--md-typescale-label-medium-size);
    font-weight: 500;
    cursor: pointer;
  }

  .compose-form__file::file-selector-button:hover {
    background: color-mix(in srgb, var(--md-sys-color-primary) 8%, transparent);
  }
}
/* ============================================================
   pages/lists.css — Mailing list management styles
   ------------------------------------------------------------
   Dense, M3 tokens throughout. No hex colours.

   Class inventory (parallel-agent contract — names are exact):
     .list-members
     .list-member-row
     .list-member-row__meta
     .list-add-member
     .smart-list-note
     .list-sends-history
     .send-failed-row
   ============================================================ */
@layer pages {

  /* ── Member list ───────────────────────────────────────────────────────
     Flex column of rows with divider separators (no box gaps — the
     border-bottom on each row creates the visual rhythm instead).
     ────────────────────────────────────────────────────────────────────── */
  .list-members {
    display: flex;
    flex-direction: column;
    /* No gap — dividers are drawn via border-bottom on .list-member-row */
  }

  /* Individual member row */
  .list-member-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
    font-size: var(--md-typescale-body-small-size);   /* 12px */
    color: var(--md-sys-color-on-surface);
  }

    .list-member-row:last-child {
      border-bottom: none;
    }

  /* Secondary meta text (e.g. email address, subscription status) */
  .list-member-row__meta {
    color: var(--md-sys-color-on-surface-variant);
    font-size: var(--md-typescale-label-small-size);   /* 11px */
    font-weight: var(--md-typescale-label-small-weight);
    white-space: nowrap;
    flex-shrink: 0;
    margin-left: var(--space-3);
  }

  /* ── Add member form ───────────────────────────────────────────────────
     Flex row: input(s) + button, aligned at the bottom for a natural
     form baseline. margin-top adds breathing room after the list.
     ────────────────────────────────────────────────────────────────────── */
  .list-add-member {
    display: flex;
    align-items: flex-end;
    gap: var(--space-2);
    margin-top: var(--space-3);
    flex-wrap: wrap;
  }

  /* ── Smart list note ───────────────────────────────────────────────────
     Informational notice for auto-managed (smart) lists — explains that
     membership is computed, not manually managed. Muted, surface bg.
     ────────────────────────────────────────────────────────────────────── */
  .smart-list-note {
    font-size: var(--md-typescale-body-small-size);   /* 12px */
    color: var(--md-sys-color-on-surface-variant);
    background: var(--md-sys-color-surface-container-low);
    border-radius: var(--radius-sm);
    padding: var(--space-3);
    line-height: 1.5;
  }

  /* ── Sends history ─────────────────────────────────────────────────────
     Flex column of send records, matching the drawer-list visual pattern
     (dividers, body-small, no list markers).
     ────────────────────────────────────────────────────────────────────── */
  .list-sends-history {
    display: flex;
    flex-direction: column;
    /* Dividers come from individual row border-bottom, same as .list-members */
  }

  /* ── Failed send row ───────────────────────────────────────────────────
     Error-tinted row for sends that bounced or failed. Kept subtle:
     error text colour + a very light error-container background so the
     row is distinguishable without being alarming in a long send history.
     ────────────────────────────────────────────────────────────────────── */
  .send-failed-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--space-2);
    font-size: var(--md-typescale-label-small-size);   /* 11px */
    border-radius: var(--radius-sm);

    /* Subtle error tint — error text, very light error-container background.
       color-mix keeps us in token-land; percentage controls strength. */
    color: var(--md-sys-color-on-error-container);
    background: color-mix(in srgb, var(--md-sys-color-error-container) 40%, transparent);
    border-bottom: 1px solid color-mix(in srgb, var(--md-sys-color-error) 15%, transparent);
  }

    .send-failed-row:last-child {
      border-bottom: none;
    }

}
/* ============================================================
   pages/commons.css — The Commons (member-authored board)
   ------------------------------------------------------------
   The /commons feed: filter tabs, topic chips, and a flat
   reverse-chron list of post cards. Reuses the global
   .timeline-feed-area / .pagination-bar / .resource-page /
   .empty-state / .chip--filter primitives — this file only
   styles the commons-specific pieces (tabs row, topic bar,
   post card). Dense, desktop-first, tokens only, light + dark.
   ============================================================ */
@layer pages {
  /* ── Filter tabs row (kind: All / Gear / Skills / Announcements) ── */
  .commons-tabs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
  }

  /* ── Topic filter chips row ── */
  .commons-topics-bar {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-4);
    border-bottom: 1px solid var(--md-sys-color-outline-variant);
  }

  .commons-topics-bar .chip.is-active {
    background: var(--md-sys-color-primary-container);
    color: var(--md-sys-color-on-primary-container);
    font-weight: 600;
  }

  /* ── Feed list ── */
  .commons-feed {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding: var(--space-4);
  }

  /* ── Post card ── */
  .post-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-4);
    background: var(--md-sys-color-surface-container-low);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-lg);
  }

  .post-card:focus-visible {
    outline: 2px solid var(--md-sys-color-primary);
    outline-offset: 2px;
  }

  .post-card__header {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
  }

  .post-card__author {
    font-weight: 600;
    color: var(--md-sys-color-on-surface);
  }

  /* The byline is a link to the author's profile. The global reset strips link
     affordance (a { text-decoration: none }), so add it back on interaction —
     same pattern as .notification__main:hover — otherwise the name is
     indistinguishable from body text. */
  .post-card__author:hover,
  .post-card__author:focus-visible {
    text-decoration: underline;
  }

  .post-card__time {
    margin-left: auto;
    font-size: 0.8125rem;
    color: var(--md-sys-color-on-surface-variant);
  }

  .post-card__body {
    margin: 0;
    color: var(--md-sys-color-on-surface);
    line-height: 1.5;
  }

  /* ── Photo grid ── */
  .post-card__photos {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: var(--space-2);
  }

  .post-card__photo {
    width: 100%;
    aspect-ratio: 1 / 1;
    -o-object-fit: cover;
       object-fit: cover;
    border-radius: var(--radius-md);
    background: var(--md-sys-color-surface-container);
  }

  /* ── Topic chips on a card ── */
  .post-card__topics {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1);
  }

  /* ── "Open" affordance ── */
  .post-card__open {
    align-self: flex-start;
    /* Touch target (commons review): a bare 13px inline link is untappable on
       a phone. Make it an inline-flex block with padding + a comfortable
       min-height so it is a real tap target without turning the whole card
       into a link (keeps the topic chips independently clickable). */
    display: inline-flex;
    align-items: center;
    min-height: 36px;
    padding: var(--space-2) 0;
    font-size: 0.8125rem;
    font-weight: 600;
    color: var(--md-sys-color-primary);
    text-decoration: none;
  }

  .post-card__open:hover {
    text-decoration: underline;
  }

  /* ── Drawer show meta row ── */
  .post-show__meta {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-2);
  }

  /* ── Form: post-kind segmented selector ── */
  .commons-kind-tabs {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
  }

  .commons-kind-tab {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--md-sys-color-outline);
    border-radius: var(--radius-full);
    cursor: pointer;
    -webkit-user-select: none;
       -moz-user-select: none;
            user-select: none;
  }

  .commons-kind-tab__input {
    margin: 0;
  }

  /* When a kind radio is checked, tint its pill so the active kind is obvious. */
  .commons-kind-tab:has(.commons-kind-tab__input:checked) {
    background: var(--md-sys-color-primary-container);
    color: var(--md-sys-color-on-primary-container);
    border-color: transparent;
    font-weight: 600;
  }

  /* ── Mobile: cards go full-bleed and the tabs scroll horizontally ── */
  @media (max-width: 640px) {
    .commons-tabs {
      flex-wrap: nowrap;
      overflow-x: auto;
      /* Suppress the horizontal scrollbar track — it is jarring on a chip row
         and wastes vertical space (commons review). */
      scrollbar-width: none;        /* Firefox */
      -ms-overflow-style: none;     /* old Edge/IE */
    }

    .commons-tabs::-webkit-scrollbar {
      display: none;                /* Safari + Chrome */
    }

    .commons-feed {
      padding: var(--space-3);
    }

    .post-card__photos {
      grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
    }
  }

  /* ── Mobile touch targets + readability (commons review) ──
     WCAG 2.5.5 / Apple HIG 44px minimum, and a one-step body-text bump for
     this social/content surface (the dense 13px ops token is sized for data
     tables, not field-readable prose). 767px matches the app-wide mobile
     touch-target breakpoint in components/button.css. */
  @media (max-width: 767px) {
    /* The post-kind selector is the form's primary control — must be tappable. */
    .commons-kind-tab {
      min-height: 44px;
      padding: var(--space-3) var(--space-4);
    }

    /* The "Open" link is the sole entry point per card — grow it to 44px. */
    .post-card__open {
      min-height: 44px;
    }

    /* Card body prose: bump from 13px so it is readable outdoors. */
    .post-card__body {
      font-size: 15px;
      line-height: 1.55;
    }
  }

  /* ============================================================
     Phase B: comments, flag affordance, moderation queue.
     (The reaction buttons live in components/reaction.css — they
     are a reusable component per CLAUDE.md "buttons in components/".)
     Tokens only, light + dark, >= 44px tap targets on mobile.
     ============================================================ */

  /* ── Comments in the post drawer ── */
  .comment-list {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
  }

  .comment {
    padding: var(--space-3);
    background: var(--md-sys-color-surface-container);
    border-radius: var(--radius-md);
  }

  .comment__head {
    display: flex;
    gap: var(--space-2);
    /* center (not baseline) so the avatar circle aligns to the byline row;
       a baseline alignment would float the round avatar above the text. */
    align-items: center;
  }

  .comment__author { font-weight: 600; }

  .comment__body {
    margin-top: var(--space-1);
    line-height: 1.5;
  }

  /* Inline edit mode — a clear visual signal the row is being edited (tonal
     surface + primary border), so the form is not mistaken for a read-only row. */
  .comment--editing {
    background: var(--md-sys-color-surface-container-highest);
    border: 1px solid var(--md-sys-color-primary);
  }

  .comment__actions {
    display: flex;
    gap: var(--space-2);
    margin-top: var(--space-2);
    flex-wrap: wrap;
    align-items: center;
  }

  .comment-form {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin-top: var(--space-3);
  }

  /* ── Flag affordance (the inline Report disclosure) ── */
  .flag-affordance > summary {
    cursor: pointer;
    list-style: none;          /* hide the default <details> triangle */
    display: inline-flex;
  }
  .flag-affordance > summary::-webkit-details-marker { display: none; }

  .flag-form {
    display: flex;
    gap: var(--space-2);
    margin-top: var(--space-2);
    flex-wrap: wrap;
    align-items: center;
  }

  /* ── Moderation queue ── */
  .flag-queue {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    padding: var(--space-4);
  }

  .flag-queue__row {
    padding: var(--space-4);
    background: var(--md-sys-color-surface-container-low);
    border: 1px solid var(--md-sys-color-outline-variant);
    border-radius: var(--radius-lg);
  }

  .flag-queue__meta {
    display: flex;
    gap: var(--space-2);
    align-items: center;
    flex-wrap: wrap;
  }

  .flag-queue__target { font-weight: 600; }

  .flag-queue__reason {
    margin-top: var(--space-2);
    font-style: italic;
    color: var(--md-sys-color-on-surface-variant);
  }

  .flag-queue__excerpt { margin-top: var(--space-2); }

  .flag-queue__actions { margin-top: var(--space-3); }

  /* Mobile: comfortable prose + tap targets. */
  @media (max-width: 767px) {
    .comment__body { font-size: 15px; }
  }
}
/* ── The Commons Phase C: notifications inbox + top-bar bell ─────────────────
   Dense, desktop-first, light+dark via M3 tokens. The .count-badge (top-bar
   bell) is the shared component (components/chip.css) — NOT restyled here. */
/* Top-bar bell wrapper: the badge floats top-right of the rounded icon button.
   position:relative anchors the absolutely-positioned .count-badge. */
.top-bar__notifications {
  position: relative;
}
.top-bar__notifications .count-badge {
  position: absolute;
  top: 2px;
  inset-inline-end: 2px;
  /* The shared count-badge already styles colour/size; we only place it. */
}
/* ── Inbox list ── */
.notification-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.notification {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--md-sys-color-surface-container-low);
  /* >= 44px tap target height for the whole row (mobile). */
  min-height: 44px;
}
.notification--unread {
  background: var(--md-sys-color-surface-container);
  border-inline-start: 3px solid var(--md-sys-color-primary);
}
.notification__main {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex: 1 1 auto;
  min-width: 0;
  text-decoration: none;
  color: var(--md-sys-color-on-surface);
}
.notification__main:hover .notification__text {
  text-decoration: underline;
}
.notification__dot {
  flex: 0 0 auto;
  width: 8px;
  height: 8px;
  border-radius: var(--radius-full);
  background: var(--md-sys-color-primary);
}
.notification__text {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.notification__time {
  flex: 0 0 auto;
  font-size: 0.8125rem;
  color: var(--md-sys-color-on-surface-variant);
}
.notification__mark {
  flex: 0 0 auto;
  /* Ensure the per-row action is a >= 44px touch target on mobile. */
  min-height: 44px;
}
/* ── Mobile: the notification SENTENCE is the content — let it wrap to its own
   line so it is readable, instead of truncating to ~12 chars while a fixed-width
   time column and mark button eat the row. Time + mark drop below the text. ── */
@media (max-width: 767px) {
  .notification {
    flex-wrap: wrap;
    align-items: flex-start;
  }

  .notification__main {
    flex-wrap: wrap;
    align-items: flex-start;
  }

  .notification__text {
    /* Allow wrap; field-readable size (the dense 13px ops token is for tables,
       not outdoor reading) — matches the commons feed prose bump. */
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    flex-basis: 100%;
    font-size: 15px;
    line-height: 1.5;
  }

  .notification__time {
    order: 3; /* drops below the text */
    font-size: 0.75rem;
  }

  .notification__mark {
    order: 4;
    margin-inline-start: auto;
  }
}
/* ── The Commons Phase C: opt-in profiles (member + org) ─────────────────────
   Profile header (avatar + byline + bio + follow), and the post/skill/gear
   aggregate sections. Desktop-first, light+dark via M3 tokens. */
.profile-page {
  max-width: 880px;
  margin-inline: auto;
  padding: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}
/* ── Header ── */
.profile-header {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  padding-block-end: var(--space-4);
  border-block-end: 1px solid var(--md-sys-color-outline-variant);
}
.profile-header__main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.profile-header__name {
  margin: 0;
  /* Token scale, not a hardcoded 24px — the name hero uses the display step
     (the same prominent-number token the dashboard uses). */
  font-size: var(--md-typescale-display-size);
  color: var(--md-sys-color-on-surface);
}
.profile-header__bio {
  color: var(--md-sys-color-on-surface-variant);
}
.profile-header__actions {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
/* ── Avatar ──
   The profile HERO sizing only. Layout/shape/fallback colour now come from the
   shared `.avatar` component (avatar.css) — the hero passes css_class:
   "profile-avatar" to avatar_tag, so this rule just sets the 96px box and the
   larger fallback initial. No `--placeholder`/`--sm` modifiers: the fallback
   colour and the 64px edit preview are handled by the shared helper. */
.profile-avatar {
  width: 96px;
  height: 96px;
  /* The initial-circle fallback (.avatar--initial) sets ~0.75em; bump the hero
     letter so a 96px circle is not dwarfed by a tiny initial. */
  font-size: 2rem;
}
/* ── Aggregate sections ── */
.profile-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.profile-section__title {
  margin: 0;
  /* Token scale, not a hardcoded ~17px — headline-small is the nearest defined
     step for a section heading. */
  font-size: var(--md-typescale-headline-small-size);
  color: var(--md-sys-color-on-surface);
}
.profile-posts,
.profile-skills,
.profile-gear {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.profile-post {
  padding: var(--space-3);
  border-radius: var(--radius-md);
  background: var(--md-sys-color-surface-container-low);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.profile-post__meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.profile-skill,
.profile-gear__item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  background: var(--md-sys-color-surface-container-low);
}
.profile-skill__name,
.profile-gear__name {
  font-weight: 600;
  color: var(--md-sys-color-on-surface);
}
.profile-gear__rate {
  margin-inline-start: auto;
  color: var(--md-sys-color-on-surface-variant);
}
/* ── Follow affordances (button + the drawer follow row) ── */
.follow-frame {
  display: inline-block;
}
.follow-btn.is-following {
  /* Subtle "already following" state — still tappable to unfollow. */
  opacity: 0.85;
}
.post-follows__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
.post-follows__label {
  font-size: 0.875rem;
  color: var(--md-sys-color-on-surface-variant);
}
/* ── Edit form ──
   The form lives inside .resource-page, which is height:100% + overflow:hidden
   (built for table pages whose table-area scrolls and whose pagination is
   pinned). A long form would be CLIPPED by that overflow, so the form itself
   becomes the scroll container: flex:1 + min-height:0 lets it take the remaining
   height under the pinned page-header, and overflow-y:auto scrolls its fields. */
.profile-form {
  max-width: 560px;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.profile-form__toggle {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}
/* Keep "Save profile" reachable on small phones: the form is the scroll
   container (overflow-y:auto above), so position:sticky pins the actions row to
   the bottom of the scroll viewport. A member filling the Notifications section
   no longer has to scroll past every section to find Save. The surface
   background + top hairline keep it legible over scrolling content, and the
   safe-area inset clears the iPhone home indicator. */
.profile-form .form-actions {
  position: sticky;
  bottom: 0;
  z-index: 1;
  margin-top: 0;
  padding-block: var(--space-3);
  padding-bottom: max(var(--space-3), env(safe-area-inset-bottom));
  background: var(--md-sys-color-surface);
  border-top: 1px solid var(--md-sys-color-outline-variant);
}
/* ── Sectioned settings (Identity / Visibility / Notifications) ──
   Each section is a labelled group separated by a hairline, so the long form
   reads as three intents rather than one wall of fields. */
.settings-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding-block-end: var(--space-4);
  border-block-end: 1px solid var(--md-sys-color-outline-variant);
}
.settings-section:last-of-type {
  border-block-end: none;
}
/* Field groups INSIDE a section rely on the section's `gap` for rhythm, not the
   default .drawer-field-group hairline+margin (detail-drawer.css) — otherwise
   every non-last group in a section draws a spurious bottom border, doubling up
   with the section's own gap and trailing border-block-end. */
.settings-section .drawer-field-group {
  border-bottom: none;
  margin-bottom: 0;
}
.settings-section__title {
  margin: 0;
  font-size: var(--md-typescale-headline-small-size);
  color: var(--md-sys-color-on-surface);
}
/* ── Notification preferences grid ──
   Three columns: kind label | in-app checkbox | email checkbox. Each checkbox
   sits in a label cell sized to a >= 44px tap target (same a11y bar as the
   avatar-chip dropdown). */
.notif-grid {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.notif-grid__head,
.notif-row {
  display: grid;
  grid-template-columns: 1fr 72px 72px;
  align-items: center;
  gap: var(--space-2);
}
.notif-grid__head {
  font-size: var(--md-typescale-label-small-size);
  color: var(--md-sys-color-on-surface-variant);
  text-align: center;
}
.notif-grid__head span:first-child {
  text-align: left;
}
/* Subheading row that separates the social block from the gear-rental block.
   A quiet label-small uppercase divider — same voice as the column header, not a
   loud .settings-section title (it lives INSIDE one). The top border draws the
   visual break; spacing above gives the two groups room to read as distinct. */
.notif-grid__subhead {
  margin-top: var(--space-2);
  padding-top: var(--space-2);
  border-top: 1px solid var(--md-sys-color-outline-variant);
  font-size: var(--md-typescale-label-small-size);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--md-sys-color-on-surface-variant);
}
.notif-row {
  padding-block: var(--space-1);
}
.notif-row__label {
  color: var(--md-sys-color-on-surface);
}
.notif-row__cell {
  /* Tap target: the whole cell is the clickable label, >= 44px tall + centred. */
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  cursor: pointer;
}
.form-label--inline {
  margin: 0;
}
/* Helper text under a stacked profile field. Same treatment as the .field__hint
   in time_entries.css / assets.css — defined here so the profile form reads as
   self-contained. */
.field__hint {
  display: block;
  font-size: var(--md-typescale-label-small-size);
  color: var(--md-sys-color-on-surface-variant);
  margin-top: var(--space-1);
}
/* File input keeps a usable native control (the floating-label .field__input
   styling does not apply to file pickers). Minimal token-aware sizing so it does
   not render as a bare browser default. */
.field__input--file {
  height: auto;
  padding: var(--space-2);
  padding-top: var(--space-2);
  font-size: var(--md-typescale-body-medium-size);
  color: var(--md-sys-color-on-surface);
}
/* ── Mobile: stack the header, bump prose, keep tap targets >= 44px ──
   Use the app-wide 767px breakpoint (matches shell.css + button.css) so the
   601–767px range is not left in the cramped desktop side-by-side layout. */
@media (max-width: 767px) {
  .profile-header {
    flex-direction: column;
    align-items: center;
    text-align: center;
  }

  .profile-header__actions {
    width: 100%;
    justify-content: center;
  }

  /* Explicit touch-target width floor — "Following" must clear 44px even when
     the wrapping post-follows row gives the button only its content width. */
  .follow-btn {
    min-width: 88px;
  }

  /* Field-readable prose: bump the profile post body from the dense 13px ops
     token (sized for tables) to match the commons feed card. */
  .profile-post__body {
    font-size: 15px;
    line-height: 1.55;
  }

  /* The notif-grid column headers are 11px (label-small) — fine on desktop, but
     unreadable on a phone in mixed lighting. Bump the (aria-hidden, purely
     visual) headers and the kind labels for readability. These are <label>s, not
     <input>s, so 14px does not trip iOS auto-zoom. */
  .notif-grid__head {
    font-size: var(--md-typescale-label-medium-size);
  }

  .notif-row__label {
    font-size: 14px;
  }
}
/* ============================================================
   pages/splash.css — Public splash landing (GET / for guests)
   ------------------------------------------------------------
   The content-rich public front door rendered by
   PagesController#splash in the public layout. SPLASH-ONLY layout
   lives here; everything visual reuses existing components
   (.card, .stat-card, .btn, .chip, .public-section, .public-card-grid).

   Tokens only — light + dark come for free via the M3 colour tokens.
   Touch targets are >= 44px via the reused .btn sizing; the jumplinks
   below set their own min-height so they clear 44px on mobile too.

   These rules sit in the `pages` @layer (declared in application.postcss.css).
   ============================================================ */
@layer pages {

  /* ── Hero: tighter than the marketing .public-page__hero so the splash
        stays content-first (the showcase below is the point, not the hero). ── */
  .splash-hero {
    padding: var(--space-8) var(--space-6) var(--space-6);
  }

  /* ── Jumplinks: an inline anchor row under the hero CTAs. Real <a> anchors
        (keyboard + focus friendly); min-height keeps the tap target >= 44px. ── */
  .splash-jumplinks {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-2) var(--space-4);
    margin-top: var(--space-5);
  }

    .splash-jumplinks a {
      display: inline-flex;
      align-items: center;
      min-height: 44px;
      padding: 0 var(--space-2);
      font-size: var(--md-typescale-body-medium-size);
      color: var(--md-sys-color-primary);
      text-decoration: none;
    }

      :is(.splash-jumplinks a):hover { text-decoration: underline; }

  /* ── Stats band: responsive grid wrapping the reused .stat-card. ── */
  .splash-stats {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: var(--space-4);
  }

  /* ── Commons post card: photo on top, body below. ── */
  .splash-post-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
    overflow: hidden;
  }

  .splash-post-card__photo {
    display: block;
    width: 100%;
    aspect-ratio: 12 / 7;
    -o-object-fit: cover;
       object-fit: cover;
    border-radius: var(--radius-sm);
  }

  .splash-post-card__byline {
    font-size: var(--md-typescale-label-medium-size);
    font-weight: 500;
    color: var(--md-sys-color-on-surface-variant);
    margin: var(--space-2) 0 0;
  }

  /* ── Gear card: body fills, hook pinned to the bottom edge. ── */
  .splash-gear-card {
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
  }

  /* Lead gear photo — mirrors .splash-post-card__photo exactly. */
  .splash-gear-card__photo {
    display: block;
    width: 100%;
    aspect-ratio: 12 / 7;
    -o-object-fit: cover;
       object-fit: cover;
    border-radius: var(--radius-sm);
  }

  .splash-gear-card__hook {
    margin-top: auto;
    padding-top: var(--space-3);
    border-top: 1px solid var(--md-sys-color-outline-variant);
  }

  /* A single rate row: label left, value right. */
  .splash-rate {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--space-3);
    margin-top: var(--space-2);
  }

  .splash-rate__label {
    font-size: var(--md-typescale-label-medium-size);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--md-sys-color-on-surface-variant);
  }

  .splash-rate__value {
    font-size: var(--md-typescale-title-medium-size);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
    color: var(--md-sys-color-on-surface);
  }

  /* The member rate is the headline — accent it so the "join to save" hook
     reads at a glance. */
  .splash-rate--member .splash-rate__value {
    color: var(--md-sys-color-primary);
  }

  /* ── Skills chip cloud: aggregated counts only, never members. ── */
  .splash-skill-chips {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
  }

  /* Each chip carries the skill name, a count, and an optional teach chip.
     gap + inline-flex keep the inner count/teach pills aligned. */
  .splash-skill-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
  }

  .splash-skill-chip__count {
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    color: var(--md-sys-color-on-surface);
  }

  /* ── Learn: a course card is a whole-card link; no underline, hover lift. ── */
  .splash-course-card {
    display: block;
    text-decoration: none;
    color: inherit;
    transition: box-shadow var(--md-sys-motion-duration-short2),
                border-color var(--md-sys-motion-duration-short2);
  }

    .splash-course-card:hover {
      box-shadow: var(--md-sys-elevation-2);
      border-color: var(--md-sys-color-outline);
    }

  /* ── Teaser CTA row under each section — centered, wraps on mobile. ── */
  .splash-teaser {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-3);
    margin-top: var(--space-5);
  }

  /* ── Empty-state copy when a section has no public content (fresh clone). ── */
  .splash-empty {
    text-align: center;
    color: var(--md-sys-color-on-surface-variant);
  }

  /* ── Public flash region (top of <main>) — the magic-link "check your email"
        notice and lockout alerts render here on the public-layout sign-in. ── */
  .public-flash {
    max-width: 400px;
    margin: var(--space-6) auto 0;
    padding: 0 var(--space-4);
    display: flex;
    flex-direction: column;
    gap: var(--space-3);
  }

  /* ── Polished sign-in rendered in the public layout ──
        The card contract (.auth-card etc.) is already styled in
        components/auth.css; these are cosmetic-only additions. .splash-auth
        centres the card in the public <main> (which is not the app-main grid
        cell .auth-page expects), and the back/explore links sit above/below. */
  .splash-auth {
    min-height: 60dvh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-8) var(--space-4);
  }

  .auth-card__back {
    margin: 0 0 calc(-1 * var(--space-2));
    text-align: left;
  }

  .auth-card__footer-links {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--space-4);
    font-size: var(--md-typescale-body-small-size);
  }

    .auth-card__footer-links a {
      color: var(--md-sys-color-primary);
      text-decoration: underline;
      text-underline-offset: 2px;
    }

  /* ── Gear packages on splash (Phase 2) — reuses .splash-gear-card ─────────
     Only the items-inside list is new; rates reuse .splash-rate. */

  /* Intro copy shown when packages DO exist (not an empty state — that's
     .splash-empty). Carries the centring + bottom margin that used to be an
     inline style override; .public-card-text already supplies the muted colour. */
  .splash-package-desc {
    text-align: center;
    margin-bottom: var(--space-4);
  }

  .splash-package-items {
    list-style: none;
    margin: var(--space-2) 0 var(--space-3);
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    font-size: var(--md-typescale-body-small-size);
    color: var(--md-sys-color-on-surface-variant);
  }

  .splash-package-items li {
    line-height: 1.3;
  }

  /* Public-facing item names on the dark splash surface — bump from body-small
     (12px) to body-medium (13px) on phones so prospective renters can read item
     names in outdoor light. Desktop density (12px) is unchanged above. */
  @media (max-width: 600px) {
    .splash-package-items {
      font-size: var(--md-typescale-body-medium-size);
    }
  }
}
/* ── Reservations (Phase 2) ────────────────────────────────────────────────
   The staff booking queue, the manual-reservation drawer, and the server-
   rendered calendar/agenda. Tokens only (M3), desktop-first, light+dark via
   tokens, mobile-safe. Status chips reuse the shared components/ chips — this
   file styles only LAYOUT (the date inputs, the calendar grid, the agenda).
   ────────────────────────────────────────────────────────────────────────── */
/* ── Inclusive date-window inputs (scan rent form + staff new form) ──────────
   Two date fields side by side, collapsing to a single column on narrow
   viewports so the rent-request form stays usable on a phone. */
.rental-dates {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
}
.rental-dates__hint {
  display: block;
  margin-top: calc(-1 * var(--space-2));
  margin-bottom: var(--space-3);
}
@media (max-width: 640px) {
  .rental-dates {
    grid-template-columns: 1fr;
  }
}
/* ── Drawer actions (confirm / cancel) ── */
.reservation-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-4);
}
/* ── Calendar controls (month nav + view toggle) ── */
.calendar-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.calendar-controls__month-nav {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.calendar-controls__current {
  font-size: var(--md-typescale-title-medium-size);
  font-weight: var(--md-typescale-title-medium-weight);
  color: var(--md-sys-color-on-surface);
  min-width: 9rem;
  text-align: center;
}
.calendar-controls__view-toggle {
  display: flex;
  gap: var(--space-1);
}
/* ── Month grid ── */
.calendar-grid {
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--md-sys-color-surface);
}
.calendar-grid__weekdays {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  background: var(--md-sys-color-surface-container);
}
.calendar-grid__weekday {
  padding: var(--space-2);
  text-align: center;
  font-size: var(--md-typescale-label-small-size);
  font-weight: var(--md-typescale-label-medium-weight);
  color: var(--md-sys-color-on-surface-variant);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.calendar-grid__week {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
}
.calendar-grid__cell {
  min-height: 6.5rem;
  padding: var(--space-1);
  border-top: 1px solid var(--md-sys-color-outline-variant);
  border-right: 1px solid var(--md-sys-color-outline-variant);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.calendar-grid__week > .calendar-grid__cell:last-child {
  border-right: none;
}
.calendar-grid__cell--outside {
  background: var(--md-sys-color-surface-container-low);
}
.calendar-grid__cell--outside .calendar-grid__date {
  color: var(--md-sys-color-on-surface-variant);
  opacity: 0.55;
}
.calendar-grid__cell--today {
  background: var(--md-sys-color-secondary-container);
}
.calendar-grid__date {
  font-size: var(--md-typescale-label-medium-size);
  font-weight: var(--md-typescale-label-medium-weight);
  color: var(--md-sys-color-on-surface);
  padding: 0 var(--space-1);
}
.calendar-event {
  text-decoration: none;
  display: block;
}
.calendar-event__chip {
  display: block;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* ── Agenda (upcoming, grouped by start date) ── */
.reservations-agenda__day {
  margin-bottom: var(--space-6);
}
.reservations-agenda__date {
  font-size: var(--md-typescale-title-medium-size);
  font-weight: var(--md-typescale-title-medium-weight);
  color: var(--md-sys-color-on-surface);
  margin-bottom: var(--space-2);
  padding-bottom: var(--space-1);
  border-bottom: 1px solid var(--md-sys-color-outline-variant);
}
.reservations-agenda__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.reservations-agenda__link {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--md-sys-color-outline-variant);
  border-radius: var(--radius-md);
  background: var(--md-sys-color-surface);
  text-decoration: none;
  color: var(--md-sys-color-on-surface);
}
.reservations-agenda__link:hover {
  background: var(--md-sys-color-surface-container);
}
.reservations-agenda__gear {
  font-weight: var(--md-typescale-label-medium-weight);
}
@media (max-width: 640px) {
  .calendar-grid__cell {
    min-height: 4.5rem;
  }

  /* The 7-column month grid is illegible on a phone (≈49px cells truncate the
     gear name to a few characters). The agenda view is the readable, tap-friendly
     default, so we hide the "Month grid" toggle on narrow screens rather than let
     a staff member tap into an unusable view. Staff on a wider screen keep it.
     The toggle's href always contains view=grid, so this attribute selector is
     stable against the server-rendered link. */
  .calendar-controls__view-toggle .btn[href*="view=grid"] {
    display: none;
  }
}
/* ── Phase 5: deposit + agreement panels (reservation drawer) ───────────────
   Layout only; chips/buttons reuse shared components + tokens. */
.deposit-panel,
.agreement-panel {
  margin-block-start: var(--space-4, 1rem);
  padding-block-start: var(--space-3, 0.75rem);
  border-block-start: 1px solid var(--md-sys-color-outline-variant, #ccc);
}
.deposit-panel__actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2, 0.5rem);
  margin-block-start: var(--space-2, 0.5rem);
}
.deposit-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-2, 0.5rem);
}
/* The amount field is a .field (width:100% from the component); let it grow to
   fill the row on desktop so the submit button sits beside it. */
.deposit-form__field {
  flex: 1 1 12rem;
}
/* On mobile the drawer is 100vw and the three flex children (field + submit)
   would fight for one cramped row. Stack them and make the button full-width so
   the tap target spans the drawer (≥ 44px) — pairs with the .field contract that
   already gives the input its 44px/16px mobile sizing. */
@media (max-width: 767px) {
  .deposit-form {
    flex-direction: column;
    align-items: stretch;
  }

  .deposit-form .btn {
    width: 100%;
  }
}
/* The Stripe Elements mount point — Stripe renders its own iframe inside. */
.stripe-element {
  margin-block: var(--space-2, 0.5rem);
  min-height: 2.5rem;
}
/* The portal "add a card" form (portal/payment_methods): give it a real column
   layout with a token gap so the Stripe iframe and the Save button are clearly
   separated, and make the CTA full-width on mobile for an easy tap target. The
   .btn is inline-flex by default, so without this it renders at intrinsic width
   flush under the iframe on a 375px phone. */
.stripe-card-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-3, 0.75rem);
}
.stripe-card-form .btn {
  align-self: flex-start;
}
@media (max-width: 767px) {
  .stripe-card-form .btn {
    align-self: stretch;
    width: 100%;
  }
}
/* Public-surface brand refresh — imported LAST in the pages layer so its
   .public-layout overrides win over pages/public.css + pages/splash.css. */
/* ============================================================
   pages/public-brand.css — Public-surface brand refresh
   ------------------------------------------------------------
   The outward-facing co-op identity: a dark, violet→magenta→
   periwinkle brand derived from the bnkops logo. Implements the
   "bnkops Site" design handoff (docs/design/frontend-handoff.md).

   SCOPE: every rule is nested under `.public-layout`. The
   authenticated workspace (nav rail, master-detail drawers) keeps
   its Material-3 ops styling untouched — this file never leaks
   out of the public chrome. It is imported LAST in the `pages`
   layer so its `.public-layout` overrides win over the older
   pages/public.css base rules.

   THEME: the public surface is dark-mode-forward by design. We do
   NOT read the M3 light/dark tokens here — the brand palette is
   declared once on `.public-layout` and applied directly, so a
   guest sees the same calm violet whatever their OS prefers.

   FONTS: Inter (self-hosted, see base/typography.css) + the app's
   system-mono fallback stack for JetBrains Mono. No web-font CDN —
   the CSP forbids foreign origins and the co-op self-hosts type.
   ============================================================ */
@layer pages {

  .public-layout {
    /* ── Brand palette (logo-derived) ── */
    --bnk-bg:            #0B0713;   /* page background           */
    --bnk-bg-footer:     #0D0819;   /* footer background         */
    --bnk-surface:       #161023;   /* cards                     */
    --bnk-surface-alt:   #181027;   /* nested / alt surfaces     */
    --bnk-input:         #0F0A1C;   /* input background          */
    --bnk-panel:         #110A1E;   /* sign-in panel base        */
    --bnk-hold:          linear-gradient(135deg, #1c1233, #130c22); /* image placeholder */
    --bnk-hold-art:      linear-gradient(135deg, #241640, #181027); /* course/thumb hold */

    --bnk-violet:        #7C6BFF;   /* primary                   */
    --bnk-violet-deep:   #6D5AFE;
    --bnk-magenta:       #C074E6;
    --bnk-periwinkle:    #AFC0FE;

    --bnk-text:          #F3F0FB;   /* primary text              */
    --bnk-text-hero:     #D3CCE8;   /* on-hero copy              */
    --bnk-text-2:        #C4BBDD;   /* secondary                 */
    --bnk-text-3:        #BBB2D6;
    --bnk-text-muted:    #9B8FD6;   /* muted                     */
    --bnk-text-muted-2:  #8E86AE;
    --bnk-text-faint:    #6E6690;   /* mono captions             */
    --bnk-on-violet:     #E9E3FB;   /* text on outlined/tonal    */

    --bnk-emerald:       #54D6A0;
    --bnk-emerald-text:  #7FE7BE;
    --bnk-amber:         #E5A94E;
    --bnk-amber-text:    #F0C485;

    --bnk-hairline:      rgba(255, 255, 255, .07);
    --bnk-hairline-soft: rgba(255, 255, 255, .06);
    --bnk-violet-edge:   rgba(167, 139, 250, .4);   /* card-hover / emphasis border */
    --bnk-violet-edge-soft: rgba(167, 139, 250, .2);

    --bnk-mono: var(--md-sys-typescale-mono-family,
      "JetBrains Mono", "Fira Code", "Cascadia Code", ui-monospace, monospace);

    /* ── M3 token remap (the keystone) ───────────────────────────────────────
       The shared dual-layout views (marketplace, learn, profiles) are built
       from M3 components that reference these semantic tokens. Redefining the
       tokens to brand values HERE — scoped to .public-layout — re-themes every
       shared component to the dark violet brand in one place, exactly the
       "theming is a single-file change" contract in tokens/color.css. The
       authenticated workspace renders in application.html.erb (no .public-layout
       class), so its Material-3 ops palette is completely unaffected. */
    --md-sys-color-background:                #0B0713;
    --md-sys-color-on-background:             #F3F0FB;
    --md-sys-color-surface:                   #161023;
    --md-sys-color-on-surface:                #F3F0FB;
    --md-sys-color-surface-variant:           #181027;
    --md-sys-color-on-surface-variant:        #9B8FD6;
    --md-sys-color-surface-container-lowest:  #0F0A1C;
    --md-sys-color-surface-container-low:     #161023;
    --md-sys-color-surface-container:         #181027;
    --md-sys-color-surface-container-high:    #1d1530;
    --md-sys-color-surface-container-highest: #221836;
    --md-sys-color-outline:                   rgba(255, 255, 255, .18);
    --md-sys-color-outline-variant:           rgba(255, 255, 255, .07);

    --md-sys-color-primary:               #7C6BFF;
    --md-sys-color-on-primary:            #FFFFFF;
    --md-sys-color-primary-container:     rgba(124, 107, 255, .22);
    --md-sys-color-on-primary-container:  #C9C0FF;
    --md-sys-color-secondary:             #54D6A0;
    --md-sys-color-on-secondary:          #06251A;
    --md-sys-color-secondary-container:   rgba(84, 214, 160, .16);
    --md-sys-color-on-secondary-container:#7FE7BE;
    --md-sys-color-tertiary:              #E5A94E;
    --md-sys-color-on-tertiary:           #2A1B05;
    --md-sys-color-tertiary-container:    rgba(229, 169, 78, .20);
    --md-sys-color-on-tertiary-container: #F0C485;
    --md-sys-color-error:                 #FCA5A5;
    --md-sys-color-on-error:              #2A0B0B;
    --md-sys-color-error-container:       rgba(220, 80, 80, .18);
    --md-sys-color-on-error-container:    #FCA5A5;

    /* Override pages/public.css's M3-token background so the whole
       public chrome (header→main→footer flex column) is brand-dark. */
    background: var(--bnk-bg);
    color: var(--bnk-text);
    overflow-x: hidden;
  }

  .public-layout .public-main,
  .public-layout .public-page {
    background: var(--bnk-bg);
    color: var(--bnk-text);
  }

  /* Shared content measure used across the rewritten public pages. */
  .public-layout .bnk-container {
    max-width: 1180px;
    margin: 0 auto;
    padding: 0 24px;
  }

  .public-layout .bnk-mono {
    font-family: var(--bnk-mono);
  }

  /* ════════════════════════════════════════════════════════════
     CHROME — sticky nav, mobile overlay menu, footer
     ════════════════════════════════════════════════════════════ */

  .public-layout .bnk-nav {
    position: sticky;
    top: 0;
    z-index: 50;
    background: rgba(11, 7, 19, .78);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    border-bottom: 1px solid var(--bnk-hairline-soft);
  }

  .public-layout .bnk-nav__inner {
    max-width: 1180px;
    margin: 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 14px 24px;
  }

  .public-layout .bnk-brand {
    display: inline-flex;
    align-items: center;
    gap: 11px;
    color: var(--bnk-text);
    cursor: pointer;
  }

  .public-layout .bnk-brand__mark { display: block; flex-shrink: 0; }

  .public-layout .bnk-brand__word {
    font-size: 18px;
    font-weight: 800;
    letter-spacing: -.01em;
  }

  .public-layout .bnk-nav__links {
    display: flex;
    align-items: center;
    gap: 26px;
    font-size: 14px;
    font-weight: 500;
  }

  .public-layout .bnk-nav__link {
    color: var(--bnk-text-3);
    transition: color .15s ease;
  }
  .public-layout .bnk-nav__link:hover { color: #fff; }
  .public-layout .bnk-nav__link[aria-current="page"] { color: #fff; }

  /* Hamburger trigger — a real <button> (accessible, JS-toggled). */
  .public-layout .bnk-burger {
    display: none;            /* shown < 820px via the media query below */
    flex-direction: column;
    gap: 5px;
    width: 34px;
    padding: 4px;
    background: none;
    border: none;
    cursor: pointer;
  }
  .public-layout .bnk-burger span {
    height: 2px;
    background: var(--bnk-on-violet);
    border-radius: 2px;
  }

  /* Full-screen overlay menu (mobile). Hidden until .is-open is set by JS. */
  .public-layout .bnk-menu {
    position: fixed;
    inset: 0;
    z-index: 100;
    background: linear-gradient(175deg, #19102E, #0B0713);
    padding: 20px 24px;
    display: none;
    flex-direction: column;
  }
  .public-layout .bnk-menu.is-open { display: flex; }

  .public-layout .bnk-menu__top {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }
  .public-layout .bnk-menu__close {
    width: 32px;
    height: 32px;
    background: none;
    border: none;
    color: var(--bnk-on-violet);
    cursor: pointer;
    font-size: 26px;
    line-height: 1;
  }
  .public-layout .bnk-menu__links {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-top: 30px;
  }
  .public-layout .bnk-menu__link {
    font-size: 30px;
    font-weight: 800;
    letter-spacing: -.02em;
    color: var(--bnk-text-3);
    padding: 13px 0;
  }
  .public-layout .bnk-menu__link[aria-current="page"] { color: #fff; }
  .public-layout .bnk-menu__signin {
    margin-top: 18px;
    padding: 15px;
    border-radius: 13px;
    background: var(--bnk-violet);
    color: #fff;
    font-size: 16px;
    font-weight: 600;
    text-align: center;
  }
  .public-layout .bnk-menu__foot {
    margin: 26px 0 0;
    font-family: var(--bnk-mono);
    font-size: 10.5px;
    line-height: 1.7;
    letter-spacing: .04em;
    color: var(--bnk-text-faint);
  }

  /* ── Footer ── */
  .public-layout .bnk-footer {
    border-top: 1px solid var(--bnk-hairline);
    background: var(--bnk-bg-footer);
  }
  .public-layout .bnk-footer__inner {
    max-width: 1180px;
    margin: 0 auto;
    padding: 40px 24px;
    display: flex;
    justify-content: space-between;
    gap: 34px;
    flex-wrap: wrap;
  }
  .public-layout .bnk-footer__blurb {
    margin: 12px 0 0;
    font-size: 13px;
    line-height: 1.6;
    color: var(--bnk-text-muted-2);
    max-width: 34ch;
  }
  .public-layout .bnk-footer__rooted {
    margin: 10px 0 0;
    font-family: var(--bnk-mono);
    font-size: 11px;
    letter-spacing: .06em;
    color: var(--bnk-text-faint);
  }
  .public-layout .bnk-footer__cols {
    display: flex;
    gap: 54px;
    font-size: 13px;
    color: var(--bnk-text-3);
    flex-wrap: wrap;
  }
  .public-layout .bnk-footer__col {
    display: flex;
    flex-direction: column;
    gap: 10px;
  }
  .public-layout .bnk-footer__head {
    color: var(--bnk-text-faint);
    font-family: var(--bnk-mono);
    font-size: 11px;
    letter-spacing: .1em;
  }
  .public-layout .bnk-footer__link { color: var(--bnk-text-3); }
  .public-layout .bnk-footer__link:hover { color: #fff; }

  /* Breakpoint: 820px swaps the desktop links for the hamburger. */
  @media (max-width: 819px) {
    .public-layout .bnk-nav__links { display: none; }
    .public-layout .bnk-burger { display: flex; }
  }

  /* ════════════════════════════════════════════════════════════
     BUTTONS — brand variants (filled / outlined / tonal / text)
     ════════════════════════════════════════════════════════════ */

  .public-layout .bnk-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    font-size: 15px;
    font-weight: 600;
    border: 1px solid transparent;
    border-radius: 12px;
    padding: 13px 24px;
    cursor: pointer;
    text-align: center;
    transition: filter .16s ease, background .16s ease,
                border-color .16s ease, transform .16s ease, color .16s ease;
  }
  .public-layout .bnk-btn--filled {
    background: var(--bnk-violet);
    color: #fff;
    box-shadow: 0 14px 34px -12px rgba(124, 107, 255, .7);
  }
  .public-layout .bnk-btn--filled:hover {
    filter: brightness(1.08);
    transform: translateY(-1px);
  }
  .public-layout .bnk-btn--outlined {
    border-color: var(--bnk-violet-edge);
    color: var(--bnk-on-violet);
    background: transparent;
  }
  .public-layout .bnk-btn--outlined:hover { background: rgba(124, 107, 255, .16); }
  .public-layout .bnk-btn--tonal {
    background: rgba(124, 107, 255, .16);
    color: #C9C0FF;
  }
  .public-layout .bnk-btn--tonal:hover { filter: brightness(1.12); }
  .public-layout .bnk-btn--text {
    background: none;
    padding: 0;
    color: var(--bnk-periwinkle);
    box-shadow: none;
  }
  .public-layout .bnk-btn--text:hover { color: #fff; }
  .public-layout .bnk-btn--sm { padding: 10px 18px; font-size: 14px; }

  /* ════════════════════════════════════════════════════════════
     STATUS CHIPS — gear/asset status pills
     ════════════════════════════════════════════════════════════ */

  .public-layout .bnk-chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 11px;
    border-radius: 999px;
    font-size: 11px;
    font-weight: 600;
    line-height: 1.4;
  }
  .public-layout .bnk-chip__dot {
    width: 6px;
    height: 6px;
    border-radius: 999px;
    background: currentColor;
  }
  .public-layout .bnk-chip--available    { background: rgba(84, 214, 160, .16); color: #7FE7BE; }
  .public-layout .bnk-chip--available .bnk-chip__dot   { background: #54D6A0; }
  .public-layout .bnk-chip--deployed     { background: rgba(124, 107, 255, .22); color: #C9C0FF; }
  .public-layout .bnk-chip--deployed .bnk-chip__dot    { background: #7C6BFF; }
  .public-layout .bnk-chip--maintenance  { background: rgba(229, 169, 78, .20); color: #F0C485; }
  .public-layout .bnk-chip--maintenance .bnk-chip__dot { background: #E5A94E; }
  .public-layout .bnk-chip--retired      { background: rgba(255, 255, 255, .12); color: #C8C0E2; }
  .public-layout .bnk-chip--retired .bnk-chip__dot     { background: #8A82A6; }

  /* ════════════════════════════════════════════════════════════
     HERO + HOME
     ════════════════════════════════════════════════════════════ */

  .public-layout .bnk-hero {
    position: relative;
    padding: 80px 24px;
    overflow: hidden;
    background:
      radial-gradient(720px 380px at 18% 20%, rgba(124, 107, 255, .22), transparent 60%),
      radial-gradient(560px 360px at 88% 72%, rgba(192, 116, 230, .16), transparent 60%),
      var(--bnk-bg);
  }
  .public-layout .bnk-hero__inner {
    max-width: 1180px;
    margin: 0 auto;
    display: flex;
    gap: 48px;
    align-items: center;
    flex-wrap: wrap;
  }
  .public-layout .bnk-hero__copy { flex: 1 1 440px; min-width: 300px; }
  .public-layout .bnk-eyebrow {
    font-family: var(--bnk-mono);
    font-size: 12px;
    letter-spacing: .16em;
    text-transform: uppercase;
    color: var(--bnk-periwinkle);
  }
  .public-layout .bnk-hero__h1 {
    margin: 18px 0 0;
    font-size: clamp(38px, 5.6vw, 62px);
    line-height: 1.02;
    font-weight: 800;
    letter-spacing: -.03em;
  }
  .public-layout .bnk-hero__sub {
    margin: 22px 0 0;
    font-size: clamp(15px, 1.6vw, 18px);
    line-height: 1.6;
    color: var(--bnk-text-hero);
    max-width: 46ch;
  }
  .public-layout .bnk-hero__cta {
    display: flex;
    flex-wrap: wrap;
    gap: 12px;
    margin-top: 30px;
  }
  .public-layout .bnk-trust {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    margin-top: 30px;
    font-family: var(--bnk-mono);
    font-size: 11.5px;
    letter-spacing: .05em;
    color: #9991B8;
  }

  /* Logo medallion + floating stat cards */
  .public-layout .bnk-hero__art { flex: 1 1 360px; min-width: 280px; }
  .public-layout .bnk-medallion { position: relative; max-width: 420px; margin: 0 auto; }
  .public-layout .bnk-medallion__glow {
    position: absolute;
    inset: -10% -6%;
    background: radial-gradient(circle at 50% 45%, rgba(124, 107, 255, .42), transparent 65%);
    filter: blur(8px);
  }
  .public-layout .bnk-medallion__img {
    position: relative;
    width: 100%;
    border-radius: 24px;
    border: 1px solid rgba(255, 255, 255, .1);
    box-shadow: 0 30px 70px -30px rgba(0, 0, 0, .8);
    display: block;
  }
  .public-layout .bnk-floatcard {
    position: absolute;
    background: var(--bnk-surface);
    border: 1px solid rgba(255, 255, 255, .1);
    border-radius: 12px;
    padding: 10px 14px;
    box-shadow: 0 16px 40px -16px rgba(0, 0, 0, .7);
  }
  .public-layout .bnk-floatcard--bl { left: -18px; bottom: 30px; }
  .public-layout .bnk-floatcard--tr { right: -14px; top: 22px; }
  .public-layout .bnk-floatcard__label {
    font-family: var(--bnk-mono);
    font-size: 10px;
    letter-spacing: .1em;
    color: var(--bnk-text-muted-2);
  }
  .public-layout .bnk-floatcard__value { font-size: 13px; font-weight: 700; margin-top: 3px; }

  /* Section scaffold */
  .public-layout .bnk-section { max-width: 1180px; margin: 0 auto; padding: 56px 24px 0; }
  .public-layout .bnk-section--last { padding-bottom: 64px; }
  .public-layout .bnk-section__head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 20px;
  }
  .public-layout .bnk-h2 {
    margin: 0;
    font-size: clamp(22px, 3vw, 28px);
    font-weight: 800;
    letter-spacing: -.02em;
  }
  .public-layout .bnk-section__link {
    font-size: 14px;
    color: var(--bnk-periwinkle);
    font-weight: 600;
    white-space: nowrap;
  }
  .public-layout .bnk-section__link:hover { color: #fff; }
  .public-layout .bnk-section__note { margin: 0 0 18px; font-size: 13.5px; color: var(--bnk-text-muted-2); }

  /* Gear cards (home + reused) */
  .public-layout .bnk-gear-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 18px;
  }
  .public-layout .bnk-gear-card {
    background: var(--bnk-surface);
    border: 1px solid var(--bnk-hairline);
    border-radius: 16px;
    overflow: hidden;
    cursor: pointer;
    transition: transform .18s ease, border-color .18s ease;
  }
  .public-layout .bnk-gear-card:hover {
    transform: translateY(-4px);
    border-color: var(--bnk-violet-edge);
  }
  .public-layout .bnk-gear-card__media {
    position: relative;
    height: 172px;
    background: var(--bnk-hold);
    display: grid;
    place-items: center;
    border-bottom: 1px solid var(--bnk-hairline-soft);
    overflow: hidden;
  }
  .public-layout .bnk-gear-card__media img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    -o-object-fit: cover;
       object-fit: cover;
  }
  .public-layout .bnk-hold-cap {
    font-family: var(--bnk-mono);
    font-size: 11px;
    letter-spacing: .08em;
    color: var(--bnk-text-faint);
  }
  .public-layout .bnk-gear-card__media .bnk-chip {
    position: absolute;
    top: 12px;
    left: 12px;
  }
  .public-layout .bnk-gear-card__body { padding: 16px 18px; }
  .public-layout .bnk-gear-card__name { font-size: 15.5px; font-weight: 700; }
  .public-layout .bnk-gear-card__type { font-size: 12.5px; color: var(--bnk-text-muted-2); margin-top: 2px; }
  .public-layout .bnk-gear-card__rates {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-top: 16px;
    padding-top: 13px;
    border-top: 1px solid var(--bnk-hairline-soft);
  }
  .public-layout .bnk-rate {
    font-family: var(--bnk-mono);
    font-size: 13px;
    color: var(--bnk-text-muted);
  }
  .public-layout .bnk-rate small { font-size: 10px; }
  .public-layout .bnk-rate--member { font-weight: 700; color: var(--bnk-emerald-text); }
  .public-layout .bnk-rate--member small { color: #6E8E7E; }

  /* Skill pills (home commons) */
  .public-layout .bnk-skill-pills { display: flex; flex-wrap: wrap; gap: 10px; }
  .public-layout .bnk-skill-pill {
    background: var(--bnk-surface);
    border: 1px solid var(--bnk-hairline);
    border-radius: 999px;
    padding: 10px 16px 10px 14px;
    display: flex;
    align-items: center;
    gap: 10px;
    transition: border-color .15s ease;
  }
  .public-layout .bnk-skill-pill:hover { border-color: var(--bnk-violet-edge); }
  .public-layout .bnk-skill-pill__count {
    font-family: var(--bnk-mono);
    font-size: 13px;
    font-weight: 700;
    color: #BBAFFF;
  }
  .public-layout .bnk-skill-pill__name { font-size: 13.5px; font-weight: 600; }
  .public-layout .bnk-skill-pill__teach {
    font-size: 11px;
    color: var(--bnk-emerald-text);
    background: rgba(84, 214, 160, .12);
    padding: 2px 9px;
    border-radius: 999px;
  }

  /* Values band (home) */
  .public-layout .bnk-values {
    padding: clamp(24px, 3vw, 36px);
    background: linear-gradient(120deg, rgba(124, 107, 255, .14), rgba(192, 116, 230, .08));
    border: 1px solid var(--bnk-violet-edge-soft);
    border-radius: 20px;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 28px;
  }
  .public-layout .bnk-values__eyebrow {
    font-family: var(--bnk-mono);
    font-size: 11px;
    letter-spacing: .12em;
    color: var(--bnk-periwinkle);
  }
  .public-layout .bnk-values__title { font-size: 16px; font-weight: 700; margin-top: 10px; }
  .public-layout .bnk-values__desc { font-size: 13.5px; line-height: 1.55; color: #B6ADCF; margin-top: 6px; }

  /* ════════════════════════════════════════════════════════════
     ABOUT
     ════════════════════════════════════════════════════════════ */

  .public-layout .bnk-hero--about {
    padding: 80px 24px 56px;
    background:
      radial-gradient(680px 360px at 22% 16%, rgba(124, 107, 255, .2), transparent 60%),
      radial-gradient(520px 320px at 90% 80%, rgba(192, 116, 230, .13), transparent 60%),
      var(--bnk-bg);
  }
  .public-layout .bnk-hero--about .bnk-hero__inner { display: block; max-width: 64ch; }
  .public-layout .bnk-hero--about .bnk-hero__h1 {
    font-size: clamp(34px, 5vw, 52px);
    line-height: 1.04;
  }
  .public-layout .bnk-hero--about .bnk-hero__sub { max-width: 62ch; color: var(--bnk-text-hero); }

  .public-layout .bnk-principles {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 16px;
    margin-top: 20px;
  }
  .public-layout .bnk-principle {
    background: var(--bnk-surface);
    border: 1px solid var(--bnk-hairline);
    border-radius: 16px;
    padding: 20px;
  }
  .public-layout .bnk-principle--hi {
    background: linear-gradient(135deg, rgba(124, 107, 255, .16), rgba(192, 116, 230, .1));
    border-color: rgba(167, 139, 250, .22);
  }
  .public-layout .bnk-principle__num {
    font-family: var(--bnk-mono);
    font-size: 12px;
    font-weight: 700;
    color: #BBAFFF;
    background: rgba(124, 107, 255, .16);
    width: 32px;
    height: 32px;
    border-radius: 9px;
    display: grid;
    place-items: center;
  }
  .public-layout .bnk-principle--hi .bnk-principle__num { color: #fff; background: rgba(124, 107, 255, .5); }
  .public-layout .bnk-principle__title { font-size: 15.5px; font-weight: 700; margin-top: 13px; }
  .public-layout .bnk-principle__desc { font-size: 13px; line-height: 1.55; color: var(--bnk-text-muted); margin-top: 6px; }
  .public-layout .bnk-principle--hi .bnk-principle__desc { color: var(--bnk-text-2); }

  .public-layout .bnk-steps {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 16px;
  }
  .public-layout .bnk-step {
    background: var(--bnk-surface);
    border: 1px solid var(--bnk-hairline);
    border-radius: 16px;
    padding: 24px;
  }
  .public-layout .bnk-step__num {
    font-family: var(--bnk-mono);
    font-size: 11px;
    letter-spacing: .12em;
    color: var(--bnk-periwinkle);
  }
  .public-layout .bnk-step__title { font-size: 18px; font-weight: 700; margin-top: 10px; }
  .public-layout .bnk-step__desc { font-size: 13.5px; line-height: 1.6; color: var(--bnk-text-muted); margin-top: 6px; }

  .public-layout .bnk-stats {
    padding: 28px 30px;
    background: var(--bnk-surface);
    border: 1px solid var(--bnk-hairline);
    border-radius: 18px;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 24px;
  }
  .public-layout .bnk-stat__value {
    font-size: clamp(28px, 4vw, 36px);
    font-weight: 800;
    font-family: var(--bnk-mono);
  }
  .public-layout .bnk-stat__label { font-size: 12.5px; color: var(--bnk-text-muted); margin-top: 4px; }

  .public-layout .bnk-rooted {
    position: relative;
    border-radius: 18px;
    overflow: hidden;
    border: 1px solid var(--bnk-violet-edge-soft);
  }
  .public-layout .bnk-rooted__media {
    width: 100%;
    height: 170px;
    background: var(--bnk-hold);
    display: grid;
    place-items: center;
  }
  .public-layout .bnk-rooted__bar {
    padding: 26px 30px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 24px;
    flex-wrap: wrap;
    background: linear-gradient(120deg, rgba(124, 107, 255, .14), rgba(192, 116, 230, .08));
  }
  .public-layout .bnk-rooted__title { font-size: 19px; font-weight: 700; }
  .public-layout .bnk-rooted__desc { font-size: 13.5px; color: #B6ADCF; margin-top: 6px; max-width: 54ch; }

  /* ════════════════════════════════════════════════════════════
     CONTACT
     ════════════════════════════════════════════════════════════ */

  .public-layout .bnk-contact {
    max-width: 1180px;
    margin: 0 auto;
    padding: 48px 24px 64px;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    gap: 40px;
    align-items: start;
  }
  .public-layout .bnk-contact__h1 {
    margin: 16px 0 0;
    font-size: clamp(32px, 4.5vw, 44px);
    font-weight: 800;
    letter-spacing: -.025em;
    line-height: 1.05;
  }
  .public-layout .bnk-contact__lead { margin: 18px 0 0; font-size: 15px; line-height: 1.65; color: var(--bnk-text-2); max-width: 40ch; }
  .public-layout .bnk-contact__facts { margin-top: 30px; display: flex; flex-direction: column; gap: 18px; }
  .public-layout .bnk-fact__label {
    font-family: var(--bnk-mono);
    font-size: 11px;
    letter-spacing: .1em;
    color: var(--bnk-text-faint);
  }
  .public-layout .bnk-fact__value { font-size: 15px; font-weight: 600; margin-top: 4px; }
  .public-layout .bnk-note {
    margin-top: 26px;
    padding: 14px 16px;
    background: rgba(255, 255, 255, .04);
    border: 1px solid var(--bnk-hairline);
    border-radius: 12px;
    font-size: 12.5px;
    line-height: 1.5;
    color: var(--bnk-text-muted);
  }
  .public-layout .bnk-form-card {
    background: var(--bnk-surface);
    border: 1px solid var(--bnk-hairline);
    border-radius: 18px;
    padding: 30px;
  }
  .public-layout .bnk-form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
  .public-layout .bnk-field--full { grid-column: 1 / -1; }
  .public-layout .bnk-label {
    display: block;
    font-family: var(--bnk-mono);
    font-size: 11px;
    letter-spacing: .08em;
    color: var(--bnk-text-muted);
    margin-bottom: 7px;
  }
  .public-layout .bnk-input,
  .public-layout .bnk-textarea {
    width: 100%;
    background: var(--bnk-input);
    border: 1px solid rgba(255, 255, 255, .1);
    border-radius: 10px;
    padding: 0 14px;
    color: var(--bnk-on-violet);
    font-size: 14px;
    outline: none;
    transition: border-color .15s ease;
  }
  .public-layout .bnk-input { height: 46px; }
  .public-layout .bnk-textarea { height: 120px; padding: 12px 14px; resize: vertical; }
  .public-layout .bnk-input:focus,
  .public-layout .bnk-textarea:focus { border-color: rgba(167, 139, 250, .6); }
  .public-layout .bnk-input::-moz-placeholder, .public-layout .bnk-textarea::-moz-placeholder { color: var(--bnk-text-faint); }
  .public-layout .bnk-input::placeholder,
  .public-layout .bnk-textarea::placeholder { color: var(--bnk-text-faint); }
  .public-layout .bnk-check {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    gap: 10px;
    color: #B6ADCF;
    font-size: 13px;
    cursor: pointer;
  }
  .public-layout .bnk-check input { width: 18px; height: 18px; accent-color: var(--bnk-violet); }
  .public-layout .bnk-form-foot {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    flex-wrap: wrap;
    margin-top: 6px;
  }
  .public-layout .bnk-form-foot__note { font-size: 12px; color: var(--bnk-text-faint); }
  /* Honeypot — off-screen, never shown to humans or AT (bots fill it). */
  .public-layout .bnk-hp {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }

  /* ════════════════════════════════════════════════════════════
     SIGN IN — two-panel card
     ════════════════════════════════════════════════════════════ */

  .public-layout .bnk-signin {
    max-width: 1080px;
    margin: 0 auto;
    padding: 40px 24px 64px;
  }
  .public-layout .bnk-signin__card {
    background: var(--bnk-panel);
    border: 1px solid var(--bnk-hairline-soft);
    border-radius: 22px;
    overflow: hidden;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  }
  .public-layout .bnk-signin__brand {
    position: relative;
    padding: 44px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: 30px;
    background: linear-gradient(165deg, rgba(124, 107, 255, .2), rgba(192, 116, 230, .1));
    overflow: hidden;
  }
  .public-layout .bnk-signin__glow {
    position: absolute;
    inset: 0;
    background: radial-gradient(460px 340px at 30% 20%, rgba(124, 107, 255, .3), transparent 60%);
    animation: bnkGlow 7s ease-in-out infinite;
  }
  .public-layout .bnk-signin__brandrow { position: relative; display: flex; align-items: center; gap: 11px; }
  .public-layout .bnk-signin__brandrow span { font-size: 20px; font-weight: 800; }
  .public-layout .bnk-signin__logo {
    width: 116px;
    border-radius: 18px;
    border: 1px solid rgba(255, 255, 255, .12);
    box-shadow: 0 24px 50px -20px rgba(0, 0, 0, .7);
    margin-bottom: 22px;
    display: block;
  }
  .public-layout .bnk-signin__welcome {
    position: relative;
  }
  .public-layout .bnk-signin__welcome h2 {
    margin: 0;
    font-size: clamp(24px, 3.5vw, 30px);
    font-weight: 800;
    letter-spacing: -.02em;
    line-height: 1.1;
  }
  .public-layout .bnk-signin__welcome p { margin: 14px 0 0; font-size: 14px; line-height: 1.6; color: var(--bnk-text-2); max-width: 34ch; }
  .public-layout .bnk-signin__brandfoot {
    position: relative;
    font-family: var(--bnk-mono);
    font-size: 11px;
    letter-spacing: .06em;
    color: var(--bnk-text-muted);
  }
  .public-layout .bnk-signin__form {
    padding: 44px;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }
  .public-layout .bnk-signin__kicker {
    font-family: var(--bnk-mono);
    font-size: 11px;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--bnk-periwinkle);
  }
  .public-layout .bnk-signin__h1 {
    margin: 10px 0 4px;
    font-size: clamp(22px, 3vw, 26px);
    font-weight: 800;
    letter-spacing: -.02em;
  }
  .public-layout .bnk-signin__hint { margin: 0 0 20px; font-size: 13.5px; color: var(--bnk-text-muted); }
  .public-layout .bnk-signin__divider {
    display: flex;
    align-items: center;
    gap: 14px;
    margin: 26px 0;
  }
  .public-layout .bnk-signin__divider::before,
  .public-layout .bnk-signin__divider::after {
    content: "";
    flex: 1;
    height: 1px;
    background: rgba(255, 255, 255, .08);
  }
  .public-layout .bnk-signin__divider span {
    font-family: var(--bnk-mono);
    font-size: 11px;
    color: var(--bnk-text-faint);
    letter-spacing: .1em;
  }
  .public-layout .bnk-signin__admingrid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 10px; }
  /* Full-width only for submit buttons + the disclosure summary, not the
     inline text links in the explore row. */
  .public-layout .bnk-signin input[type="submit"].bnk-btn,
  .public-layout .bnk-signin summary.bnk-btn { width: 100%; }
  .public-layout .bnk-signin__remember {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 12px;
    font-size: 13px;
    color: #B6ADCF;
  }
  /* The auth panel reuses the .auth-card hook (selector contract) but must wear
     the brand, not the M3 white card. Strip its surface + spacing here. */
  .public-layout .bnk-signin .auth-card {
    background: none;
    border: none;
    box-shadow: none;
    border-radius: 0;
    max-width: none;
    margin: 0;
    padding: 44px;
  }
  .public-layout .bnk-signin .auth-card__form { display: block; }
  .public-layout .bnk-signin summary.bnk-btn {
    list-style: none;
    display: block;
    text-align: center;
    padding: 12px;
    border: 1px solid var(--bnk-violet-edge);
    border-radius: 11px;
    color: var(--bnk-on-violet);
  }
  .public-layout .bnk-signin summary.bnk-btn::-webkit-details-marker { display: none; }
  .public-layout .bnk-signin summary.bnk-btn:hover { background: rgba(124, 107, 255, .16); color: #fff; }
  .public-layout .bnk-signin details[open] summary.bnk-btn { margin-bottom: 14px; }

  /* ════════════════════════════════════════════════════════════
     ANIMATIONS — calm by design (respect reduced-motion)
     ════════════════════════════════════════════════════════════ */
  @keyframes bnkGlow { 0%, 100% { opacity: .5; } 50% { opacity: .85; } }
  @keyframes bnkBlink { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }

  @media (prefers-reduced-motion: reduce) {
    .public-layout .bnk-signin__glow,
    .public-layout .bnk-blink { animation: none; }
    .public-layout .bnk-gear-card,
    .public-layout .bnk-btn { transition: none; }
  }

  /* ════════════════════════════════════════════════════════════
     SHARED-VIEW SKINS — marketplace / learn / profiles
     These views also render inside the authenticated app shell, so
     we ONLY retheme them when they sit in the public chrome. Markup
     is untouched; the M3 ops styling still applies signed-in.
     ════════════════════════════════════════════════════════════ */

  /* ── Marketplace ── */
  .public-layout .marketplace-page { max-width: 1180px; margin: 0 auto; padding: 48px 24px 64px; }
  .public-layout .marketplace-header__title {
    font-size: clamp(30px, 4.5vw, 40px);
    font-weight: 800;
    letter-spacing: -.025em;
    color: var(--bnk-text);
  }
  .public-layout .marketplace-header__intro { color: var(--bnk-text-muted); font-size: 14.5px; line-height: 1.6; max-width: 60ch; }
  .public-layout .marketplace-shelf { margin-top: 36px; }
  .public-layout .marketplace-shelf__title { font-size: 17px; font-weight: 700; color: var(--bnk-text); display: flex; align-items: center; gap: 8px; }
  .public-layout .marketplace-shelf__icon { color: var(--bnk-periwinkle); }
  .public-layout .marketplace-shelf__desc { color: var(--bnk-text-muted-2); font-size: 13.5px; margin-top: 4px; }
  .public-layout .marketplace-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 18px;
    margin-top: 16px;
  }
  .public-layout .marketplace-card {
    background: var(--bnk-surface);
    border: 1px solid var(--bnk-hairline);
    border-radius: 16px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    transition: transform .18s ease, border-color .18s ease;
  }
  .public-layout .marketplace-card:hover { transform: translateY(-4px); border-color: var(--bnk-violet-edge); }
  .public-layout .marketplace-card__photo { width: 100%; height: 172px; -o-object-fit: cover; object-fit: cover; border-bottom: 1px solid var(--bnk-hairline-soft); }
  .public-layout .marketplace-card__body { padding: 16px 18px; flex: 1; background: transparent; color: var(--bnk-text); }
  .public-layout .marketplace-card__name { font-size: 15.5px; font-weight: 700; margin: 8px 0 0; color: var(--bnk-text); }
  .public-layout .marketplace-card__desc { font-size: 13px; color: var(--bnk-text-muted); margin-top: 6px; line-height: 1.5; }
  .public-layout .marketplace-card__items { margin: 10px 0 0; padding-left: 18px; font-size: 12.5px; color: var(--bnk-text-muted); }
  .public-layout .marketplace-card__items li { color: var(--bnk-text-muted); }
  .public-layout .marketplace-card__rates { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--bnk-hairline-soft); }
  .public-layout .marketplace-card__rate { display: flex; justify-content: space-between; font-family: var(--bnk-mono); font-size: 13px; color: var(--bnk-text-muted); }
  .public-layout .marketplace-card__rate-label { color: var(--bnk-text-muted); }
  .public-layout .marketplace-card__rate-value { font-weight: 700; color: var(--bnk-text); }
  .public-layout .marketplace-card__rate--member,
  .public-layout .marketplace-card__rate--member .marketplace-card__rate-value { color: var(--bnk-emerald-text); font-weight: 700; }
  .public-layout .marketplace-card__rate-note { background: rgba(255, 255, 255, .08); color: #C8C0E2; }
  .public-layout .marketplace-card__offering-count { font-family: var(--bnk-mono); font-size: 13px; color: #BBAFFF; }
  /* The shared card footer ships a light surface-variant band; flatten it onto
     the brand surface (this was the white strip at the card bottom). */
  .public-layout .marketplace-card__footer {
    padding: 0 18px 18px;
    background: transparent;
    border-top: none;
  }
  .public-layout .marketplace-empty {
    background: var(--bnk-surface);
    border: 1px solid var(--bnk-hairline);
    border-radius: 16px;
    padding: 24px;
    color: var(--bnk-text-muted);
  }

  /* Re-skin the shared .chip component when it appears in marketplace/learn
     public surfaces so type/teach badges read in the violet brand. */
  .public-layout .chip {
    border-radius: 999px;
    padding: 4px 11px;
    font-size: 11px;
    font-weight: 600;
    border: none;
  }
  .public-layout .chip--neutral { background: rgba(255, 255, 255, .08); color: #C8C0E2; }
  .public-layout .chip--primary,
  .public-layout .chip--accent { background: rgba(124, 107, 255, .22); color: #C9C0FF; }
  .public-layout .chip--success { background: rgba(84, 214, 160, .16); color: var(--bnk-emerald-text); }

  .public-layout .marketplace-header__cta,
  .public-layout .marketplace-card__action,
  .public-layout .marketplace-shelf__member-note { font-size: 13px; }

  /* Re-skin shared .btn variants used by the marketplace request flow so the
     signed-in-in-public-chrome buttons match the brand. */
  .public-layout .btn--filled {
    background: var(--bnk-violet);
    color: #fff;
    border: none;
    border-radius: 11px;
    box-shadow: 0 12px 30px -12px rgba(124, 107, 255, .6);
  }
  .public-layout .btn--filled:hover { filter: brightness(1.08); }
  .public-layout .btn--outlined {
    background: transparent;
    border: 1px solid var(--bnk-violet-edge);
    color: var(--bnk-on-violet);
    border-radius: 11px;
  }
  .public-layout .btn--outlined:hover { background: rgba(124, 107, 255, .16); }
  .public-layout .btn--text { color: var(--bnk-periwinkle); }
  .public-layout .btn--text:hover { color: #fff; }
  .public-layout .text-link,
  .public-layout .link { color: var(--bnk-periwinkle); }
  .public-layout .text-muted-sm { color: var(--bnk-text-faint); font-size: 12px; }

  /* ── Learn (training catalogue + lesson reader share this skin) ── */
  .public-layout .learn-page { max-width: 1180px; margin: 0 auto; padding: 48px 24px 64px; }
  .public-layout .learn-page .page-header__title,
  .public-layout .training-show__title {
    font-size: clamp(30px, 4.5vw, 40px);
    font-weight: 800;
    letter-spacing: -.025em;
  }
  .public-layout .training-card-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 16px;
    margin-top: 20px;
  }
  .public-layout .training-card {
    background: var(--bnk-surface);
    border: 1px solid var(--bnk-hairline);
    border-radius: 14px;
    padding: 18px;
    transition: transform .18s ease, border-color .18s ease;
  }
  .public-layout .training-card:hover { transform: translateY(-3px); border-color: var(--bnk-violet-edge); }
  .public-layout .training-card__title { font-size: 15px; font-weight: 700; color: var(--bnk-text); }
  .public-layout .training-card__title:hover { color: #fff; }
  .public-layout .empty-state {
    background: var(--bnk-surface);
    border: 1px solid var(--bnk-hairline);
    border-radius: 16px;
    padding: 28px;
    text-align: center;
    color: var(--bnk-text-muted);
  }
  .public-layout .empty-state__title { font-weight: 700; color: var(--bnk-text); }

  /* Lesson reader progress bar (Learn show, public). */
  .public-layout .progress-bar,
  .public-layout .lesson-progress__track {
    background: rgba(255, 255, 255, .07);
    border-radius: 999px;
    overflow: hidden;
  }
  .public-layout .progress-bar__fill,
  .public-layout .lesson-progress__fill {
    background: linear-gradient(90deg, #7C6BFF, #C074E6);
    border-radius: 999px;
    transition: width .3s ease;
  }

  /* ── Member profile ── */
  .public-layout .profile-page { max-width: 1180px; margin: 0 auto; padding: 44px 24px 64px; }
  .public-layout .profile-header {
    display: flex;
    align-items: flex-start;
    gap: 24px;
    flex-wrap: wrap;
  }
  .public-layout .profile-header__name {
    font-size: clamp(26px, 4vw, 32px);
    font-weight: 800;
    letter-spacing: -.02em;
  }
  .public-layout .profile-header__bio { margin: 10px 0 0; font-size: 14.5px; line-height: 1.6; color: var(--bnk-text-2); max-width: 56ch; }
  .public-layout .profile-avatar {
    border-radius: 24px;
    background: linear-gradient(135deg, #7C6BFF, #C074E6) !important;
    color: #fff !important;
    box-shadow: 0 16px 36px -14px rgba(124, 107, 255, .6);
  }
  .public-layout .profile-page .card,
  .public-layout .aggregate-card {
    background: var(--bnk-surface);
    border: 1px solid var(--bnk-hairline);
    border-radius: 12px;
    color: var(--bnk-text);
  }

  /* ── Legacy public-class bridge ──────────────────────────────────────────
     pages/public.css styles (.public-page__hero, .public-section, etc.) point
     at the M3 light/surface tokens, which clash on the brand-dark surface. The
     refreshed pages use .bnk-* classes, but /welcome ("What we do") and any
     stragglers still use these — re-point them at brand tokens so nothing
     renders as a stray light band. */
  .public-layout .public-page__hero { background: var(--bnk-bg); }
  .public-layout .public-page__title,
  .public-layout .public-section__heading { color: var(--bnk-text); }
  .public-layout .public-page__sub,
  .public-layout .public-card-text,
  .public-layout .public-prose { color: var(--bnk-text-2); }
  .public-layout .public-page__hero,
  .public-layout .public-section { max-width: 1180px; margin: 0 auto; }
  .public-layout .public-contact-card { background: var(--bnk-surface); border-radius: 16px; }

  /* ── Splash reconciliation ───────────────────────────────────────────────
     The splash keeps a few class hooks the test suite pins (.splash-stats /
     .stat-card / .splash-gear-card / .splash-gear-card__hook). Those classes
     carry M3-token styling from pages/splash.css; re-skin them for the brand
     where they sit in the public chrome. (Two-class selectors out-specify the
     single-class splash.css rules.) */
  .public-layout .splash-stats .stat-card {
    background: none;
    border: none;
    padding: 0;
    box-shadow: none;
  }
  .public-layout .splash-gear-card {
    display: block;          /* override splash.css's flex column + gap */
    gap: 0;
  }
  .public-layout .splash-gear-card__hook {
    margin-top: 12px;
    padding-top: 0;
    border-top: none;
  }

  /* ── Commons feed ────────────────────────────────────────────────────────── */
  .public-layout .bnk-commons-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 18px;
  }
  .public-layout .bnk-commons-card {
    background: var(--bnk-surface);
    border: 1px solid var(--bnk-hairline);
    border-radius: 16px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    transition: transform .18s ease, border-color .18s ease;
  }
  .public-layout .bnk-commons-card:hover { transform: translateY(-4px); border-color: var(--bnk-violet-edge); }
  .public-layout .bnk-commons-card__media { height: 180px; overflow: hidden; border-bottom: 1px solid var(--bnk-hairline-soft); }
  .public-layout .bnk-commons-card__media img { width: 100%; height: 100%; -o-object-fit: cover; object-fit: cover; display: block; }
  .public-layout .bnk-commons-card__body { padding: 18px 20px; display: flex; flex-direction: column; gap: 10px; flex: 1; }
  .public-layout .bnk-commons-card__head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
  .public-layout .bnk-commons-card__byline { font-size: 13px; font-weight: 600; color: var(--bnk-text-2); }
  .public-layout .bnk-commons-card__text { font-size: 14px; line-height: 1.6; color: var(--bnk-text-2); }
  .public-layout .bnk-commons-card__text p { margin: 0 0 8px; }
  .public-layout .bnk-commons-card__text p:last-child { margin-bottom: 0; }
  .public-layout .bnk-commons-card__time {
    margin-top: auto;
    font-family: var(--bnk-mono);
    font-size: 11px;
    letter-spacing: .04em;
    color: var(--bnk-text-faint);
  }
  .public-layout .bnk-commons-cta { margin-top: 32px; display: flex; justify-content: center; }
}
