/* =============================================================================
   sidur — assets/app.css
   The entire visual identity. Design direction: "CLINICAL WARMTH".

   A calm, dense, Hebrew-native operations surface. The reference points are a
   well-set printed rota and a good clinical chart — not a SaaS landing page and
   not a card grid floating on drop shadows. High information density, generous
   type, colour used only to carry meaning, nothing decorative.

   -----------------------------------------------------------------------------
   FOR WHOEVER WRITES index.html / r.html / s.html
   -----------------------------------------------------------------------------
   1. The document MUST be:

        <html lang="he" dir="rtl">

      Every rule here is written with logical properties. There is not one
      physical margin-left, padding-right, left: or right: in this file. Setting
      dir="rtl" is the only thing that makes the app read correctly.

   2. Load Heebo in <head>, ABOVE this stylesheet. Weights 400 / 500 / 700 only —
      nothing here asks for another weight, and synthesised bold on Hebrew is the
      ugliest thing a browser does.

        <link rel="preconnect" href="https://fonts.googleapis.com">
        <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
        <link rel="stylesheet"
              href="https://fonts.googleapis.com/css2?family=Heebo:wght@400;500;700&display=swap">
        <link rel="stylesheet" href="./assets/app.css">

      CONTRACT §0 permits exactly this one external request. If the font never
      arrives, the fallback stack below is a real Hebrew stack, not a wish:
      Heebo, Assistant, Segoe UI, Arial Hebrew, David, system-ui, sans-serif.
      (The old styles.css declared Inter and never loaded it, so its intended
      design had literally never rendered. That is fixed here.)

   3. THEME. Dark mode follows the OS. To let the user override it, set an
      attribute on <html> — it wins in BOTH directions:

        document.documentElement.dataset.theme = 'dark';   // force dark
        document.documentElement.dataset.theme = 'light';  // force light
        delete document.documentElement.dataset.theme;     // back to the OS

   4. COLOUR CARRIES EXACTLY THREE MEANINGS AND NOTHING ELSE.

      (1) ROLE — put  data-role="Resident|YA|YA-MH|Adjunct|intern|empty"  on ANY
          element. That one attribute sets --role-fg / --role-tint / --role-rule /
          --role-glyph, which every component below consumes. Four genuinely
          separate hues (blue / ochre / violet / green), each >= 5:1 against the
          ground, and EACH PAIRED WITH A GLYPH (● ▲ ◆ ■ ○) so the mapping survives
          greyscale printing and colour blindness.

      (2) WEEKEND / HOLIDAY — a tinted column BAND, never a border. Put
          class="is-weekend" / "is-holiday" on the <col>, and
          data-band="weekend" / "holiday" on the <th>.

      (3) ATTENTION — one saturated colour, used sparingly: .is-pinned,
          .is-conflict, .is-unfilled, .is-below-floor, .issue--error.

      There is deliberately NO green "ok" and NO amber "warn" hue. Teal is the
      brand and means "interactive / selected / done", never "good"; warnings and
      errors are the same attention hue at two different weights (spine only vs.
      filled). The legacy kind names (ok / warn / err) are mapped onto that
      discipline rather than given three more colours.

      The three meanings resolve onto the submission board like this, and the
      tokens are the contract — a tile and its legend swatch MUST read the same
      custom property, never two that happen to look alike today:

          blocked   "I cannot work"   -> --state-blocked-*  (attention)
          requested "I want this"     -> --state-want-*     (brand teal)
          committed "this is agreed"  -> --state-commit-*   (indigo)

      Indigo is the one hue added beyond role / band / attention, and it exists
      because a YA's committed date (H9/H10) is neither a wish nor a fault. Each
      of the three also carries a mark (✕ / ☆ / ●), so none of them depends on
      colour alone.

   5. Every interactive target is >= 44px; date tiles are >= 48px. The old app's
      38px tiles were below the accessibility minimum on exactly the phones the
      residents use to fill the form.

   6. PRINT is designed, not switched off. The chief prints the rota, pins it up
      and photographs it for the department WhatsApp group. See section 22.

   -----------------------------------------------------------------------------
   CLASS VOCABULARY
   -----------------------------------------------------------------------------
   This file is the union of two vocabularies, on purpose:

     * the canonical one introduced here (.app/.sidebar/.panel/.matrix/.date-tile/
       .tier-row/.dialog/.toast/.chip/.issue/…), and
     * the vocabulary js/ui/shell.js and js/ui/dialogs.js already emit
       (.app-shell, .app-header, .nav-tab, .card, .banner, .modal__head,
       .toast__row, .role-glyph--Resident, .tile, .cellbtn, .daycard,
       .slotline, .ladder-row, …).

   Both are styled, in one identity, so nothing already written breaks. Where the
   two overlap the rule is grouped rather than duplicated. New UI code should
   prefer the canonical names.

   assets/mobile.css reads through to --surface / --fg / --border / --accent /
   --danger / --success / --info / --band-weekend / --band-holiday / … ; those
   tokens are defined in sections 1b and 1c so the phone pages inherit this
   palette instead of falling back to their own.

   -----------------------------------------------------------------------------
   CONTENTS
   -----------------------------------------------------------------------------
     1  Design tokens          10  Tables
     2  Reset & base           11  Person x day matrix
     3  Typography             12  Day-card calendar
     4  App shell              13  Person cards & report blocks
     5  Panels, cards, banners 14  Tier ladder
     6  Buttons                15  Date-tile grid
     7  Segmented control      16  Issues, stats, progress, empty states
     8  Forms                  17  Dialog / modal
     9  Chips, badges, roles   18  Toast
                               19  Utilities
                               20  Responsive
                               21  Reduced motion
                               22  Print
============================================================================= */


/* =============================================================================
   1. DESIGN TOKENS
   -----------------------------------------------------------------------------
   1a  PRIMITIVES — raw ramps, theme-independent, defined once.
   1b  SEMANTIC   — what components consume. These flip with the theme.
   1c  ALIASES    — legacy / cross-file names mapped onto the semantic layer.
   Components must never reach past the semantic layer into a primitive.
============================================================================= */

:root {

  /* --- 1a. PRIMITIVES ------------------------------------------------------ */

  /* Warm neutral ground. Paper, not screen-grey: every step carries a little
     yellow, so Hebrew sits on it the way ink sits on a printed rota. */
  --n-0:   #FFFFFF;
  --n-25:  #FBFAF8;   /* the paper */
  --n-50:  #F5F2ED;
  --n-100: #EDE9E2;
  --n-200: #DFD9CF;
  --n-300: #C9C1B4;
  --n-400: #A79E8F;
  --n-500: #847B6D;
  --n-600: #635C51;
  --n-700: #443F38;
  --n-800: #2A2723;
  --n-900: #1B1917;
  --n-950: #141311;   /* the dark paper */

  /* Deep clinical teal — the ONLY brand accent in the app. */
  --teal-50:  #E8F3F3;
  --teal-100: #C9E5E6;
  --teal-200: #9BD0D3;
  --teal-300: #63B4BA;
  --teal-400: #2F949C;
  --teal-500: #0F757C;
  --teal-600: #0B6B70;
  --teal-700: #085458;
  --teal-800: #063E42;
  --teal-900: #052A2D;

  /* Role hues. Every *-fg is >= 5:1 against the paper (the brief asked for 3:1);
     every *-tint sits near 90% lightness so the four pills are distinguishable
     at a glance — the old file's four >95% pills read as one grey. */
  --role-blue-fg:   #33517E;  --role-blue-tint:   #E4EAF5;  --role-blue-rule:   #B4C4E0;
  --role-ochre-fg:  #8A5300;  --role-ochre-tint:  #F7E9D2;  --role-ochre-rule:  #DEC397;
  --role-violet-fg: #6B3FA0;  --role-violet-tint: #EDE3F7;  --role-violet-rule: #CCB4E6;
  --role-green-fg:  #2F6B36;  --role-green-tint:  #DFEDDF;  --role-green-rule:  #ABC9AC;

  --role-blue-dk:   #9CB6EA;  --role-blue-tint-dk:   #1D2637;  --role-blue-rule-dk:   #2F3F5E;
  --role-ochre-dk:  #E2AC5C;  --role-ochre-tint-dk:  #332614;  --role-ochre-rule-dk:  #5A4320;
  --role-violet-dk: #C4A6EC;  --role-violet-tint-dk: #291F38;  --role-violet-rule-dk: #453563;
  --role-green-dk:  #8CC992;  --role-green-tint-dk:  #182A1B;  --role-green-rule-dk:  #2C4830;

  /* Attention. Exactly one saturated colour for pinned / conflicted / unfilled /
     below-floor. If you want a second one, you actually want a glyph.
     "Blocked" (I cannot work) is the same meaning — unavailable / stop — so it
     is drawn from this family and NOT given a hue of its own. */
  --attn-fg:      #B4301A;
  --attn-strong:  #92230F;
  --attn-tint:    #FBE7E1;
  --attn-rule:    #E7B7A8;
  --attn-fg-dk:   #FF9575;
  --attn-tint-dk: #38190F;
  --attn-rule-dk: #6E3020;

  /* Amber — caution, NOT error. See the --warn alias below for why this exists.
     Contrast checked against both grounds: #8A5300 on #FBFAF8 = 5.6:1,
     #F0B357 on #141311 = 9.1:1. */
  --caution-fg:      #8A5300;
  --caution-tint:    #FBF0DC;
  --caution-rule:    #E2C48A;
  --caution-fg-dk:   #F0B357;
  --caution-tint-dk: #33270F;
  --caution-rule-dk: #6B5322;

  /* Indigo. The ONE hue added beyond brand + attention + roles, and it exists
     for exactly one reason: a YA's committed date (CONTRACT H9/H10) is a hard
     pre-assignment, not a wish and not a problem, so it may borrow neither the
     brand (which means "wanted / selected") nor attention (which means "stop").
     Before this existed, --success and --info both collapsed onto the brand and
     "ימים מבוקשים" and "תאריכים שסוכמו" rendered pixel-identical on r.html. */
  --indigo-fg:      #55349F;
  --indigo-strong:  #4C2F91;
  --indigo-tint:    #EBE3FA;
  --indigo-rule:    #C3B2EE;
  --indigo-fg-dk:   #C0A9F5;
  --indigo-ink-dk:  #D3C2FB;
  --indigo-tint-dk: #241C3A;
  --indigo-rule-dk: #4A3A72;

  /* Weekend / holiday band. Warm sand — "a different kind of day", not an alert.
     Holiday is the same family, one step deeper. The dark values used to be one
     step off the dark surface (1.02:1 against it — invisible), which is why the
     published schedule was reaching for the attention tint instead. */
  --band-wk:    #F3ECE0;
  --band-hd:    #EDE1CD;
  --band-wk-dk: #2C2620;
  --band-hd-dk: #37301E;

  /* --- Type scale: 6 steps, ratio 1.25, base 1rem ---------------------------
     Hebrew has no capitals and no ascender/descender rhythm to lean on, so it
     needs more size and more leading than Latin at the same perceived density.
     Nothing in this app renders body text below 1rem. */
  --fs-100: 0.8rem;      /* 12.8px — micro labels, legends, column headers */
  --fs-200: 1rem;        /* 16px   — body, table cells, inputs, buttons    */
  --fs-300: 1.25rem;     /* 20px   — card and panel titles                 */
  --fs-400: 1.5625rem;   /* 25px   — section headings                      */
  --fs-500: 1.9531rem;   /* 31.3px — page title                            */
  --fs-600: 2.4414rem;   /* 39.1px — display numerals, the one big metric  */

  --lh-tight: 1.2;
  --lh-snug:  1.35;
  --lh-body:  1.55;

  --fw-normal: 400;
  --fw-medium: 500;
  --fw-bold:   700;

  --font-ui:  'Heebo', 'Assistant', 'Segoe UI', 'Arial Hebrew', 'David',
              system-ui, sans-serif;
  --font-num: 'Heebo', 'Segoe UI', system-ui, sans-serif;

  /* --- Space, radius, density ---------------------------------------------- */
  --sp-1: 0.25rem;
  --sp-2: 0.375rem;
  --sp-3: 0.5rem;
  --sp-4: 0.75rem;
  --sp-5: 1rem;
  --sp-6: 1.5rem;
  --sp-7: 2rem;
  --sp-8: 3rem;

  --r-1: 4px;
  --r-2: 8px;
  --r-3: 12px;
  --r-pill: 999px;

  --tap:    2.75rem;   /* 44px — minimum interactive target      */
  --tap-lg: 3rem;      /* 48px — date tiles, phone-first targets */
  --sidebar-w: 15rem;
  --matrix-name-w: 9.5rem;
  --matrix-col-w:  2.5rem;
  --page-max: 1500px;

  /* --- Motion -------------------------------------------------------------- */
  --ease: cubic-bezier(0.2, 0, 0.15, 1);
  --dur-1: 90ms;
  --dur-2: 160ms;
  --dur-3: 260ms;

  /* --- Role glyphs. Geometric, monochrome, present in every Hebrew font. ----- */
  --glyph-resident: '\25CF';  /* ● */
  --glyph-ya:       '\25B2';  /* ▲ */
  --glyph-yamh:     '\25C6';  /* ◆ */
  --glyph-adjunct:  '\25A0';  /* ■ */
  --glyph-intern:   '\25CB';  /* ○ */
  --glyph-empty:    '\2014';  /* — */
  --glyph-holiday:  '\2726';  /* ✦ */
  --glyph-low:      '\2193';  /* ↓ */

  /* Submission-state marks. Colour is never the only cue on a date tile: these
     are the same marks js/resident.js already prints on the phone grid, so the
     chief's board and the resident's form read identically in greyscale. */
  --glyph-blocked:      '\2715';  /* ✕ */
  --glyph-available:    '\2713';  /* ✓ */
  --glyph-request:      '\2606';  /* ☆ */
  --glyph-request-high: '\2605';  /* ★ */
  --glyph-commit:       '\25CF';  /* ● */

  /* --- Icon masks. Inlined SVG painted with currentColor: exact in dark mode,
         solid black on a photocopier, no network, no icon font. --------------- */
  --icon-lock: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%3E%3Cpath%20d='M7%2010V7a5%205%200%200%201%2010%200v3h1a2%202%200%200%201%202%202v7a2%202%200%200%201-2%202H6a2%202%200%200%201-2-2v-7a2%202%200%200%201%202-2h1zm2%200h6V7a3%203%200%200%200-6%200v3z'/%3E%3C/svg%3E");
  --icon-drag: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%3E%3Ccircle%20cx='9'%20cy='5'%20r='1.7'/%3E%3Ccircle%20cx='15'%20cy='5'%20r='1.7'/%3E%3Ccircle%20cx='9'%20cy='12'%20r='1.7'/%3E%3Ccircle%20cx='15'%20cy='12'%20r='1.7'/%3E%3Ccircle%20cx='9'%20cy='19'%20r='1.7'/%3E%3Ccircle%20cx='15'%20cy='19'%20r='1.7'/%3E%3C/svg%3E");
  --icon-pin: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%3E%3Cpath%20d='M12%202a6%206%200%200%200-6%206c0%204.4%206%2013%206%2013s6-8.6%206-13a6%206%200%200%200-6-6zm0%208.3A2.3%202.3%200%201%201%2012%205.7a2.3%202.3%200%200%201%200%204.6z'/%3E%3C/svg%3E");
  --icon-check: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%3E%3Cpath%20d='M9.6%2017.2%204.4%2012l1.6-1.6%203.6%203.6L18%205.6%2019.6%207.2z'/%3E%3C/svg%3E");
  --icon-chev: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%3E%3Cpath%20d='M12%2015.5%205.8%209.3%207.2%207.9%2012%2012.7l4.8-4.8%201.4%201.4z'/%3E%3C/svg%3E");
  --icon-close: url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%3E%3Cpath%20d='M18.3%207.1%2016.9%205.7%2012%2010.6%207.1%205.7%205.7%207.1%2010.6%2012l-4.9%204.9%201.4%201.4%204.9-4.9%204.9%204.9%201.4-1.4-4.9-4.9z'/%3E%3C/svg%3E");


  /* --- 1b. SEMANTIC TOKENS — LIGHT ----------------------------------------- */
  color-scheme: light;

  --paper:        var(--n-25);
  --surface:      var(--n-0);
  --surface-2:    var(--n-50);
  --surface-3:    var(--n-100);
  --surface-sunk: var(--n-50);

  --ink:         var(--n-900);
  --ink-2:       var(--n-700);
  --ink-3:       var(--n-500);
  --ink-inverse: var(--n-0);

  --rule:        var(--n-200);
  --rule-hair:   var(--n-100);
  --rule-strong: var(--n-300);

  --brand:          var(--teal-600);
  --brand-ink:      var(--teal-700);
  --brand-hover:    var(--teal-500);
  --brand-contrast: #FFFFFF;
  --brand-tint:     var(--teal-50);
  --brand-tint-2:   var(--teal-100);
  --focus:          var(--teal-500);

  --attn:      var(--attn-fg);
  --attn-ink:  var(--attn-strong);
  --attn-bg:   var(--attn-tint);
  --caution-ink: var(--caution-fg);
  --caution-bg:  var(--caution-tint);
  --attn-line: var(--attn-rule);

  --commit:     var(--indigo-fg);
  --commit-ink: var(--indigo-strong);
  --commit-bg:  var(--indigo-tint);
  --commit-line: var(--indigo-rule);

  /* --- SUBMISSION STATES ----------------------------------------------------
     The three things a person can say about a date. Each state owns exactly one
     fill / line / ink triple, and BOTH the tile and its legend swatch read the
     same three custom properties — that is the only reason they cannot drift.
     Never hard-code a colour on a tile or a swatch; add a state here instead.

       blocked   = "I cannot work"      -> attention (unavailable / stop)
       want      = "I want this"        -> brand teal (wanted / selected)
       commit    = "this is agreed"     -> indigo (binding, neither of the above)
  --------------------------------------------------------------------------- */
  --state-blocked:     var(--attn);
  --state-blocked-ink: var(--attn-ink);
  --state-blocked-bg:  var(--attn-bg);

  --state-want:     var(--brand);
  --state-want-ink: var(--brand-ink);
  --state-want-bg:  var(--brand-tint);

  --state-commit:     var(--commit);
  --state-commit-ink: var(--commit-ink);
  --state-commit-bg:  var(--commit-bg);

  --band-weekend: var(--band-wk);
  --band-holiday: var(--band-hd);

  --zebra: rgba(27, 25, 23, 0.028);
  --hover: rgba(27, 25, 23, 0.05);
  --press: rgba(27, 25, 23, 0.09);

  /* Elevation is for things that FLOAT — overlays, sticky edges. A panel is a
     1px rule and a surface, nothing more. */
  --shadow-sticky: 0 1px 0 rgba(27, 25, 23, 0.10);
  --shadow-pop:    0 4px 14px rgba(27, 25, 23, 0.10), 0 1px 2px rgba(27, 25, 23, 0.08);
  --shadow-modal:  0 24px 64px rgba(27, 25, 23, 0.22), 0 2px 8px rgba(27, 25, 23, 0.12);

  /* Default (roleless) values so no component ever renders an empty var(). */
  --role-fg:    var(--ink-2);
  --role-tint:  var(--surface-2);
  --role-rule:  var(--rule);
  --role-glyph: '';
}


/* --- 1b (cont.) SEMANTIC TOKENS — DARK ---------------------------------------
   Declared twice on purpose. There is no build step, so there is no way to share
   one declaration block between a media query and an attribute selector. Both
   copies must stay identical. The :not([data-theme='light']) guard lets an
   explicit light choice win on a dark OS; the attribute block comes last so an
   explicit dark choice wins on a light OS. That is the "wins in both directions"
   requirement, without depending on light-dark(), which fails closed (a custom
   property holding an unsupported function is invalid at computed-value time and
   takes the whole palette down with it on an older tablet).
--------------------------------------------------------------------------- */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    color-scheme: dark;

    --paper:        var(--n-950);
    --surface:      #1D1B18;
    --surface-2:    #24211D;
    --surface-3:    #2C2924;
    --surface-sunk: #191714;

    --ink:         #F2EEE7;
    --ink-2:       #CFC8BD;
    --ink-3:       #9C9488;
    --ink-inverse: var(--n-950);

    --rule:        #35312B;
    --rule-hair:   #292521;
    --rule-strong: #4C463D;

    --brand:          #5BC8CE;
    --brand-ink:      #7FD8DD;
    --brand-hover:    #7FD8DD;
    --brand-contrast: #04282B;
    --brand-tint:     #10312F;
    --brand-tint-2:   #14403F;
    --focus:          #74D4DA;

    --attn:      var(--attn-fg-dk);
    --attn-ink:  #FFB49B;
    --attn-bg:   var(--attn-tint-dk);
    --caution-ink: var(--caution-fg-dk);
    --caution-bg:  var(--caution-tint-dk);
    --attn-line: var(--attn-rule-dk);

    --commit:      var(--indigo-fg-dk);
    --commit-ink:  var(--indigo-ink-dk);
    --commit-bg:   var(--indigo-tint-dk);
    --commit-line: var(--indigo-rule-dk);

    --band-weekend: var(--band-wk-dk);
    --band-holiday: var(--band-hd-dk);

    --zebra: rgba(255, 250, 240, 0.032);
    --hover: rgba(255, 250, 240, 0.06);
    --press: rgba(255, 250, 240, 0.11);

    --shadow-sticky: 0 1px 0 rgba(0, 0, 0, 0.55);
    --shadow-pop:    0 4px 16px rgba(0, 0, 0, 0.5), 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow-modal:  0 24px 64px rgba(0, 0, 0, 0.66), 0 2px 8px rgba(0, 0, 0, 0.5);

    --role-fg:   var(--ink-2);
    --role-tint: var(--surface-2);
    --role-rule: var(--rule);
  }
}

:root[data-theme='dark'] {
  color-scheme: dark;

  --paper:        var(--n-950);
  --surface:      #1D1B18;
  --surface-2:    #24211D;
  --surface-3:    #2C2924;
  --surface-sunk: #191714;

  --ink:         #F2EEE7;
  --ink-2:       #CFC8BD;
  --ink-3:       #9C9488;
  --ink-inverse: var(--n-950);

  --rule:        #35312B;
  --rule-hair:   #292521;
  --rule-strong: #4C463D;

  --brand:          #5BC8CE;
  --brand-ink:      #7FD8DD;
  --brand-hover:    #7FD8DD;
  --brand-contrast: #04282B;
  --brand-tint:     #10312F;
  --brand-tint-2:   #14403F;
  --focus:          #74D4DA;

  --attn:      var(--attn-fg-dk);
  --attn-ink:  #FFB49B;
  --attn-bg:   var(--attn-tint-dk);
  --caution-ink: var(--caution-fg-dk);
  --caution-bg:  var(--caution-tint-dk);
  --attn-line: var(--attn-rule-dk);

  --commit:      var(--indigo-fg-dk);
  --commit-ink:  var(--indigo-ink-dk);
  --commit-bg:   var(--indigo-tint-dk);
  --commit-line: var(--indigo-rule-dk);

  --band-weekend: var(--band-wk-dk);
  --band-holiday: var(--band-hd-dk);

  --zebra: rgba(255, 250, 240, 0.032);
  --hover: rgba(255, 250, 240, 0.06);
  --press: rgba(255, 250, 240, 0.11);

  --shadow-sticky: 0 1px 0 rgba(0, 0, 0, 0.55);
  --shadow-pop:    0 4px 16px rgba(0, 0, 0, 0.5), 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-modal:  0 24px 64px rgba(0, 0, 0, 0.66), 0 2px 8px rgba(0, 0, 0, 0.5);

  --role-fg:   var(--ink-2);
  --role-tint: var(--surface-2);
  --role-rule: var(--rule);
}


/* --- 1c. ALIAS TOKENS --------------------------------------------------------
   Two other files already read token names that predate this palette:
     * assets/mobile.css     — --surface / --surface-raised / --fg / --fg-muted /
                               --border / --border-soft / --accent / --accent-fg /
                               --accent-soft / --danger / --success / --warn / --info
     * an earlier app.css    — --bg / --bg-elev / --bg-sunk / --ink-* / --line* /
                               --ok / --err / --pin / --weekend / --s-* / --shadow-*
   Aliasing them here means the phone pages and the existing shell inherit this
   design system instead of falling back to a second, unrelated palette.

   Note the deliberate flattening: --ok resolves to the BRAND, and
   --warn / --err / --danger all resolve to ATTENTION. That is rule (4) above —
   three meanings, not six.

   The ONE place the flattening was wrong: mobile.css builds three different
   meanings out of --danger (blocked), --success (requested) and --info
   (committed). Collapsing --success and --info onto the same brand teal made a
   YA's wish and a YA's binding commitment render pixel-identical on r.html.
   They are now bound to the three SUBMISSION STATE triples above, so the phone
   pages get exactly the three distinct hues their layout already assumes.
--------------------------------------------------------------------------- */

:root {
  --font: var(--font-ui);

  --bg:          var(--paper);
  --bg-elev:     var(--surface);
  --bg-sunk:     var(--surface-2);
  --line:        var(--rule);
  --line-strong: var(--rule-strong);

  --fg:            var(--ink);
  --fg-muted:      var(--ink-3);
  --surface-raised: var(--surface);
  --border:        var(--rule);
  --border-soft:   var(--rule-hair);

  --accent:      var(--brand);
  --accent-ink:  var(--brand-contrast);
  --accent-fg:   var(--brand-contrast);
  --accent-soft: var(--brand-tint);

  --ok:           var(--brand-ink);
  --ok-soft:      var(--brand-tint);

  /* "requested / wanted" — the brand, which already means wanted + selected. */
  --success:      var(--state-want-ink);
  --success-soft: var(--state-want-bg);
  /* "committed" — indigo. A hard pre-assignment is not a wish and not a fault. */
  --info:         var(--state-commit-ink);
  --info-soft:    var(--state-commit-bg);

  /* "warning" — amber. Distinct from attention on purpose: a soft warning and a
     hard error previously resolved to the identical ground, so on r.html a notice
     saying "you have fewer weekend groups than usual" was painted exactly like
     "this submission is invalid". Amber sits inside the attention FAMILY (warm,
     reads as caution) without being the error colour. */
  --warn:        var(--caution-ink);
  --warn-soft:   var(--caution-bg);
  --err:         var(--attn-ink);
  --err-soft:    var(--attn-bg);
  /* "blocked / cannot work" — attention, the same hue as an error, on purpose. */
  --danger:      var(--state-blocked-ink);
  --danger-soft: var(--state-blocked-bg);

  --pin:      var(--attn);
  --pin-soft: var(--attn-bg);

  --weekend:      var(--band-weekend);
  --weekend-line: var(--rule);
  --holiday:      var(--band-holiday);

  --s-1: var(--sp-1);
  --s-2: var(--sp-3);
  --s-3: var(--sp-4);
  --s-4: var(--sp-5);
  --s-5: var(--sp-6);
  --s-6: var(--sp-7);

  --shadow-1: var(--shadow-sticky);
  --shadow-2: var(--shadow-modal);
}


/* --- ROLE TOKEN SETS --------------------------------------------------------
   data-role on ANY element gives it its own colour and glyph. Every component
   below reads --role-fg / --role-tint / --role-rule / --role-glyph, so roles are
   added or changed in exactly one place: here.
   .role-glyph--X (emitted by shell.js) is treated as a synonym.
--------------------------------------------------------------------------- */

[data-role='Resident'], .role-glyph--Resident, .is-role-Resident {
  --role-fg: var(--role-blue-fg);
  --role-tint: var(--role-blue-tint);
  --role-rule: var(--role-blue-rule);
  --role-glyph: var(--glyph-resident);
}
[data-role='YA'], .role-glyph--YA, .is-role-YA {
  --role-fg: var(--role-ochre-fg);
  --role-tint: var(--role-ochre-tint);
  --role-rule: var(--role-ochre-rule);
  --role-glyph: var(--glyph-ya);
}
[data-role='YA-MH'], .role-glyph--YA-MH, .is-role-YA-MH {
  --role-fg: var(--role-violet-fg);
  --role-tint: var(--role-violet-tint);
  --role-rule: var(--role-violet-rule);
  --role-glyph: var(--glyph-yamh);
}
[data-role='Adjunct'], .role-glyph--Adjunct, .is-role-Adjunct {
  --role-fg: var(--role-green-fg);
  --role-tint: var(--role-green-tint);
  --role-rule: var(--role-green-rule);
  --role-glyph: var(--glyph-adjunct);
}
/* The weekday passive-ward placeholder (model.js INTERN). Deliberately
   colourless — it is not a person and must never compete with one. */
[data-role='intern'], .is-intern {
  --role-fg: var(--ink-3);
  --role-tint: transparent;
  --role-rule: var(--rule);
  --role-glyph: var(--glyph-intern);
}
[data-role='empty'] {
  --role-fg: var(--ink-3);
  --role-tint: transparent;
  --role-rule: var(--rule-hair);
  --role-glyph: var(--glyph-empty);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) :is([data-role='Resident'], .role-glyph--Resident, .is-role-Resident) {
    --role-fg: var(--role-blue-dk); --role-tint: var(--role-blue-tint-dk); --role-rule: var(--role-blue-rule-dk);
  }
  :root:not([data-theme='light']) :is([data-role='YA'], .role-glyph--YA, .is-role-YA) {
    --role-fg: var(--role-ochre-dk); --role-tint: var(--role-ochre-tint-dk); --role-rule: var(--role-ochre-rule-dk);
  }
  :root:not([data-theme='light']) :is([data-role='YA-MH'], .role-glyph--YA-MH, .is-role-YA-MH) {
    --role-fg: var(--role-violet-dk); --role-tint: var(--role-violet-tint-dk); --role-rule: var(--role-violet-rule-dk);
  }
  :root:not([data-theme='light']) :is([data-role='Adjunct'], .role-glyph--Adjunct, .is-role-Adjunct) {
    --role-fg: var(--role-green-dk); --role-tint: var(--role-green-tint-dk); --role-rule: var(--role-green-rule-dk);
  }
}
:root[data-theme='dark'] :is([data-role='Resident'], .role-glyph--Resident, .is-role-Resident) {
  --role-fg: var(--role-blue-dk); --role-tint: var(--role-blue-tint-dk); --role-rule: var(--role-blue-rule-dk);
}
:root[data-theme='dark'] :is([data-role='YA'], .role-glyph--YA, .is-role-YA) {
  --role-fg: var(--role-ochre-dk); --role-tint: var(--role-ochre-tint-dk); --role-rule: var(--role-ochre-rule-dk);
}
:root[data-theme='dark'] :is([data-role='YA-MH'], .role-glyph--YA-MH, .is-role-YA-MH) {
  --role-fg: var(--role-violet-dk); --role-tint: var(--role-violet-tint-dk); --role-rule: var(--role-violet-rule-dk);
}
:root[data-theme='dark'] :is([data-role='Adjunct'], .role-glyph--Adjunct, .is-role-Adjunct) {
  --role-fg: var(--role-green-dk); --role-tint: var(--role-green-tint-dk); --role-rule: var(--role-green-rule-dk);
}


/* =============================================================================
   2. RESET & BASE
============================================================================= */

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  scrollbar-color: var(--rule-strong) transparent;
}

body {
  min-block-size: 100vh;
  min-inline-size: 320px;
  padding: 0;
  background-color: var(--paper);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: var(--fs-200);
  font-weight: var(--fw-normal);
  line-height: var(--lh-body);
  /* Hebrew must never be letter-spaced. Said once, meant everywhere. */
  letter-spacing: normal;
  font-variant-numeric: lining-nums;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  /* The page itself never scrolls sideways at any width. Wide surfaces scroll
     inside their own container — see .table-scroll and .matrix-scroll. */
  overflow-x: hidden;
}

img, svg, video, canvas { display: block; max-inline-size: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }
h1, h2, h3, h4, p, ul, ol, figure, dl, dd { margin: 0; }
ul, ol { padding-inline-start: 1.25em; }

a {
  color: var(--brand-ink);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
}
a:hover { color: var(--brand-hover); }

hr, .hr {
  border: 0;
  block-size: 1px;
  background-color: var(--rule);
  margin-block: var(--sp-5);
}

[hidden] { display: none !important; }

::selection { background-color: var(--brand-tint-2); color: var(--ink); }

/* One focus treatment for the whole app: two pixels of brand teal, always
   outside the box so it is never clipped by a sticky cell's overflow. */
:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
:focus:not(:focus-visible) { outline: none; }

.noscript {
  padding: var(--sp-6);
  text-align: center;
  color: var(--ink-2);
}

/* Thin, unobtrusive scrollbars inside the dense surfaces. */
.table-scroll, .matrix-scroll, .scroll-y, .nav, .nav-tabs, .cand-list {
  scrollbar-width: thin;
  scrollbar-color: var(--rule-strong) transparent;
}
.table-scroll::-webkit-scrollbar,
.matrix-scroll::-webkit-scrollbar,
.scroll-y::-webkit-scrollbar { block-size: 10px; inline-size: 10px; }
.table-scroll::-webkit-scrollbar-thumb,
.matrix-scroll::-webkit-scrollbar-thumb,
.scroll-y::-webkit-scrollbar-thumb {
  background-color: var(--rule-strong);
  border-radius: var(--r-pill);
  border: 3px solid transparent;
  background-clip: content-box;
}


/* =============================================================================
   3. TYPOGRAPHY
============================================================================= */

h1, h2, h3, h4 {
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  letter-spacing: normal;
  text-wrap: balance;
  color: var(--ink);
}

h1 { font-size: var(--fs-500); }
h2 { font-size: var(--fs-400); }
h3 { font-size: var(--fs-300); }
h4 { font-size: var(--fs-200); }

p { text-wrap: pretty; max-inline-size: 68ch; }
p + p { margin-block-start: var(--sp-4); }

/* Caption / micro-label voice. Hierarchy at this size comes from weight and
   colour — never from text-transform (Hebrew has no case) or letter-spacing. */
.caption, .label-micro, .tiny {
  font-size: var(--fs-100);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  color: var(--ink-3);
}

.small { font-size: var(--fs-100); line-height: var(--lh-snug); }

.lede {
  font-size: var(--fs-300);
  line-height: var(--lh-snug);
  color: var(--ink-2);
  max-inline-size: 60ch;
}

.muted  { color: var(--ink-2); }
.dim    { color: var(--ink-3); }
.strong { font-weight: var(--fw-bold); }
.medium { font-weight: var(--fw-medium); }

/* Every number in this app is a measurement, so every number is tabular.
   Wrap standalone numerals in <bdi> inside RTL prose so they never re-order. */
.num, .tabular, td.num, th.num, .stat-value, time, bdi {
  font-family: var(--font-num);
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: 'tnum' 1;
}

/* Latin fragments (station codes, ids, tokens) inside Hebrew prose. */
.ltr { direction: ltr; unicode-bidi: isolate; text-align: start; }

.mono {
  font-family: ui-monospace, 'Cascadia Mono', 'Segoe UI Mono', Menlo, monospace;
  font-size: 0.9em;
  direction: ltr;
  unicode-bidi: isolate;
}

.sr-only {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* A quoted free-text note from a resident, shown verbatim (CONTRACT §2). */
.note-quote, .person-note {
  border-inline-start: 3px solid var(--rule-strong);
  padding: var(--sp-2) var(--sp-4);
  color: var(--ink-2);
  background-color: var(--surface-2);
  border-start-end-radius: var(--r-2);
  border-end-end-radius: var(--r-2);
  white-space: pre-wrap;
  font-size: var(--fs-100);
  line-height: var(--lh-body);
}


/* =============================================================================
   4. APP SHELL
   -----------------------------------------------------------------------------
   Two shells are supported, in one identity.

   (a) SIDEBAR SHELL — the canonical chief layout:
       <div class="app">
         <aside class="sidebar"> .brand + <nav class="nav"> + .sidebar-foot </aside>
         <div class="main">
           <header class="topbar"> h1 + .topbar-actions </header>
           <main class="view"> … </main>
         </div>
       </div>

   (b) HEADER + TABS SHELL — what js/ui/shell.js already emits:
       #app.app-shell > .app-shell-inner > (.app-header > .app-header__top +
       nav.nav-tabs) + .banner-stack + main.app-main
============================================================================= */

/* --- (a) sidebar shell ----------------------------------------------------- */

.app {
  display: grid;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  min-block-size: 100vh;
  min-block-size: 100dvh;
}

.sidebar {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
  padding: var(--sp-5) var(--sp-4);
  background-color: var(--surface);
  border-inline-end: 1px solid var(--rule);
  position: sticky;
  inset-block-start: 0;
  block-size: 100vh;
  block-size: 100dvh;
  overflow-y: auto;
  min-inline-size: 0;
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-inline-size: 0;
  font-weight: var(--fw-bold);
  font-size: var(--fs-300);
  line-height: var(--lh-tight);
}

.brand small,
.brand-sub {
  display: block;
  font-weight: var(--fw-normal);
  font-size: var(--fs-100);
  color: var(--ink-3);
  line-height: var(--lh-tight);
  margin-block-start: 2px;
}

.brand-mark {
  display: grid;
  place-items: center;
  inline-size: 2.25rem;
  block-size: 2.25rem;
  flex: none;
  border-radius: var(--r-2);
  background-color: var(--brand);
  color: var(--brand-contrast);
  font-weight: var(--fw-bold);
  font-size: var(--fs-200);
  line-height: 1;
}

.nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-inline-size: 0;
}

.nav-group-title {
  font-size: var(--fs-100);
  font-weight: var(--fw-medium);
  color: var(--ink-3);
  padding: var(--sp-4) var(--sp-3) var(--sp-2);
}

.nav-item {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  min-block-size: var(--tap);
  padding-inline: var(--sp-3);
  padding-block: var(--sp-2);
  border: 1px solid transparent;
  border-radius: var(--r-2);
  background-color: transparent;
  color: var(--ink-2);
  font-size: var(--fs-200);
  font-weight: var(--fw-medium);
  text-align: start;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
.nav-item:hover { background-color: var(--hover); color: var(--ink); }

.nav-item[aria-current='page'],
.nav-item.is-active {
  background-color: var(--brand-tint);
  border-color: var(--brand-tint-2);
  color: var(--brand-ink);
  font-weight: var(--fw-bold);
}

.nav-item .nav-count {
  margin-inline-start: auto;
  font-size: var(--fs-100);
  font-weight: var(--fw-bold);
  color: var(--ink-3);
}
.nav-item .nav-count.is-attn { color: var(--attn); }

.sidebar-foot {
  margin-block-start: auto;
  padding-block-start: var(--sp-4);
  border-block-start: 1px solid var(--rule-hair);
  display: grid;
  gap: var(--sp-3);
  font-size: var(--fs-100);
  color: var(--ink-3);
}

.main { min-inline-size: 0; display: flex; flex-direction: column; }

.topbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-5) var(--sp-6);
  background-color: var(--paper);
  border-block-end: 1px solid var(--rule);
  position: sticky;
  inset-block-start: 0;
  z-index: 20;
}
.topbar h1 { font-size: var(--fs-400); }

.topbar-meta { display: flex; align-items: baseline; gap: var(--sp-3); min-inline-size: 0; }
.topbar-actions { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-3); }

/* --- (b) header + tabs shell ----------------------------------------------- */

.app-shell,
.app-shell-inner {
  min-block-size: 100vh;
  display: flex;
  flex-direction: column;
  min-inline-size: 0;
}
.app-shell-inner { flex: 1; }

.app-header {
  position: sticky;
  inset-block-start: 0;
  z-index: 40;
  background-color: var(--surface);
  border-block-end: 1px solid var(--rule);
  /* A hairline, not a floating card. The header sits ON the paper. */
  box-shadow: var(--shadow-sticky);
}

.app-header__top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-6);
  max-inline-size: var(--page-max);
  margin-inline: auto;
  inline-size: 100%;
}

.header-spacer { flex: 1 1 auto; }

/* The month switcher. The single most consequential control in the app
   (CONTRACT §5: a month change archives and confirms), so it is the one thing
   in the header that carries brand colour. */
.month-pill {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-block-size: var(--tap);
  padding-inline: var(--sp-4);
  border: 1px solid var(--brand-tint-2);
  border-radius: var(--r-pill);
  background-color: var(--brand-tint);
  color: var(--brand-ink);
  font-weight: var(--fw-bold);
  cursor: pointer;
  white-space: nowrap;
  transition: background-color var(--dur-1) var(--ease);
}
.month-pill:hover { background-color: var(--brand-tint-2); }
.month-pill .dim { color: inherit; opacity: 0.7; }

.nav-tabs {
  display: flex;
  gap: var(--sp-1);
  padding-inline: var(--sp-6);
  max-inline-size: var(--page-max);
  margin-inline: auto;
  inline-size: 100%;
  overflow-x: auto;
  overscroll-behavior-inline: contain;
}

.nav-tab {
  appearance: none;
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  min-block-size: var(--tap);
  padding-block: var(--sp-2);
  padding-inline: var(--sp-4);
  border: 0;
  background-color: transparent;
  color: var(--ink-2);
  font-size: var(--fs-200);
  font-weight: var(--fw-medium);
  white-space: nowrap;
  cursor: pointer;
  /* The selected tab is marked by a solid rule on the block-end edge — the way
     a tab divider is drawn on a paper form, not a filled pill. */
  border-block-end: 3px solid transparent;
  transition: color var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}
.nav-tab:hover { color: var(--ink); border-block-end-color: var(--rule-strong); }

.nav-tab[aria-selected='true'] {
  color: var(--brand-ink);
  font-weight: var(--fw-bold);
  border-block-end-color: var(--brand);
}

.tab-badge {
  display: inline-grid;
  place-items: center;
  min-inline-size: 1.4em;
  block-size: 1.4em;
  padding-inline: 0.35em;
  border-radius: var(--r-pill);
  background-color: var(--attn);
  color: #FFFFFF;
  font-size: var(--fs-100);
  font-weight: var(--fw-bold);
  line-height: 1;
  font-variant-numeric: tabular-nums;
}
/* "warn" and "err" are the same attention hue at two weights — never a second
   colour. The warn badge is outlined, the error badge is filled. */
.tab-badge.is-warn {
  background-color: transparent;
  color: var(--attn-ink);
  box-shadow: inset 0 0 0 1.5px var(--attn);
}

.app-main {
  flex: 1 1 auto;
  inline-size: 100%;
  max-inline-size: var(--page-max);
  margin-inline: auto;
  padding: var(--sp-6);
  min-inline-size: 0;
}

.banner-stack {
  inline-size: 100%;
  max-inline-size: var(--page-max);
  margin-inline: auto;
  padding-inline: var(--sp-6);
  padding-block-start: var(--sp-4);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.banner-stack:empty { display: none; }

/* --- view host ------------------------------------------------------------- */

.view {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
  min-inline-size: 0;
  flex: 1;
}
.view.is-hidden { display: none; }

.view-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4);
  padding-block-end: var(--sp-2);
  border-block-end: 1px solid var(--rule-hair);
}
.view-head h2 { font-size: var(--fs-400); }

/* Plain single-column page — r.html (resident form), s.html (published rota). */
.page {
  max-inline-size: 46rem;
  margin-inline: auto;
  padding: var(--sp-6) var(--sp-5) var(--sp-8);
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
}
.page--wide { max-inline-size: 72rem; }

/* --- layout primitives (all logical) --------------------------------------- */

.stack   { display: flex; flex-direction: column; gap: var(--sp-4); min-inline-size: 0; }
.stack-2 { display: flex; flex-direction: column; gap: var(--sp-3); min-inline-size: 0; }
.stack-6 { display: flex; flex-direction: column; gap: var(--sp-6); min-inline-size: 0; }

.row, .cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3);
  min-inline-size: 0;
}
.cluster--end   { justify-content: flex-end; }
.cluster--split { justify-content: space-between; }
.row-end, .push-end { margin-inline-start: auto; }
.grow { flex: 1 1 auto; min-inline-size: 0; }

.cols, .grid-auto {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(17rem, 100%), 1fr));
  gap: var(--sp-4);
}
.cols-2 {
  display: grid;
  grid-template-columns: minmax(min(17rem, 100%), 22rem) minmax(0, 1fr);
  gap: var(--sp-5);
  align-items: start;
}
.grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(18rem, 100%), 1fr)); gap: var(--sp-4); }
.grid-3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr)); gap: var(--sp-4); }


/* =============================================================================
   5. PANELS, CARDS, BANNERS
   A panel is a rule and a surface. No shadow. Shadows are for things that float.
============================================================================= */

.panel, .card {
  background-color: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-3);
  min-inline-size: 0;
  box-shadow: none;
}

.card { padding: var(--sp-5); }
.card + .card { margin-block-start: var(--sp-4); }
.card--flat { border-color: var(--rule-hair); }

.card__title {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  margin-block-end: var(--sp-4);
}

/* Card "kinds" collapse onto the three meanings: brand for informational,
   attention for both warn and err. Attention never fills a whole card — it
   marks the inline-start spine, which reads at a glance and prints. */
.card--ok   { border-color: var(--brand-tint-2); border-inline-start: 3px solid var(--brand); }
.card--warn { border-inline-start: 3px solid var(--attn); }
.card--err  { border-color: var(--attn-line); border-inline-start: 3px solid var(--attn); background-color: var(--attn-bg); }

.panel-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  border-block-end: 1px solid var(--rule-hair);
}
.panel-head h2 { font-size: var(--fs-300); }
.panel-head h3 { font-size: var(--fs-200); }

.panel-title-group { display: grid; gap: 2px; min-inline-size: 0; }
.panel-body { padding: var(--sp-5); }
.panel-body--flush { padding: 0; }
.panel-body--tight { padding: var(--sp-4); }

.panel-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-block-start: 1px solid var(--rule-hair);
  background-color: var(--surface-2);
  border-end-start-radius: var(--r-3);
  border-end-end-radius: var(--r-3);
}

.panel--sunken { background-color: var(--surface-sunk); }
.panel--quiet  { background-color: transparent; border-style: dashed; }

.section { display: flex; flex-direction: column; gap: var(--sp-4); min-inline-size: 0; }

/* --- banners (shell.js setBanner) ------------------------------------------ */

.banner {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--rule);
  border-inline-start: 3px solid var(--rule-strong);
  border-radius: var(--r-2);
  background-color: var(--surface);
  font-size: var(--fs-200);
  line-height: var(--lh-snug);
  min-inline-size: 0;
}
.banner--ok   { border-inline-start-color: var(--brand); border-color: var(--brand-tint-2); background-color: var(--brand-tint); color: var(--brand-ink); }
.banner--warn { border-inline-start-color: var(--attn); color: var(--ink); }
.banner--err  { border-inline-start-color: var(--attn); border-color: var(--attn-line); background-color: var(--attn-bg); color: var(--attn-ink); }


/* =============================================================================
   6. BUTTONS
============================================================================= */

.btn {
  appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-block-size: var(--tap);
  padding-inline: var(--sp-4);
  padding-block: var(--sp-2);
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-2);
  background-color: var(--surface);
  color: var(--ink);
  font-size: var(--fs-200);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--dur-1) var(--ease),
              border-color var(--dur-1) var(--ease),
              color var(--dur-1) var(--ease);
}
.btn:hover:not(:disabled)  { background-color: var(--hover); }
.btn:active:not(:disabled) { background-color: var(--press); }

.btn--primary {
  background-color: var(--brand);
  border-color: var(--brand);
  color: var(--brand-contrast);
  font-weight: var(--fw-bold);
}
.btn--primary:hover:not(:disabled)  { background-color: var(--brand-hover); border-color: var(--brand-hover); }
.btn--primary:active:not(:disabled) { background-color: var(--brand-ink); border-color: var(--brand-ink); }

.btn--ghost { background-color: transparent; border-color: transparent; color: var(--ink-2); }
.btn--ghost:hover:not(:disabled) { background-color: var(--hover); color: var(--ink); }

.btn--quiet { background-color: transparent; border-color: var(--rule); color: var(--ink-2); }

.btn--danger { border-color: var(--attn-line); color: var(--attn-ink); background-color: var(--surface); }
.btn--danger:hover:not(:disabled) { background-color: var(--attn-bg); }

.btn--sm { min-block-size: 2rem; padding-inline: var(--sp-3); font-size: var(--fs-100); }
.btn--lg { min-block-size: var(--tap-lg); padding-inline: var(--sp-6); font-size: var(--fs-300); }
.btn--block { inline-size: 100%; }

.btn:disabled,
.btn[aria-disabled='true'] { cursor: not-allowed; opacity: 0.5; }

/* Icon-only button. Always needs an aria-label. */
.btn--icon { inline-size: var(--tap); min-inline-size: var(--tap); padding-inline: 0; }
.btn--icon.btn--sm { inline-size: 2rem; min-inline-size: 2rem; }

/* Mask-painted icon inside any control. */
.icon {
  inline-size: 1.15em;
  block-size: 1.15em;
  flex: none;
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center;  mask-position: center;
  -webkit-mask-size: contain;     mask-size: contain;
}
.icon--lock  { -webkit-mask-image: var(--icon-lock);  mask-image: var(--icon-lock); }
.icon--drag  { -webkit-mask-image: var(--icon-drag);  mask-image: var(--icon-drag); }
.icon--pin   { -webkit-mask-image: var(--icon-pin);   mask-image: var(--icon-pin); }
.icon--check { -webkit-mask-image: var(--icon-check); mask-image: var(--icon-check); }
.icon--chev  { -webkit-mask-image: var(--icon-chev);  mask-image: var(--icon-chev); }
.icon--close { -webkit-mask-image: var(--icon-close); mask-image: var(--icon-close); }

/* Joined button group. Radii are logical, so this is correct in RTL. */
.btn-group { display: inline-flex; }
.btn-group .btn { border-radius: 0; margin-inline-start: -1px; }
.btn-group .btn:first-child {
  border-start-start-radius: var(--r-2);
  border-end-start-radius: var(--r-2);
  margin-inline-start: 0;
}
.btn-group .btn:last-child {
  border-start-end-radius: var(--r-2);
  border-end-end-radius: var(--r-2);
}
.btn-group .btn:hover,
.btn-group .btn:focus-visible { position: relative; z-index: 1; }


/* =============================================================================
   7. SEGMENTED CONTROL
   <div class="segmented" role="group">
     <button type="button" aria-pressed="true">חודש</button> …
   </div>
============================================================================= */

.segmented {
  display: inline-flex;
  padding: 3px;
  gap: 2px;
  background-color: var(--surface-2);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  max-inline-size: 100%;
  overflow-x: auto;
}

.segmented > button,
.segmented > .segment {
  appearance: none;
  border: 0;
  background-color: transparent;
  color: var(--ink-2);
  font-size: var(--fs-200);
  font-weight: var(--fw-medium);
  min-block-size: calc(var(--tap) - 8px);
  padding-inline: var(--sp-4);
  border-radius: calc(var(--r-2) - 2px);
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
.segmented > button:hover { color: var(--ink); background-color: var(--hover); }

.segmented > button[aria-pressed='true'],
.segmented > button.is-active {
  background-color: var(--surface);
  color: var(--brand-ink);
  font-weight: var(--fw-bold);
  box-shadow: var(--shadow-sticky);
}

.segmented--block { display: flex; inline-size: 100%; }
.segmented--block > button { flex: 1; }


/* =============================================================================
   8. FORMS
============================================================================= */

.field { display: flex; flex-direction: column; gap: var(--sp-2); min-inline-size: 0; }

.field > label,
.field > .field-label {
  font-size: var(--fs-100);
  font-weight: var(--fw-bold);
  color: var(--ink-2);
  line-height: var(--lh-snug);
}

.field-hint { font-size: var(--fs-100); color: var(--ink-3); line-height: var(--lh-snug); }

.field-error {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-100);
  font-weight: var(--fw-medium);
  color: var(--attn-ink);
}

input[type='text'], input[type='search'], input[type='number'], input[type='date'],
input[type='month'], input[type='email'], input[type='password'], input[type='tel'],
input[type='url'], select, textarea, .input, .select, .textarea {
  inline-size: 100%;
  min-block-size: var(--tap);
  padding-inline: var(--sp-3);
  padding-block: var(--sp-2);
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-2);
  background-color: var(--surface);
  color: var(--ink);
  font-size: var(--fs-200);
  line-height: var(--lh-snug);
  transition: border-color var(--dur-1) var(--ease), background-color var(--dur-1) var(--ease);
}

input::placeholder, textarea::placeholder { color: var(--ink-3); opacity: 1; }

input:hover:not(:disabled), select:hover:not(:disabled), textarea:hover:not(:disabled) {
  border-color: var(--ink-3);
}
input:focus-visible, select:focus-visible, textarea:focus-visible { border-color: var(--focus); }

input:disabled, select:disabled, textarea:disabled {
  background-color: var(--surface-2);
  color: var(--ink-3);
  cursor: not-allowed;
}
input[readonly] { background-color: var(--surface-2); color: var(--ink-2); }

input[aria-invalid='true'], textarea[aria-invalid='true'], select[aria-invalid='true'] {
  border-color: var(--attn);
  background-color: var(--attn-bg);
}

textarea, .textarea { min-block-size: 6rem; resize: vertical; line-height: var(--lh-body); }

/* Numeric inputs (maxShifts, caps, seeds) read as measurements. */
input[type='number'] {
  font-variant-numeric: tabular-nums lining-nums;
  max-inline-size: 8rem;
}

/* Date / month inputs are LTR widgets inside an RTL page. Isolate them so the
   native picker does not mirror its own internal order. */
input[type='date'], input[type='month'] { direction: ltr; text-align: start; unicode-bidi: isolate; }

/* Select. A BARE <select> keeps its native arrow — the browser places that arrow
   on the correct side for the document direction on its own, and removing it
   without supplying a replacement (which is what `appearance: none` alone does)
   leaves an unmarked box. Only a select wrapped in .select-wrap gets the custom
   chevron, positioned with a pseudo-element because background-position has no
   logical keywords and would be pinned to the wrong edge in RTL. */
.select-wrap { position: relative; display: grid; min-inline-size: 0; }
.select-wrap > select,
.select-wrap > .select { appearance: none; padding-inline-end: 2.25rem; }
.select-wrap::after {
  content: '';
  position: absolute;
  inset-inline-end: var(--sp-3);
  inset-block-start: 50%;
  translate: 0 -50%;
  inline-size: 1.1rem;
  block-size: 1.1rem;
  background-color: var(--ink-3);
  -webkit-mask-image: var(--icon-chev); mask-image: var(--icon-chev);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center;  mask-position: center;
  -webkit-mask-size: contain;     mask-size: contain;
  pointer-events: none;
}

input[type='checkbox'], input[type='radio'] {
  inline-size: 1.15rem;
  block-size: 1.15rem;
  min-block-size: 0;
  accent-color: var(--brand);
  cursor: pointer;
  flex: none;
}

/* A tappable row wrapping a checkbox or radio: 44px target, whole row clickable. */
.check {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  min-block-size: var(--tap);
  padding-inline: var(--sp-3);
  padding-block: var(--sp-2);
  border: 1px solid transparent;
  border-radius: var(--r-2);
  cursor: pointer;
  font-size: var(--fs-200);
  line-height: var(--lh-snug);
}
.check:hover { background-color: var(--hover); }
.check-text { min-inline-size: 0; }

.check--boxed { border-color: var(--rule); background-color: var(--surface); display: flex; }
.check--boxed:has(input:checked) { border-color: var(--brand); background-color: var(--brand-tint); }

/* Switch. The knob travels along the INLINE axis, so it flips in RTL. */
.switch {
  appearance: none;
  position: relative;
  inline-size: 2.6rem;
  block-size: 1.5rem;
  border-radius: var(--r-pill);
  background-color: var(--rule-strong);
  border: 0;
  cursor: pointer;
  flex: none;
  transition: background-color var(--dur-2) var(--ease);
}
.switch::before {
  content: '';
  position: absolute;
  inset-block-start: 3px;
  inset-inline-start: 3px;
  inline-size: 1.1rem;
  block-size: 1.1rem;
  border-radius: 50%;
  background-color: var(--surface);
  box-shadow: var(--shadow-sticky);
  transition: inset-inline-start var(--dur-2) var(--ease);
}
.switch:checked { background-color: var(--brand); }
.switch:checked::before { inset-inline-start: calc(100% - 1.1rem - 3px); }

.fieldset {
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  padding: var(--sp-4);
  min-inline-size: 0;
}
.fieldset > legend {
  padding-inline: var(--sp-2);
  font-size: var(--fs-100);
  font-weight: var(--fw-bold);
  color: var(--ink-2);
}

.form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(15rem, 100%), 1fr));
  gap: var(--sp-4);
}

.form-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--sp-3);
  margin-block-start: var(--sp-5);
}

/* Search field with a leading (inline-start) affordance. */
.search { position: relative; display: grid; min-inline-size: 0; }
.search > input { padding-inline-start: 2.25rem; }
.search::before {
  content: '';
  position: absolute;
  inset-inline-start: var(--sp-3);
  inset-block-start: 50%;
  translate: 0 -50%;
  inline-size: 1.05rem;
  block-size: 1.05rem;
  border: 2px solid var(--ink-3);
  border-radius: 50%;
  pointer-events: none;
}


/* =============================================================================
   9. CHIPS, BADGES, ROLE GLYPHS
============================================================================= */

/* The role glyph. Small, geometric, always drawn — this is what keeps the role
   legible in a greyscale photocopy and for a deuteranope. */
.role-dot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 1em;
  color: var(--role-fg);
  font-size: 0.72em;
  line-height: 1;
  flex: none;
}
.role-dot::before { content: var(--role-glyph); }

/* shell.js's roleGlyph() renders a Hebrew initial (מ / י / נ / ס) in a disc.
   That is also a glyph, so it is kept — tinted from the role tokens. */
.role-glyph {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 1.6rem;
  block-size: 1.6rem;
  flex: none;
  border-radius: 50%;
  background-color: var(--role-tint);
  color: var(--role-fg);
  border: 1px solid var(--role-rule);
  font-size: var(--fs-100);
  font-weight: var(--fw-bold);
  line-height: 1;
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding-inline: var(--sp-3);
  padding-block: 2px;
  min-block-size: 1.6rem;
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  background-color: var(--surface);
  color: var(--ink-2);
  font-size: var(--fs-100);
  font-weight: var(--fw-medium);
  line-height: var(--lh-snug);
  white-space: nowrap;
  max-inline-size: 100%;
  cursor: default;
}
.chip > span { overflow: hidden; text-overflow: ellipsis; }

button.chip { cursor: pointer; min-block-size: 2rem; }
button.chip:hover { background-color: var(--hover); }

/* Role chip. One rule, four roles — the colour comes from data-role. */
.chip--role {
  background-color: var(--role-tint);
  border-color: var(--role-rule);
  color: var(--role-fg);
  font-weight: var(--fw-bold);
}
.chip--role::before { content: var(--role-glyph); font-size: 0.72em; line-height: 1; }

.chip--brand, .chip--on, .chip--ok {
  background-color: var(--brand-tint);
  border-color: var(--brand-tint-2);
  color: var(--brand-ink);
}
.chip--attn, .chip--err, .chip--pin {
  background-color: var(--attn-bg);
  border-color: var(--attn-line);
  color: var(--attn-ink);
  font-weight: var(--fw-bold);
}
/* warn = the attention hue, outline only. */
.chip--warn { background-color: transparent; border-color: var(--attn-line); color: var(--attn-ink); }
.chip--plain { background-color: var(--surface-2); border-color: transparent; }
.chip--count { padding-inline: var(--sp-2); min-inline-size: 1.6rem; justify-content: center; }

.badge {
  display: inline-block;
  border-radius: var(--r-1);
  padding-inline: var(--sp-2);
  background-color: var(--surface-2);
  color: var(--ink-2);
  font-size: var(--fs-100);
  font-weight: var(--fw-medium);
  font-variant-numeric: tabular-nums;
}
.badge--ok   { background-color: var(--brand-tint); color: var(--brand-ink); }
.badge--warn { background-color: transparent; color: var(--attn-ink); box-shadow: inset 0 0 0 1px var(--attn-line); }
.badge--err  { background-color: var(--attn-bg); color: var(--attn-ink); }

/* Removable chip (a selected date, a pair member). */
.chip-remove {
  appearance: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  inline-size: 1.1rem;
  block-size: 1.1rem;
  opacity: 0.65;
  background-color: currentColor;
  -webkit-mask-image: var(--icon-close); mask-image: var(--icon-close);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center;  mask-position: center;
  -webkit-mask-size: contain;     mask-size: contain;
}
.chip-remove:hover { opacity: 1; }

/* --- legend ----------------------------------------------------------------
   The printed key that makes the glyph system self-documenting. It belongs at
   the foot of every calendar and matrix view, and it PRINTS. */
.legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3) var(--sp-5);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--rule-hair);
  border-radius: var(--r-2);
  background-color: var(--surface-2);
  font-size: var(--fs-100);
  color: var(--ink-2);
}
.legend > span,
.legend-item { display: inline-flex; align-items: center; gap: var(--sp-2); }

/* A legend swatch is a REPLICA of the thing it describes, never an approximation.
   Every swatch below therefore paints from the exact same custom property as the
   tile / cell it stands for — see the SUBMISSION STATES block in section 1b.
   The previous file painted .swatch--blocked from --attn-bg while .tile--blocked
   painted itself brand teal, so the key contradicted the grid it sat under. */
.swatch, .legend-swatch {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 1rem;
  block-size: 1rem;
  flex: none;
  border-radius: var(--r-1);
  background-color: var(--role-tint);
  border: 1px solid var(--role-rule);
  color: var(--role-fg);
  font-size: 0.6rem;
  line-height: 1;
}
/* A role swatch carries the role's glyph, exactly like every other role mark, so
   the four role keys stay separable in a greyscale print and for a deuteranope. */
.swatch[data-role]::before, .legend-swatch[data-role]::before { content: var(--role-glyph); }

.legend-swatch--weekend, .swatch--weekend { background-color: var(--band-weekend); border-color: var(--rule); }
.legend-swatch--holiday, .swatch--holiday { background-color: var(--band-holiday); border-color: var(--rule); }
.legend-swatch--attn { background-color: var(--attn-bg); border-color: var(--attn); }

.swatch--blocked   { background-color: var(--state-blocked-bg); border-color: var(--state-blocked);   color: var(--state-blocked-ink); }
.swatch--requested { background-color: var(--state-want-bg);    border-color: var(--state-want);      color: var(--state-want-ink); }
.swatch--committed { background-color: var(--state-commit-bg);  border-color: var(--state-commit);    color: var(--state-commit-ink); }
/* The same non-colour marks the tiles carry, so the key survives greyscale. */
.swatch--blocked::before   { content: var(--glyph-blocked); }
.swatch--requested::before { content: var(--glyph-request); }
.swatch--committed::before { content: var(--glyph-commit); }

.swatch--readonly  { background-color: var(--surface-2); border-style: dashed; }


/* =============================================================================
   10. TABLES
   <div class="table-scroll"><table class="table"> … </table></div>
   The scroller is width-constrained, so wide tables scroll INSIDE it and the
   page never scrolls sideways. No min-width is imposed — the old file's
   `min-width: 760px` inside a scroller is what made every table on a phone a
   two-handed operation. Give columns widths through <col> if a table needs them.
============================================================================= */

.table-scroll {
  overflow-x: auto;
  overscroll-behavior-inline: contain;
  border: 1px solid var(--rule);
  border-radius: var(--r-3);
  background-color: var(--surface);
  max-inline-size: 100%;
}
.panel-body--flush > .table-scroll { border: 0; border-radius: 0; }

.table {
  inline-size: 100%;
  border-collapse: collapse;
  background-color: var(--surface);
  font-size: var(--fs-200);
}

.table th, .table td {
  text-align: start;
  vertical-align: top;
  padding: var(--sp-3) var(--sp-4);
  border-block-end: 1px solid var(--rule-hair);
  line-height: var(--lh-snug);
}

.table thead th {
  position: sticky;
  inset-block-start: 0;
  z-index: 2;
  background-color: var(--surface-2);
  color: var(--ink-2);
  font-size: var(--fs-100);
  font-weight: var(--fw-bold);
  white-space: nowrap;
  border-block-end: 1px solid var(--rule);
  /* No text-transform: Hebrew has no case, and uppercasing it only breaks the
     shaping of the Latin station codes mixed into these headers. */
}

.table tbody tr:last-child td { border-block-end: 0; }
.table tbody tr:hover td { background-color: var(--hover); }
.table--zebra tbody tr:nth-child(even) td { background-color: var(--zebra); }

.table--compact th, .table--compact td { padding: var(--sp-2) var(--sp-3); font-size: var(--fs-100); }

.table td.num, .table th.num { text-align: end; font-variant-numeric: tabular-nums lining-nums; }

.table .cell-actions { display: flex; gap: var(--sp-2); justify-content: flex-end; white-space: nowrap; }

/* Row-level attention: a thick inline-start marker plus a tint, never a red row. */
.table tbody tr.is-attn td,
.table tbody tr.is-conflict td { background-color: var(--attn-bg); }
.table tbody tr.is-attn td:first-child,
.table tbody tr.is-conflict td:first-child { box-shadow: inset 3px 0 0 0 var(--attn); }
.table tbody tr.is-inactive td { opacity: 0.55; }

.table th[aria-sort] { cursor: pointer; user-select: none; }
.table th[aria-sort='ascending']::after  { content: ' \2191'; color: var(--brand); }
.table th[aria-sort='descending']::after { content: ' \2193'; color: var(--brand); }

/* --- selectable list rows (people roster, drafts, archives) ---------------- */

.list { display: flex; flex-direction: column; gap: var(--sp-1); }

.list-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  inline-size: 100%;
  min-block-size: var(--tap);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  background-color: var(--surface);
  text-align: start;
  cursor: pointer;
  min-inline-size: 0;
}
.list-row:hover { background-color: var(--hover); }
.list-row.is-active { border-color: var(--brand); background-color: var(--brand-tint); }
.list-row.is-inactive { opacity: 0.55; }
.list-row__main { flex: 1 1 auto; min-inline-size: 0; }
.list-row__name { font-weight: var(--fw-medium); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.list-row__sub  { font-size: var(--fs-100); color: var(--ink-3); }

/* --- key/value description list -------------------------------------------- */

.kv {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--sp-1) var(--sp-4);
  font-size: var(--fs-100);
  align-items: baseline;
}
.kv dt { color: var(--ink-3); }
.kv dd { margin: 0; font-weight: var(--fw-medium); }

/* --- diff rows (explain.js diffDrafts) ------------------------------------- */

.diff-row {
  display: grid;
  grid-template-columns: 6rem 8rem minmax(0, 1fr);
  gap: var(--sp-3);
  padding-block: var(--sp-2);
  border-block-end: 1px dashed var(--rule);
  font-size: var(--fs-100);
  align-items: baseline;
}
.diff-row:last-child { border-block-end: 0; }


/* =============================================================================
   11. PERSON x DAY MATRIX
   -----------------------------------------------------------------------------
   The densest surface in the app: 29 people down, 31 days across. Both headers
   stick. The scroller owns BOTH axes deliberately — a container that scrolls on
   one axis only cannot keep the other axis's header pinned — so it is given a
   bounded block-size and the page behind it stays perfectly still.

   <div class="matrix-scroll">
     <table class="matrix">
       <colgroup>
         <col class="matrix-col-name">
         <col><col class="is-weekend"><col class="is-holiday">…   one per day
       </colgroup>
       <thead><tr>
         <th class="matrix-corner corner">שם</th>
         <th class="matrix-day" data-band="weekend">
           <span class="matrix-dow">ו׳</span><span class="matrix-dom num">3</span>
         </th> …
       </tr></thead>
       <tbody><tr>
         <th class="matrix-rowhead rowhead" scope="row" data-role="Resident">
           <div class="matrix-name">
             <span class="role-dot"></span><span class="matrix-person">שירן גבאי</span>
             <span class="matrix-total num">7</span>
           </div>
         </th>
         <td class="matrix-cell mcell"><span class="cell-chip" data-role="Resident">מ״א</span></td>
       </tr></tbody>
     </table>
   </div>

   NOTE the split between .matrix-rowhead (the sticky <th>, which must remain a
   table cell — putting display:flex on a <th> takes it out of the table box
   model and sticky positioning stops tracking the column) and .matrix-name (a
   plain <div> inside it that does the flex layout).

   Weekend/holiday bands are painted by the <col>. Body-cell backgrounds must
   therefore stay TRANSPARENT — the zebra uses a translucent background-image so
   the band shows through it, and only the attention states paint an opaque cell.
============================================================================= */

.matrix-scroll {
  overflow: auto;
  overscroll-behavior: contain;
  max-block-size: min(70vh, 44rem);
  max-inline-size: 100%;
  border: 1px solid var(--rule);
  border-radius: var(--r-3);
  background-color: var(--surface);
}

.matrix, table.matrix {
  border-collapse: separate;
  border-spacing: 0;
  inline-size: max-content;
  min-inline-size: 100%;
  font-size: var(--fs-100);
  background-color: var(--surface);
}

.matrix col.matrix-col-name { inline-size: var(--matrix-name-w); }
.matrix col.is-weekend { background-color: var(--band-weekend); }
.matrix col.is-holiday { background-color: var(--band-holiday); }

.matrix th, .matrix td {
  padding: 0;
  border-block-end: 1px solid var(--rule-hair);
  border-inline-end: 1px solid var(--rule-hair);
  background-clip: padding-box;
  text-align: center;
  white-space: nowrap;
}

.matrix thead th {
  position: sticky;
  inset-block-start: 0;
  z-index: 2;
  background-color: var(--surface-2);
  border-block-end: 1px solid var(--rule-strong);
  font-weight: var(--fw-medium);
  color: var(--ink-2);
}

.matrix-day {
  inline-size: var(--matrix-col-w);
  min-inline-size: var(--matrix-col-w);
  padding-block: var(--sp-2);
  line-height: 1.15;
}
.matrix-dow { display: block; font-size: 0.72rem; color: var(--ink-3); }
.matrix-dom { display: block; font-size: var(--fs-100); font-weight: var(--fw-bold); color: var(--ink); }

.matrix thead th.is-weekend,
.matrix-day[data-band='weekend'] { background-color: var(--band-weekend); color: var(--ink); }
.matrix thead th.is-holiday,
.matrix-day[data-band='holiday'] { background-color: var(--band-holiday); color: var(--ink); }
.matrix thead th.is-holiday .matrix-dom::after,
.matrix-day[data-band='holiday'] .matrix-dom::after {
  content: var(--glyph-holiday);
  font-size: 0.6em;
  vertical-align: super;
  margin-inline-start: 1px;
  color: var(--ink-3);
}

/* Week separator: a heavier rule every seventh column keeps the eye tracking
   across 31 columns. The UI adds .is-week-start to the first cell of each week. */
.matrix th.is-week-start,
.matrix td.is-week-start { border-inline-start: 2px solid var(--rule-strong); }

/* --- sticky person column --- */

/* The sticky cells themselves. These stay table-cells — display:flex on a <th>
   removes it from the table box model and sticky stops tracking the column. */
.matrix-corner, .matrix-rowhead, .matrix th.rowhead, .matrix thead th.corner {
  position: sticky;
  inset-inline-start: 0;
  z-index: 3;
  background-color: var(--surface);
  border-inline-end: 1px solid var(--rule-strong);
  inline-size: var(--matrix-name-w);
  min-inline-size: var(--matrix-name-w);
  max-inline-size: var(--matrix-name-w);
  text-align: start;
  padding: var(--sp-2) var(--sp-3);
  font-weight: var(--fw-medium);
  color: var(--ink);
}

.matrix-corner, .matrix thead th.corner {
  z-index: 4;
  background-color: var(--surface-2);
  font-size: var(--fs-100);
  font-weight: var(--fw-bold);
  color: var(--ink-2);
}

/* The flex row INSIDE the sticky cell. */
.matrix-name {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-block-size: 1.6rem;
  min-inline-size: 0;
}

.matrix-person {
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fs-100);
}

.matrix-total, .matrix-name__total {
  margin-inline-start: auto;
  font-size: var(--fs-100);
  font-weight: var(--fw-bold);
  color: var(--ink-3);
  flex: none;
  font-variant-numeric: tabular-nums;
}
.matrix-total.is-attn { color: var(--attn); }

/* --- body cells --- */

.matrix-cell, td.mcell {
  inline-size: var(--matrix-col-w);
  min-inline-size: var(--matrix-col-w);
  block-size: 2.1rem;
  vertical-align: middle;
  cursor: pointer;
}
.matrix-cell:hover, td.mcell:hover { background-image: linear-gradient(var(--hover), var(--hover)); }

/* Zebra as a translucent overlay so the weekend column band shows through. */
.matrix tbody tr:nth-child(even) .matrix-cell,
.matrix tbody tr:nth-child(even) td.mcell,
.matrix tbody tr:nth-child(even) .matrix-rowhead,
.matrix tbody tr:nth-child(even) th.rowhead {
  background-image: linear-gradient(var(--zebra), var(--zebra));
}

/* Legacy per-cell weekend flag, for callers that do not emit <col>. */
td.mcell.is-weekend { background-color: var(--band-weekend); }

/* The assignment itself — a short station code in the person's role colour. */
.cell-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-inline-size: 1.9rem;
  block-size: 1.5rem;
  padding-inline: var(--sp-1);
  border-radius: var(--r-1);
  background-color: var(--role-tint);
  color: var(--role-fg);
  border: 1px solid var(--role-rule);
  font-size: 0.72rem;
  font-weight: var(--fw-bold);
  line-height: 1;
}
.cell-chip::before { content: var(--role-glyph); font-size: 0.62em; opacity: 0.85; }
.cell-chip--bare { border-color: transparent; background-color: transparent; }

/* A cell rendered as a button (legacy .cellbtn). */
.cellbtn {
  appearance: none;
  border: 0;
  background-color: transparent;
  inline-size: 100%;
  block-size: 100%;
  min-inline-size: var(--matrix-col-w);
  min-block-size: 2rem;
  padding: 2px;
  cursor: pointer;
  font-size: 0.75rem;
  font-weight: var(--fw-medium);
  color: var(--ink-2);
}
.cellbtn:hover { background-color: var(--hover); }
.cellbtn.is-on {
  background-color: var(--role-tint);
  color: var(--role-fg);
  font-weight: var(--fw-bold);
  box-shadow: inset 0 0 0 1px var(--role-rule);
}
.cellbtn.is-intern { color: var(--ink-3); font-weight: var(--fw-normal); }
.cellbtn.is-pinned { box-shadow: inset 0 0 0 2px var(--attn); }
.cellbtn.is-flagged { background-color: var(--attn-bg); box-shadow: inset 0 0 0 2px var(--attn); }

/* Unavailable / blocked day for this person: hatched, not coloured. Hatching is
   the one texture in the system, and it is reserved for "cannot". */
.matrix-cell.is-off, td.mcell.is-off {
  background-image: repeating-linear-gradient(45deg, transparent 0 4px, var(--rule-hair) 4px 5px);
  cursor: default;
}

/* --- attention states (the ONE saturated colour) --- */

.matrix-cell.is-pinned .cell-chip { box-shadow: inset 0 0 0 2px var(--attn); }

.matrix-cell.is-conflict, .matrix-cell.is-unfilled,
td.mcell.is-empty {
  background-color: var(--attn-bg);
  box-shadow: inset 0 0 0 2px var(--attn);
}
.matrix-cell.is-unfilled::after { content: var(--glyph-empty); color: var(--attn); font-weight: var(--fw-bold); }

/* A date this person asked for (S4). Understated — a corner tick in the brand
   colour, never a fill, because a request is a wish and not a state. */
.matrix-cell.is-requested .cell-chip { box-shadow: inset 0 0 0 1px var(--state-want); }
.matrix-cell.is-requested::after {
  content: '';
  position: relative;
  display: block;
  inline-size: 0;
  block-size: 0;
  margin-inline-start: auto;
  margin-block-start: -1.4rem;
  border-inline-end: 6px solid var(--state-want);
  border-block-end: 6px solid transparent;
}

.matrix-rowhead.is-below-floor, .matrix th.rowhead.is-below-floor { color: var(--attn-ink); }
.matrix-rowhead.is-below-floor .matrix-name::after,
.matrix th.rowhead.is-below-floor .matrix-name::after,
.matrix-name.is-below-floor::after {
  content: var(--glyph-low);
  color: var(--attn);
  font-weight: var(--fw-bold);
  flex: none;
}

/* Row / column peer highlight while hovering a header. */
.matrix .is-peer { background-image: linear-gradient(var(--hover), var(--hover)); }
.matrix tbody tr:hover th.rowhead,
.matrix tbody tr:hover .matrix-rowhead { background-color: var(--surface-2); }


/* =============================================================================
   12. DAY-CARD CALENDAR
   -----------------------------------------------------------------------------
   Seven columns. dir="rtl" puts the first column on the inline-start edge, which
   in Hebrew is the right — exactly where a Hebrew wall calendar puts Sunday. No
   extra work, and no `direction` override anywhere.
============================================================================= */

.calendar, .daycards {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: var(--sp-2);
  align-items: stretch;
  min-inline-size: 0;
}

.cal-dow, .daycards .dow-head {
  padding-block: var(--sp-2);
  text-align: center;
  font-size: var(--fs-100);
  font-weight: var(--fw-bold);
  color: var(--ink-3);
}

.day-card, .daycard {
  display: flex;
  flex-direction: column;
  min-inline-size: 0;
  min-block-size: 8rem;
  background-color: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  overflow: hidden;
}
.day-card.is-blank, .daycard--blank {
  background-color: transparent;
  border-style: dashed;
  border-color: var(--rule-hair);
  min-block-size: 0;
}

/* Meaning (2): weekend / holiday is a tinted BAND across the whole card. */
.day-card[data-behavior='fridayLike'],
.day-card[data-behavior='saturdayLike'],
.daycard--weekend { background-color: var(--band-weekend); }
.day-card[data-holiday], .daycard--holiday { background-color: var(--band-holiday); }

.day-head, .daycard__head {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-3);
  border-block-end: 1px solid var(--rule-hair);
  background-color: var(--surface-2);
}
.day-card[data-behavior='fridayLike'] .day-head,
.day-card[data-behavior='saturdayLike'] .day-head,
.daycard--weekend .daycard__head { background-color: var(--band-holiday); }

.day-num, .daycard__num {
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
  line-height: 1;
  font-variant-numeric: tabular-nums lining-nums;
}
.day-dow, .daycard__dow { font-size: var(--fs-100); color: var(--ink-3); }

.day-holiday, .daycard__holiday {
  margin-inline-start: auto;
  font-size: var(--fs-100);
  font-weight: var(--fw-medium);
  color: var(--ink-2);
}
.day-holiday::before, .daycard__holiday::before { content: var(--glyph-holiday) ' '; color: var(--ink-3); }

.day-ward {
  padding: var(--sp-1) var(--sp-3);
  font-size: var(--fs-100);
  color: var(--ink-3);
  border-block-end: 1px solid var(--rule-hair);
}

.day-slots { display: flex; flex-direction: column; }

/* One station row. The grid keeps the seven station labels in a hard column, so
   the eye reads down a card exactly the way it reads down a printed rota. */
.slot-row, .slotline {
  display: grid;
  grid-template-columns: 3.6rem minmax(0, 1fr);
  align-items: center;
  gap: var(--sp-2);
  min-block-size: 2rem;
  inline-size: 100%;
  padding: var(--sp-1) var(--sp-3);
  border: 0;
  border-block-end: 1px solid var(--rule-hair);
  background-color: transparent;
  text-align: start;
  font: inherit;
  font-size: var(--fs-100);
  color: var(--ink);
  cursor: pointer;
}
.slot-row:last-child, .slotline:last-child { border-block-end: 0; }
.slot-row:hover, .slotline:hover { background-color: var(--hover); }

.slot-station, .slotline__st {
  font-size: var(--fs-100);
  font-weight: var(--fw-medium);
  color: var(--ink-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.slot-station.is-active,
.slotline.is-active-ward .slotline__st { color: var(--ink-2); font-weight: var(--fw-bold); }

.slot-person, .slotline__who {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-inline-size: 0;
  font-size: var(--fs-100);
  font-weight: var(--fw-medium);
  color: var(--role-fg);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.slot-person > .slot-name { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.slot-person::before { content: var(--role-glyph); font-size: 0.7em; line-height: 1; flex: none; }

.slot-row.is-intern .slot-person { color: var(--ink-3); font-weight: var(--fw-normal); }

.slot-row.is-unfilled, .slotline.is-empty {
  background-color: var(--attn-bg);
  box-shadow: inset 3px 0 0 0 var(--attn);
}
.slot-row.is-unfilled .slot-person,
.slotline.is-empty .slotline__who { color: var(--attn-ink); font-weight: var(--fw-bold); }

.slot-row.is-pinned .slot-person::after {
  content: '';
  inline-size: 0.75em;
  block-size: 0.75em;
  flex: none;
  background-color: var(--attn);
  -webkit-mask-image: var(--icon-pin); mask-image: var(--icon-pin);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: contain;     mask-size: contain;
}
.slotline.is-pinned { box-shadow: inset 3px 0 0 0 var(--attn); }

.slot-row.is-conflict { box-shadow: inset 3px 0 0 0 var(--attn); }
.slot-row.is-selected { background-color: var(--brand-tint); box-shadow: inset 3px 0 0 0 var(--brand); }


/* =============================================================================
   13. PERSON CARDS & REPORT BLOCKS
============================================================================= */

.person-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(19rem, 100%), 1fr));
  gap: var(--sp-4);
}

.person-card {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-4);
  background-color: var(--surface);
  border: 1px solid var(--rule);
  /* The role reads as a spine down the inline-start edge — visible at a glance
     in a list of 29, and it survives a black-and-white print as a grey bar plus
     the glyph beside the name. */
  border-inline-start: 3px solid var(--role-rule);
  border-radius: var(--r-2);
  min-inline-size: 0;
}
.person-card.is-inactive { opacity: 0.6; border-style: dashed; }

.person-head, .person-card__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  min-inline-size: 0;
}

.person-name, .person-card__name {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--fs-200);
  font-weight: var(--fw-bold);
  min-inline-size: 0;
}
.person-name > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.person-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  font-size: var(--fs-100);
  color: var(--ink-3);
}

.person-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(4.5rem, 1fr));
  gap: var(--sp-2);
  padding-block-start: var(--sp-3);
  border-block-start: 1px solid var(--rule-hair);
}
.person-stat { display: grid; gap: 1px; min-inline-size: 0; }
.person-stat-value {
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
  line-height: 1.1;
  font-variant-numeric: tabular-nums lining-nums;
}
.person-stat-label { font-size: var(--fs-100); color: var(--ink-3); line-height: var(--lh-snug); }
.person-stat.is-attn .person-stat-value { color: var(--attn-ink); }

/* --- shift-count histogram (report view) ----------------------------------- */

.hist { display: flex; align-items: flex-end; gap: 3px; block-size: 3rem; }
.hist-col { display: flex; flex-direction: column; align-items: center; justify-content: flex-end; gap: 2px; }
.hist__bar {
  inline-size: 0.8rem;
  min-block-size: 2px;
  background-color: var(--brand);
  border-start-start-radius: 2px;
  border-start-end-radius: 2px;
}
.hist__bar.is-attn { background-color: var(--attn); }
.hist__lbl { font-size: 0.65rem; color: var(--ink-3); font-variant-numeric: tabular-nums; }

/* --- generate view: KPI lines, draft alternatives, live stats --------------- */

.kpi-line {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  inline-size: 100%;
  min-block-size: 2.25rem;
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid transparent;
  border-radius: var(--r-2);
  background-color: transparent;
  text-align: start;
  color: var(--ink);
  cursor: pointer;
  font-size: var(--fs-200);
}
.kpi-line:hover { background-color: var(--hover); border-color: var(--rule); }
.kpi-line::before { content: ''; inline-size: 6px; block-size: 6px; border-radius: 50%; background-color: var(--rule-strong); flex: none; }
.kpi-line.is-attn::before { background-color: var(--attn); }
.kpi-line.is-ok::before { background-color: var(--brand); }

.alt-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(19rem, 100%), 1fr));
  gap: var(--sp-4);
}
.alt-card { display: flex; flex-direction: column; gap: var(--sp-3); }
.alt-card.is-active { border-color: var(--brand); box-shadow: inset 0 0 0 1px var(--brand); }
.alt-card__label { font-size: var(--fs-300); font-weight: var(--fw-bold); }
.alt-card__trade { font-size: var(--fs-100); color: var(--ink-2); line-height: var(--lh-body); }

.tier-trace {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--sp-1) var(--sp-4);
  align-items: baseline;
  font-size: var(--fs-100);
}
.tier-trace .tid  { color: var(--ink-3); }
.tier-trace .tval { text-align: end; font-variant-numeric: tabular-nums; font-weight: var(--fw-medium); }

.live-stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-5);
  font-size: var(--fs-100);
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

/* --- candidate picker (editor.js "why this person") ------------------------ */

.cand-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  max-block-size: 42vh;
  overflow-y: auto;
  overscroll-behavior: contain;
}
.cand {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  inline-size: 100%;
  min-block-size: var(--tap);
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  background-color: var(--surface);
  text-align: start;
  cursor: pointer;
  min-inline-size: 0;
}
.cand:hover { background-color: var(--hover); }
.cand.is-blocked {
  cursor: default;
  background-color: var(--surface-2);
  color: var(--ink-3);
  border-style: dashed;
}
.cand.is-blocked:hover { background-color: var(--surface-2); }
.cand__name { font-weight: var(--fw-medium); min-inline-size: 9rem; }
.cand__why  { flex: 1 1 auto; min-inline-size: 0; font-size: var(--fs-100); color: var(--ink-2); }


/* =============================================================================
   14. TIER LADDER
   -----------------------------------------------------------------------------
   The objective in CONTRACT §4: ten rows compared lexicographically. Rows 0-2 are
   locked (a padlock, no drag handle). Rows 3-9 are draggable — reordering them
   permutes Settings.tierOrder and nothing else.

   <ol class="ladder">
     <li class="tier-row is-locked">
       <span class="tier-rank num">1</span>
       <span class="icon icon--lock tier-lock" aria-hidden="true"></span>
       <span class="tier-label">תאים ריקים</span>
       <span class="tier-value num">0</span>
     </li>
     <li class="tier-row" draggable="true">
       <span class="tier-rank num">4</span>
       <span class="icon icon--drag tier-grip" aria-hidden="true"></span> …
     </li>
   </ol>
============================================================================= */

.ladder {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 0;
  margin: 0;
  list-style: none;
  min-inline-size: 0;
}

.tier-row, .ladder-row {
  display: grid;
  grid-template-columns: 1.75rem 1.25rem minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--sp-3);
  min-block-size: var(--tap);
  padding-inline: var(--sp-3);
  padding-block: var(--sp-2);
  background-color: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  font-size: var(--fs-200);
  transition: border-color var(--dur-1) var(--ease), background-color var(--dur-1) var(--ease);
}
/* shell-side ladder rows carry their own five-column shape. */
.ladder-row { grid-template-columns: 1.5rem minmax(0, 1fr) 5rem 5rem 8rem; font-size: var(--fs-100); }

.tier-rank { font-size: var(--fs-100); font-weight: var(--fw-bold); color: var(--ink-3); text-align: center; }

.tier-grip {
  color: var(--ink-3);
  cursor: grab;
  inline-size: 1.1rem;
  block-size: 1.1rem;
  touch-action: none;
}
.tier-row:hover .tier-grip { color: var(--ink-2); }
.tier-row.is-dragging .tier-grip { cursor: grabbing; }

.tier-lock { color: var(--ink-3); inline-size: 1rem; block-size: 1rem; }

.tier-label {
  font-weight: var(--fw-medium);
  min-inline-size: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tier-value, .ladder-row .lval {
  font-weight: var(--fw-bold);
  color: var(--ink-2);
  font-variant-numeric: tabular-nums lining-nums;
  text-align: end;
  min-inline-size: 3.5rem;
}
.tier-value.is-attn, .ladder-row .lval.is-attn { color: var(--attn-ink); }
.ladder-row .lmeet { font-size: var(--fs-100); }

/* Locked rows sit on the sunken surface and cannot be picked up. */
.tier-row.is-locked, .ladder-row.is-locked {
  background-color: var(--surface-2);
  border-color: var(--rule-hair);
  color: var(--ink-2);
  cursor: default;
}

.tier-row[draggable='true'], .ladder-row.is-drag { cursor: grab; }
.tier-row.is-dragging, .ladder-row.is-dragging {
  opacity: 0.55;
  border-color: var(--brand);
  cursor: grabbing;
}
.tier-row.is-drop-target, .ladder-row.is-over {
  border-color: var(--brand);
  background-color: var(--brand-tint);
  box-shadow: inset 0 2px 0 0 var(--brand);
}

.ladder-divider {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding-block: var(--sp-2);
  font-size: var(--fs-100);
  color: var(--ink-3);
}
.ladder-divider::after { content: ''; flex: 1; block-size: 1px; background-color: var(--rule); }

/* Compact read-only ladder for a draft card. */
.ladder--readout .tier-row {
  min-block-size: 1.9rem;
  grid-template-columns: minmax(0, 1fr) auto;
  border: 0;
  border-radius: 0;
  border-block-end: 1px solid var(--rule-hair);
  background-color: transparent;
  padding-inline: 0;
}
.ladder--readout .tier-rank,
.ladder--readout .tier-grip,
.ladder--readout .tier-lock { display: none; }
.ladder--readout .tier-label,
.ladder--readout .tier-value { font-size: var(--fs-100); }


/* =============================================================================
   15. DATE-TILE GRID
   -----------------------------------------------------------------------------
   The heart of r.html. Residents tap these on a phone, so every tile is at least
   48px in both axes. The old app's 38px tiles were below the accessibility
   minimum on exactly the devices that use them most.

   <fieldset class="date-field">
     <legend>ימים חסומים</legend>
     <div class="date-grid tile-grid">
       <span class="date-dow tile-dow">א׳</span> ×7
       <span class="date-tile is-blank"></span>
       <label class="date-tile is-weekend">
         <input type="checkbox"><span class="date-num num">3</span>
       </label>
     </div>
   </fieldset>
============================================================================= */

.date-field {
  border: 1px solid var(--rule);
  border-radius: var(--r-3);
  padding: var(--sp-4);
  background-color: var(--surface);
  min-inline-size: 0;
}
.date-field > legend {
  padding-inline: var(--sp-2);
  font-size: var(--fs-200);
  font-weight: var(--fw-bold);
}

.date-grid, .tile-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: var(--sp-2);
  margin-block-start: var(--sp-3);
}

.date-dow, .tile-dow {
  text-align: center;
  font-size: var(--fs-100);
  font-weight: var(--fw-bold);
  color: var(--ink-3);
  padding-block-end: var(--sp-1);
}

.date-tile, .tile {
  appearance: none;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1px;
  min-block-size: var(--tap-lg);
  min-inline-size: 0;
  padding: var(--sp-1);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  background-color: var(--surface);
  color: var(--ink);
  font-size: var(--fs-200);
  font-weight: var(--fw-medium);
  font-variant-numeric: tabular-nums lining-nums;
  line-height: 1.1;
  cursor: pointer;
  user-select: none;
  transition: background-color var(--dur-1) var(--ease),
              border-color var(--dur-1) var(--ease),
              color var(--dur-1) var(--ease);
}
.date-tile:hover, .tile:hover { border-color: var(--rule-strong); background-color: var(--hover); }

/* The real input stays in the DOM (keyboard + screen reader) and out of sight. */
.date-tile input {
  position: absolute;
  inline-size: 100%;
  block-size: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}
.date-tile:has(input:focus-visible) { outline: 2px solid var(--focus); outline-offset: 2px; }

.date-num, .tile__num { font-size: var(--fs-200); font-weight: var(--fw-bold); }
.date-sub, .tile__mark {
  display: block;
  font-size: 0.68rem;
  line-height: 1.1;
  text-align: center;
  color: var(--ink-3);
}

.date-tile.is-blank, .tile--blank { border-color: transparent; background-color: transparent; cursor: default; }

/* Meaning (2): weekend / holiday band, on the tile as on the column. */
.date-tile.is-weekend, .tile--weekend { background-color: var(--band-weekend); }
.date-tile.is-holiday { background-color: var(--band-holiday); }
.date-tile.is-holiday .date-sub::before { content: var(--glyph-holiday) ' '; }

/* Generic "selected" (a checkbox date grid — holidays, month picking). Teal,
   because teal means "you chose this". This is NOT the blocked state: blocked is
   a statement about availability and gets the attention hue below. */
.date-tile:has(input:checked), .date-tile.is-on {
  background-color: var(--brand);
  border-color: var(--brand);
  color: var(--brand-contrast);
  font-weight: var(--fw-bold);
}
.date-tile:has(input:checked) .date-sub,
.date-tile.is-on .date-sub { color: inherit; opacity: 0.82; }

/* BLOCKED — "I cannot work". Attention hue: unavailable / stop. It used to be
   brand teal, i.e. the same colour as REQUESTED ("I want this"), which are the
   two most opposite statements on the board; and the legend swatch beside it was
   already pale red, so the key and the grid disagreed. Both now read
   --state-blocked-*, so they cannot drift apart again. */
.tile--blocked, .date-tile.is-blocked {
  background-color: var(--state-blocked-bg);
  border-color: var(--state-blocked);
  box-shadow: inset 0 0 0 1px var(--state-blocked);
  color: var(--state-blocked-ink);
  font-weight: var(--fw-bold);
}
.tile--blocked .tile__mark, .date-tile.is-blocked .date-sub { color: inherit; opacity: 0.9; }
.tile--blocked .tile__num::after, .date-tile.is-blocked .date-num::after {
  content: var(--glyph-blocked);
  font-size: 0.62em;
  margin-inline-start: 0.15em;
  vertical-align: 0.15em;
}

/* REQUESTED — a wish, and a positive one. Brand teal, outlined and tinted rather
   than solid, so a request layered on any other state still reads. */
.date-tile.is-requested, .tile--requested {
  background-color: var(--state-want-bg);
  border-color: var(--state-want);
  box-shadow: inset 0 0 0 1px var(--state-want);
  color: var(--state-want-ink);
  font-weight: var(--fw-bold);
}

.date-tile.is-requested::after, .tile--requested::after,
.date-tile.is-requested-high::after, .tile--high::after {
  content: var(--glyph-request);
  position: absolute;
  inset-block-start: 2px;
  inset-inline-end: 3px;
  font-size: 0.6rem;
  line-height: 1;
  color: var(--state-want);
}
/* priority:'high' (CONTRACT §2.1). Same colour, one heavier mark. Declared last
   so it wins on a tile that carries both classes. */
.date-tile.is-requested-high::after, .tile--high::after { content: var(--glyph-request-high); }

/* Locked / derived tiles — the residency blackout (CONTRACT §2.1): pre-checked,
   read-only, and visibly not the resident's own doing. */
.date-tile.is-readonly, .tile--readonly, .date-tile:has(input:disabled) {
  cursor: not-allowed;
  color: var(--ink-3);
  background-color: var(--surface-2);
  background-image: repeating-linear-gradient(45deg, transparent 0 5px, var(--rule-hair) 5px 6px);
  border-style: dashed;
}
.date-tile.is-readonly::after, .tile--readonly::after {
  content: '';
  position: absolute;
  inset-block-start: 3px;
  inset-inline-end: 3px;
  inline-size: 0.7rem;
  block-size: 0.7rem;
  background-color: var(--ink-3);
  -webkit-mask-image: var(--icon-lock); mask-image: var(--icon-lock);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: contain;     mask-size: contain;
}

/* COMMITTED (YA / YA-MH hard pre-assignment, CONTRACT H9/H10). Indigo: this one
   binds the schedule. It used to sit on the attention hue, which put it in the
   same colour as BLOCKED — the exact opposite instruction to the solver. */
.date-tile.is-committed, .tile--committed {
  background-color: var(--state-commit-bg);
  border-color: var(--state-commit);
  box-shadow: inset 0 0 0 1px var(--state-commit);
  color: var(--state-commit-ink);
  font-weight: var(--fw-bold);
}
.date-tile.is-committed .date-num::after, .tile--committed .tile__num::after {
  content: var(--glyph-commit);
  font-size: 0.5em;
  margin-inline-start: 0.2em;
  vertical-align: 0.35em;
}

/* A tile the chief has managed to mark BOTH blocked and requested is a genuine
   contradiction (CONTRACT H11 territory). Show it as blocked — the hard fact —
   with the request outline still visible on top, rather than letting whichever
   rule came last silently hide the other. */
.tile--blocked.tile--requested, .date-tile.is-blocked.is-requested {
  background-color: var(--state-blocked-bg);
  border-color: var(--state-blocked);
  box-shadow: inset 0 0 0 2px var(--state-want);
  color: var(--state-blocked-ink);
}
/* Same story for blocked ∩ committed, which CONTRACT H11 makes a named
   pre-flight blocker. The blocked fill must not be hidden by the commitment. */
.tile--blocked.tile--committed, .date-tile.is-blocked.is-committed {
  background-color: var(--state-blocked-bg);
  border-color: var(--state-blocked);
  box-shadow: inset 0 0 0 2px var(--state-commit);
  color: var(--state-blocked-ink);
}

.date-grid-summary {
  margin-block-start: var(--sp-4);
  padding-block-start: var(--sp-3);
  border-block-start: 1px solid var(--rule-hair);
  font-size: var(--fs-100);
  color: var(--ink-2);
}


/* =============================================================================
   16. ISSUES, STATS, PROGRESS, EMPTY STATES
============================================================================= */

/* --- issue list (audit errors / warnings, oracle blockers, levers) ---------- */

.issue-list { display: flex; flex-direction: column; gap: var(--sp-3); }

.issue {
  display: grid;
  grid-template-columns: 1.25rem minmax(0, 1fr);
  gap: var(--sp-3);
  padding: var(--sp-3) var(--sp-4);
  background-color: var(--surface);
  border: 1px solid var(--rule);
  border-inline-start: 3px solid var(--rule-strong);
  border-radius: var(--r-2);
  min-inline-size: 0;
}

.issue-mark {
  display: grid;
  place-items: center;
  inline-size: 1.25rem;
  block-size: 1.25rem;
  margin-block-start: 2px;
  border-radius: 50%;
  border: 1.5px solid currentColor;
  color: var(--ink-3);
  font-size: 0.72rem;
  font-weight: var(--fw-bold);
  line-height: 1;
}

.issue-title  { font-weight: var(--fw-bold); font-size: var(--fs-200); line-height: var(--lh-snug); }
.issue-detail { font-size: var(--fs-100); color: var(--ink-2); line-height: var(--lh-body); }
.issue-people { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-block-start: var(--sp-2); }

/* Errors get the one saturated colour, filled. */
.issue--error {
  border-inline-start-color: var(--attn);
  border-color: var(--attn-line);
  background-color: var(--attn-bg);
}
.issue--error .issue-mark { color: var(--attn); }
.issue--error .issue-mark::before { content: '!'; }

/* Warnings: the SAME hue, spine only, never a filled block. That is what keeps
   "attention" one colour instead of a traffic light. */
.issue--warning { border-inline-start-color: var(--attn); }
.issue--warning .issue-mark { color: var(--attn); }
.issue--warning .issue-mark::before { content: '!'; }

.issue--info { border-inline-start-color: var(--brand); }
.issue--info .issue-mark { color: var(--brand); }
.issue--info .issue-mark::before { content: 'i'; }

/* A lever (an oracle suggestion) is an ACTION, so it is brand-tinted. */
.issue--lever {
  border-inline-start-color: var(--brand);
  border-color: var(--brand-tint-2);
  background-color: var(--brand-tint);
}
.issue--lever .issue-mark { color: var(--brand-ink); }
.issue--lever .issue-mark::before { content: '\2192'; }

/* --- stat readouts --------------------------------------------------------- */

.stat-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(7.5rem, 100%), 1fr));
  gap: 1px;
  background-color: var(--rule-hair);
  border: 1px solid var(--rule);
  border-radius: var(--r-2);
  overflow: hidden;
}
.stat { display: grid; gap: 2px; padding: var(--sp-3) var(--sp-4); background-color: var(--surface); min-inline-size: 0; }
.stat-value { font-size: var(--fs-400); font-weight: var(--fw-bold); line-height: 1.05; }
.stat--hero .stat-value { font-size: var(--fs-600); }
.stat-label { font-size: var(--fs-100); color: var(--ink-3); line-height: var(--lh-snug); }
.stat-sub   { font-size: var(--fs-100); color: var(--ink-3); }
.stat.is-attn .stat-value { color: var(--attn-ink); }

/* --- progress -------------------------------------------------------------- */

.progress, .progress-bar {
  position: relative;
  block-size: 0.5rem;
  inline-size: 100%;
  background-color: var(--surface-3);
  border-radius: var(--r-pill);
  overflow: hidden;
}

.progress-fill, .progress-bar__fill {
  block-size: 100%;
  inline-size: calc(var(--value, 0) * 1%);
  background-color: var(--brand);
  border-radius: var(--r-pill);
  transition: inline-size var(--dur-3) var(--ease);
}
/* .progress-bar__fill is driven by an inline inline-size from the UI. */
.progress-bar__fill { inline-size: 0%; }

/* Indeterminate: the bar travels along the INLINE axis via margin-inline-start,
   so it runs the correct way round in RTL. */
.progress.is-indeterminate .progress-fill {
  inline-size: 35%;
  transition: none;
  animation: prog-slide 1.15s var(--ease) infinite alternate;
}
@keyframes prog-slide {
  from { margin-inline-start: 0; }
  to   { margin-inline-start: 65%; }
}

.progress-caption {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-block-start: var(--sp-2);
  font-size: var(--fs-100);
  color: var(--ink-3);
}

/* --- empty state ----------------------------------------------------------- */

.empty {
  display: grid;
  justify-items: center;
  gap: var(--sp-3);
  padding: var(--sp-8) var(--sp-5);
  text-align: center;
  color: var(--ink-3);
  border: 1px dashed var(--rule);
  border-radius: var(--r-3);
  background-color: var(--surface-sunk);
}
.empty-mark { inline-size: 2.5rem; block-size: 2.5rem; border-radius: var(--r-2); border: 2px dashed var(--rule-strong); }
.empty-title { font-size: var(--fs-300); font-weight: var(--fw-bold); color: var(--ink-2); }
.empty-body { max-inline-size: 34ch; font-size: var(--fs-200); line-height: var(--lh-body); }
.empty .btn { margin-block-start: var(--sp-2); }

/* Skeleton placeholder while the worker runs. */
.skeleton {
  background-color: var(--surface-3);
  border-radius: var(--r-1);
  color: transparent;
  animation: skeleton-pulse 1.4s ease-in-out infinite;
}
@keyframes skeleton-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}


/* =============================================================================
   17. DIALOG / MODAL
   CONTRACT §0 forbids alert / confirm / prompt, so this is the ONLY modal
   surface in the app. Both the native <dialog> and the .modal-backdrop/.modal
   pair that js/ui/dialogs.js already builds are styled.
============================================================================= */

.dialog {
  inline-size: min(44rem, calc(100vw - 2rem));
  max-block-size: min(88vh, 52rem);
  padding: 0;
  border: 1px solid var(--rule);
  border-radius: var(--r-3);
  background-color: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-modal);
  overflow: hidden;
}
.dialog--sm { inline-size: min(28rem, calc(100vw - 2rem)); }
.dialog--lg { inline-size: min(64rem, calc(100vw - 2rem)); }
.dialog::backdrop { background-color: rgba(20, 19, 17, 0.42); backdrop-filter: blur(2px); }

.modal-backdrop {
  position: fixed;
  inset-block: 0;
  inset-inline: 0;
  z-index: 100;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: var(--sp-5);
  overflow: auto;
  background-color: rgba(20, 19, 17, 0.42);
  backdrop-filter: blur(2px);
}

.modal {
  display: flex;
  flex-direction: column;
  inline-size: min(44rem, 100%);
  max-block-size: calc(100vh - 2 * var(--sp-5));
  margin-block: auto;
  background-color: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-3);
  box-shadow: var(--shadow-modal);
  overflow: hidden;
}
.modal--wide   { inline-size: min(64rem, 100%); }
.modal--narrow { inline-size: min(28rem, 100%); }

.dialog-inner { display: flex; flex-direction: column; max-block-size: inherit; min-block-size: 0; }

.dialog-head, .modal__head {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  border-block-end: 1px solid var(--rule-hair);
}
.dialog-head h2, .modal__title {
  flex: 1 1 auto;
  font-size: var(--fs-300);
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  min-inline-size: 0;
}

.dialog-body, .modal__body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  padding: var(--sp-5);
  overflow-y: auto;
  min-block-size: 0;
}

.dialog-foot, .modal__foot {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5);
  border-block-start: 1px solid var(--rule-hair);
  background-color: var(--surface-2);
}
.modal__foot .foot-start { margin-inline-end: auto; }


/* =============================================================================
   18. TOAST
   Block-axis entrance only, so nothing about the animation is handed.
============================================================================= */

.toasts, .toast-stack {
  position: fixed;
  inset-block-end: var(--sp-5);
  inset-inline-start: var(--sp-5);
  z-index: 200;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  inline-size: min(30rem, calc(100vw - 2rem));
  pointer-events: none;
}

.toast {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  inline-size: 100%;
  padding: var(--sp-3) var(--sp-4);
  background-color: var(--surface);
  color: var(--ink);
  border: 1px solid var(--rule-strong);
  border-inline-start: 3px solid var(--ink-3);
  border-radius: var(--r-2);
  box-shadow: var(--shadow-pop);
  font-size: var(--fs-200);
  line-height: var(--lh-snug);
  pointer-events: auto;
  animation: toast-in var(--dur-3) var(--ease);
}
@keyframes toast-in {
  from { opacity: 0; translate: 0 0.75rem; }
  to   { opacity: 1; translate: 0 0; }
}

.toast--ok   { border-inline-start-color: var(--brand); }
.toast--warn { border-inline-start-color: var(--attn); }
.toast--err  { border-inline-start-color: var(--attn); background-color: var(--attn-bg); border-color: var(--attn-line); }
.toast--attn { border-inline-start-color: var(--attn); }

.toast__row { display: flex; align-items: center; gap: var(--sp-3); }
.toast__msg, .toast-text { flex: 1 1 auto; min-inline-size: 0; }

.toast__timer {
  block-size: 3px;
  border-radius: var(--r-pill);
  background-color: var(--surface-3);
  overflow: hidden;
}
.toast__timer i { display: block; block-size: 100%; inline-size: 100%; background-color: var(--brand); }


/* =============================================================================
   19. UTILITIES  (deliberately few — components carry the design, not utilities)
============================================================================= */

.flush       { padding: 0; }
.full        { inline-size: 100%; }
.nowrap      { white-space: nowrap; }
.truncate    { min-inline-size: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.center      { text-align: center; }
.end         { text-align: end; }
.divider-top { border-block-start: 1px solid var(--rule-hair); padding-block-start: var(--sp-4); }
.scroll-y    { overflow-y: auto; overscroll-behavior: contain; }
.print-only  { display: none; }
.print-meta  { display: none; }


/* =============================================================================
   20. RESPONSIVE
   Everything works at 375px with no horizontal PAGE scroll. The two wide
   surfaces (matrix, tables) scroll inside their own containers.
============================================================================= */

/* Tablet and below: the sidebar shell folds into a horizontal nav strip. */
@media (max-width: 64rem) {
  .app { grid-template-columns: minmax(0, 1fr); }

  .sidebar {
    position: sticky;
    inset-block-start: 0;
    z-index: 30;
    block-size: auto;
    flex-direction: row;
    align-items: center;
    gap: var(--sp-4);
    padding: var(--sp-3) var(--sp-4);
    border-inline-end: 0;
    border-block-end: 1px solid var(--rule);
    overflow-x: auto;
  }
  .brand { flex: none; }
  .brand-sub, .brand small { display: none; }

  .nav {
    flex-direction: row;
    gap: var(--sp-2);
    overflow-x: auto;
    overscroll-behavior-inline: contain;
    padding-block: 2px;
  }
  .nav-item { white-space: nowrap; }
  .nav-group-title, .sidebar-foot { display: none; }

  .topbar { position: static; padding: var(--sp-4); }
  .view { padding: 0; }

  .app-header__top, .nav-tabs, .banner-stack { padding-inline: var(--sp-4); }
  .app-main { padding: var(--sp-4); }

  .cols-2 { grid-template-columns: minmax(0, 1fr); }

  .calendar, .daycards { grid-template-columns: repeat(auto-fill, minmax(min(11rem, 100%), 1fr)); }
  .cal-dow, .daycards .dow-head { display: none; }
  .day-card.is-blank, .daycard--blank { display: none; }
}

/* Phone. */
@media (max-width: 40rem) {
  :root {
    --matrix-name-w: 7rem;
    --matrix-col-w: 2.25rem;
    --sp-6: 1rem;
  }

  h1 { font-size: var(--fs-400); }
  .topbar h1, .view-head h2 { font-size: var(--fs-300); }

  .topbar { flex-direction: column; align-items: flex-start; gap: var(--sp-3); }
  .topbar-actions { inline-size: 100%; }

  .app-header__top { gap: var(--sp-2); padding: var(--sp-3); }
  .app-header__top .brand { inline-size: 100%; }
  .header-spacer { display: none; }
  .nav-tabs { padding-inline: var(--sp-3); }
  .app-main, .banner-stack { padding-inline: var(--sp-3); }
  .app-main { padding-block: var(--sp-4); }

  .page { padding: var(--sp-4) var(--sp-3) var(--sp-7); }
  .panel-head, .panel-body, .panel-foot, .card { padding: var(--sp-4); }

  .calendar, .daycards { grid-template-columns: minmax(0, 1fr); }

  /* Seven columns of >=44px will not fit inside 375px once panel padding is
     taken out, so on a phone the tap grid goes FULL-BLEED inside its panel.
     That buys back ~24px and lands the tiles at ~46 x 48px instead of 43 x 48. */
  .date-grid, .tile-grid { gap: 3px; }
  .date-field { padding: var(--sp-3); }
  .panel-body > .date-field,
  .card > .date-field {
    margin-inline: calc(-1 * var(--sp-4));
    border-inline: 0;
    border-radius: 0;
  }

  .matrix-scroll { max-block-size: 68vh; }

  .form-actions .btn,
  .dialog-foot .btn,
  .modal__foot .btn { flex: 1 1 auto; }

  .modal, .dialog {
    inline-size: 100%;
    max-inline-size: 100%;
    max-block-size: 100vh;
    border-radius: 0;
    border-inline: 0;
  }
  .modal-backdrop { padding: 0; }

  .toasts, .toast-stack { inset-inline: var(--sp-3); inline-size: auto; }

  .stat-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .diff-row { grid-template-columns: minmax(0, 1fr); gap: var(--sp-1); }
  .ladder-row { grid-template-columns: 1.5rem minmax(0, 1fr) auto; }
  .ladder-row .ltarget, .ladder-row .lfrozen { display: none; }
}

/* 375px and below: the tightest legible density. */
@media (max-width: 24rem) {
  :root { --matrix-col-w: 2.1rem; --matrix-name-w: 6.25rem; }
  .date-grid, .tile-grid { gap: 3px; }
  .btn { padding-inline: var(--sp-3); }
  .nav-tab { padding-inline: var(--sp-3); }
}

/* Wide desks: let the matrix breathe instead of stretching a 46rem measure. */
@media (min-width: 100rem) {
  :root { --matrix-col-w: 2.9rem; --matrix-name-w: 11rem; }
  .app-main, .view { padding-inline: var(--sp-7); }
}


/* =============================================================================
   21. REDUCED MOTION
============================================================================= */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  /* An indeterminate bar that cannot move should still say "working": a static
     striped fill spanning the whole track. */
  .progress.is-indeterminate .progress-fill {
    inline-size: 100%;
    margin-inline-start: 0;
    background-image: repeating-linear-gradient(45deg, var(--brand) 0 6px, var(--brand-tint-2) 6px 12px);
  }

  .skeleton { opacity: 1; }
}


/* =============================================================================
   22. PRINT
   -----------------------------------------------------------------------------
   The chief prints the finished rota, pins it up, and photographs it for the
   department WhatsApp group. That is the real output of this application, so it
   gets designed rather than switched off.

   Decisions, and why:
     - Landscape A4. A 31-column matrix in portrait is unreadable.
     - The palette is FORCED back to light, and to pure black on pure white.
       Someone working in dark mode should not print white-on-black, and should
       not spend a toner cartridge on our warm paper colour.
     - `print-color-adjust: exact` so the role tints and the weekend band survive
       the printer's helpful instinct to drop backgrounds. When they do not
       survive — a mono laser, a bad phone photo — the geometric glyphs
       (● ▲ ◆ ■ ○) carry the whole role mapping on their own, and the legend
       explains them.
     - Table headers repeat on every sheet; rows and day cards never split.
     - Attention becomes a heavy OUTLINE rather than a wash: a red fill
       photographs as an indistinct grey blob, an outline survives.
     - No printed URLs. This is a rota, not a bibliography.
============================================================================= */

@media print {

  @page { size: A4 landscape; margin: 9mm; }

  /* Forced light palette — overrides both the OS preference and data-theme. */
  :root,
  :root[data-theme='dark'],
  :root[data-theme='light'] {
    color-scheme: light;
    --paper: #FFFFFF;
    --surface: #FFFFFF;
    --surface-2: #F2EFE9;
    --surface-3: #E8E4DC;
    --surface-sunk: #FFFFFF;
    --ink: #000000;
    --ink-2: #24211D;
    --ink-3: #4A443C;
    --rule: #6B655C;
    --rule-hair: #A8A197;
    --rule-strong: #26231F;
    --brand: #06474C;
    --brand-ink: #06474C;
    --brand-contrast: #FFFFFF;
    --brand-tint: #E8F3F3;
    --brand-tint-2: #C9E5E6;
    --attn: #8E1D08;
    --attn-ink: #8E1D08;
    --attn-bg: #F7DED6;
    --attn-line: #8E1D08;
    /* Indigo survives a colour laser printer and goes dark grey on a mono one,
       which is why every state also carries a glyph. */
    --commit: #40257C;
    --commit-ink: #40257C;
    --commit-bg: #E6DBF8;
    --commit-line: #40257C;
    --band-weekend: #EDE4D4;
    --band-holiday: #E2D4BC;
    --zebra: rgba(0, 0, 0, 0.045);
    --hover: transparent;
    --press: transparent;
    --shadow-sticky: none;
    --shadow-pop: none;
    --shadow-modal: none;
    --role-blue-fg: #1B3260;   --role-blue-tint: #DCE5F4;   --role-blue-rule: #1B3260;
    --role-ochre-fg: #6B4000;  --role-ochre-tint: #F4E4C8;  --role-ochre-rule: #6B4000;
    --role-violet-fg: #4C2578; --role-violet-tint: #E8DCF5; --role-violet-rule: #4C2578;
    --role-green-fg: #1E4E25;  --role-green-tint: #D8E9D8;  --role-green-rule: #1E4E25;
  }

  html, body {
    background: #FFFFFF !important;
    color: #000000;
    font-size: 9pt;
    line-height: 1.35;
    overflow: visible;
  }

  body, .panel, .card, .table, .matrix, .day-card, .daycard, .chip, .cell-chip,
  .legend, .stat, .issue, .role-glyph, .date-tile, .tile {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  /* --- what disappears --- */
  .sidebar, .nav, .nav-tabs, .app-header, .topbar-actions, .toasts, .toast-stack,
  .progress, .progress-bar, .btn, .btn-group, .segmented, .chip-remove,
  .cell-actions, .form-actions, .dialog-foot, .modal__foot, .banner-stack,
  .no-print, input, select, textarea, [role='button'] { display: none !important; }

  dialog:not([open]) { display: none !important; }
  dialog[open] { position: static; inline-size: auto; max-block-size: none; border: 0; box-shadow: none; }
  dialog::backdrop { background: transparent; }
  .modal-backdrop { position: static; padding: 0; background: transparent; overflow: visible; }
  .modal { border: 0; box-shadow: none; max-block-size: none; inline-size: 100%; }

  .print-only, .print-meta { display: block !important; }

  /* --- layout collapses to one flowing column --- */
  .app, .main, .app-shell, .app-shell-inner { display: block; }
  .topbar {
    position: static;
    display: block;
    padding: 0 0 3mm;
    border-block-end: 1.5pt solid #000;
    margin-block-end: 4mm;
  }
  .topbar h1 { font-size: 15pt; }
  .view, .page, .app-main {
    display: block;
    padding: 0;
    max-inline-size: none;
  }
  .view > * + *, .app-main > * + * { margin-block-start: 4mm; }

  .panel, .card {
    border: 0.75pt solid var(--rule);
    border-radius: 0;
    break-inside: avoid;
  }
  .card { padding: 2mm; }
  .panel-head { padding: 2mm 0; border-block-end: 0.75pt solid var(--rule); }
  .panel-body { padding: 2mm 0; }
  .panel-foot { display: none; }

  h1, h2, h3, h4, .card__title { break-after: avoid; }
  p, li { orphans: 3; widows: 3; }

  /* --- tables --- */
  .table-scroll, .matrix-scroll {
    overflow: visible !important;
    max-block-size: none !important;
    border: 0;
    border-radius: 0;
  }
  .table, .matrix { inline-size: 100%; min-inline-size: 0; font-size: 8pt; }

  .table thead, .matrix thead { display: table-header-group; }
  .table tfoot, .matrix tfoot { display: table-footer-group; }
  .table tr, .matrix tr { break-inside: avoid; }

  .table th, .table td { padding: 1.2mm 1.6mm; border-block-end: 0.5pt solid var(--rule-hair); }
  .table thead th, .matrix thead th {
    position: static;
    background-color: var(--surface-2) !important;
    border-block-end: 1pt solid #000;
    color: #000;
  }
  .table--zebra tbody tr:nth-child(even) td { background-color: var(--zebra) !important; }

  /* --- the matrix: the thing that actually gets photographed --- */
  .matrix { font-size: 7pt; border-collapse: collapse; }
  .matrix th, .matrix td { border: 0.4pt solid var(--rule-hair); padding: 0.6mm 0.4mm; }
  .matrix-corner, .matrix-rowhead, .matrix th.rowhead, .matrix thead th.corner {
    position: static;
    inline-size: auto;
    min-inline-size: 24mm;
    max-inline-size: 34mm;
    border-inline-end: 1pt solid #000;
    background-color: #FFFFFF !important;
  }
  .matrix-person { font-size: 7pt; white-space: normal; }
  .matrix-cell, td.mcell { block-size: 5.5mm; }
  .matrix th.is-week-start, .matrix td.is-week-start { border-inline-start: 1.2pt solid #000; }
  .matrix col.is-weekend { background-color: var(--band-weekend) !important; }
  .matrix col.is-holiday { background-color: var(--band-holiday) !important; }

  .cell-chip {
    border-width: 0.5pt;
    block-size: auto;
    min-inline-size: 0;
    font-size: 6.5pt;
    padding-inline: 0.4mm;
  }
  .matrix-cell.is-unfilled, .matrix-cell.is-conflict, td.mcell.is-empty {
    background-color: var(--attn-bg) !important;
    background-image: none;
    box-shadow: none;
    outline: 1.2pt solid var(--attn);
    outline-offset: -1.2pt;
  }

  /* --- calendar --- */
  .calendar, .daycards { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 1.5mm; }
  .cal-dow, .daycards .dow-head { display: block; font-size: 7.5pt; padding-block: 1mm; }
  .day-card, .daycard { break-inside: avoid; border: 0.6pt solid var(--rule); border-radius: 0; min-block-size: 0; }
  .day-head, .daycard__head { padding: 1mm 1.5mm; border-block-end: 0.6pt solid var(--rule); }
  .day-num, .daycard__num { font-size: 10pt; }
  .slot-row, .slotline {
    min-block-size: 0;
    padding: 0.5mm 1.5mm;
    grid-template-columns: 12mm minmax(0, 1fr);
    font-size: 7pt;
    border-block-end: 0.4pt solid var(--rule-hair);
  }
  .slot-person, .slot-station, .slotline__st, .slotline__who { font-size: 7pt; }
  .slot-row.is-unfilled, .slotline.is-empty {
    outline: 1pt solid var(--attn);
    outline-offset: -1pt;
    box-shadow: none;
  }

  /* --- person cards / ladder / issues / stats --- */
  .person-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 2mm; }
  .person-card { break-inside: avoid; border-radius: 0; border-inline-start-width: 2pt; padding: 2mm; }

  .ladder { gap: 0; }
  .tier-row, .ladder-row {
    min-block-size: 0;
    padding: 1mm 1.5mm;
    border: 0;
    border-radius: 0;
    border-block-end: 0.4pt solid var(--rule-hair);
    break-inside: avoid;
  }
  .tier-grip { display: none; }

  .issue-list { gap: 1.5mm; }
  .issue { break-inside: avoid; border-radius: 0; padding: 1.5mm 2mm; }

  .stat-row { border-radius: 0; }
  .stat { padding: 1.5mm 2mm; }
  .stat-value { font-size: 13pt; }

  /* The legend ALWAYS prints. Without it the glyphs are decoration; with it they
     are a key, and the photograph explains itself to whoever opens it. */
  .legend {
    display: flex !important;
    break-inside: avoid;
    border: 0.6pt solid var(--rule);
    border-radius: 0;
    background-color: #FFFFFF !important;
    font-size: 7.5pt;
    margin-block-start: 3mm;
    padding: 1.5mm 2mm;
  }
  .legend-swatch, .swatch { border-width: 0.5pt; }

  /* Footer block the UI fills with month, draft id and generation stamp.
     CONTRACT §5 makes the search deterministic and says so on the printed rota —
     this is where that line lives. */
  .print-meta {
    margin-block-start: 3mm;
    padding-block-start: 1.5mm;
    border-block-start: 0.6pt solid var(--rule);
    font-size: 7.5pt;
    color: var(--ink-2);
    break-inside: avoid;
  }

  a { color: inherit; text-decoration: none; }
  a[href]::after { content: ''; }

  .empty, .skeleton { display: none !important; }
}
