/*
 * core.css: the platform shell. Tokens, the ten themes, font sets, block
 * styles, the rail, controls, panels, rows, modals, the dashboard grid, global
 * search, the mobile navigation, the settings launcher and the dock.
 *
 * Nothing feature-shaped lives here. A module ships its own
 * public/assets/modules/{key}/module.css carrying its .row.<feature> grid
 * lines and its screens, loaded after this file.
 *
 * Carved from GnomeWork's app.css. Comments explaining WHY a rule is the way
 * it is were kept on purpose: most of them record a measurement.
 */

  :root{
    --ink:#1F3A34; --ink-soft:#3E5850;
    --paper:#F5F1E8; --paper-raised:#FFFFFF;
    --line:#DAD3C2; --line-strong:#C3BAA4;
    --mustard:#B8792E; --mustard-soft:#EFDFC7;
    --sage:#7C9082; --sage-soft:#E4E9DF;
    --brick:#B8433C; --brick-soft:#F3DEDB;
    --blue:#3E6E8E; --blue-soft:#DDE8EE;
    --green-dot:#4C8B5C; --yellow-dot:#D4A62B;
    --text-muted:#8A8570; --subtle-bg:#FBF9F3; --radius:6px;
    /*
     * Typography. A font set swaps display and ui; --font-mono never changes,
     * because it is doing alignment work in counts, SKUs and money columns
     * rather than decoration, and a proportional face there would break the
     * columns. Sets live in the html[data-font=…] blocks further down.
     */
    --font-display:'Fraunces', serif;
    --font-ui:'IBM Plex Sans', sans-serif;
    --font-mono:'IBM Plex Mono', monospace;
    /*
     * Block style — how content panels are drawn. Every content panel reads
     * these, so a style is six declarations rather than a hunt through the
     * stylesheet. Chrome that isn't content (modals, dropdowns, the floating
     * chat panel) deliberately doesn't use them: a dropdown with no border
     * because somebody chose Flat would be unreadable over the page behind it.
     */
    --panel-bg:var(--paper-raised);
    --panel-border:1px solid var(--line);
    --panel-radius:8px;
    --panel-pad:16px 18px;
    --panel-shadow:none;
    --row-pad:13px 16px;
    /*
     * The primary button. Every theme carved from upstream paints it in the
     * ink, so the four tokens default to the ink and the paper; a theme whose
     * brand puts ONE colour on its buttons (Midnight's gold) restates these
     * four rather than re-declaring .btn, which would also catch .ghost and
     * .danger.
     */
    --btn-bg:var(--ink);
    --btn-ink:var(--paper);
    --btn-hover:var(--ink-soft);
    --btn-radius:var(--radius);
    /* Nav category tints. Every group head is a filled band so it can't be
       mistaken for one of the tabs underneath it. Each family needs a soft
       fill AND a matching dark ink — text on a tint must come from the same
       hue, or it reads as a mistake. Plum and teal are new: the palette had
       four usable families and there are seven groups. Apps is violet rather
       than the obvious green because green sits on top of CRM's sage — two
       adjacent groups reading as the same colour defeats the point. */
    --violet:#6A6AA8; --violet-soft:#E3E2F2;
    --plum:#8A5A7A; --plum-soft:#EDE0E8;
    --teal:#3E8E85; --teal-soft:#DAEBE8;
    --sage-ink:#3A5240; --mustard-ink:#7A4E17; --blue-ink:#2A4C63;
    --violet-ink:#3F3F73; --brick-ink:#8A2F29; --plum-ink:#6B4159;
    --teal-ink:#2C6660;
  }
  /*
   * Themes. `:root` above is Garden paper, the default, so a theme block only
   * has to restate what differs — except that in practice every block restates
   * ALL of them. A theme that overrode only paper and ink would inherit Garden
   * paper's nav tints, and the sidebar headers would come out as pale mint on a
   * dark background. Copy a whole block when adding one.
   *
   * `data-theme` names the specific theme; `data-scheme` says light or dark and
   * is what the handful of scheme-specific rules below key on, so adding a
   * fourth dark theme doesn't mean editing ten selectors. applyTheme() sets
   * both.
   */

  /*
   * Block styles. Framed is :root, so it needs no block.
   *
   * Elevated is the only one that has to know about the colour scheme: a shadow
   * tuned for cream paper disappears on a near-black one. That is what
   * data-scheme is for, and why it exists separately from data-theme — one rule
   * covers all five dark themes.
   */
  html[data-blocks="flat"]{
    --panel-bg:var(--subtle-bg);
    --panel-border:1px solid transparent;
    --panel-shadow:none;
  }
  html[data-blocks="elevated"]{
    --panel-border:1px solid transparent;
    --panel-radius:12px;
    --panel-shadow:0 2px 8px rgba(31,58,52,0.10), 0 1px 2px rgba(31,58,52,0.06);
  }
  html[data-scheme="dark"][data-blocks="elevated"]{
    --panel-shadow:0 2px 10px rgba(0,0,0,0.45), 0 1px 3px rgba(0,0,0,0.35);
  }
  /* No panels at all. The section heading and its rule do the separating, the
     way a printed report does it. Rows keep their own dividers, or a list would
     become an undifferentiated block of text. */
  html[data-blocks="ruled"]{
    --panel-bg:transparent;
    --panel-border:none;
    --panel-radius:0;
    --panel-pad:0;
    --panel-shadow:none;
  }
  html[data-blocks="ruled"] .section-head{border-bottom:1px solid var(--line);padding-bottom:8px;}
  html[data-blocks="ruled"] .row{padding-left:0;padding-right:0;}
  html[data-blocks="compact"]{
    --panel-radius:4px;
    --panel-pad:10px 12px;
    --row-pad:8px 12px;
  }

  /*
   * Font sets. Garden is :root, so it needs no block. `system` deliberately
   * names no web font at all — it downloads nothing and never flashes.
   */
  html[data-font="plain"]{--font-display:'Inter', sans-serif;--font-ui:'Inter', sans-serif;}
  html[data-font="legible"]{--font-display:'Atkinson Hyperlegible', sans-serif;--font-ui:'Atkinson Hyperlegible', sans-serif;}
  html[data-font="editorial"]{--font-display:'Source Serif 4', serif;--font-ui:'Source Sans 3', sans-serif;}
  html[data-font="system"]{--font-display:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;--font-ui:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;}
  html[data-font="slab"]{--font-display:'Zilla Slab', serif;--font-ui:'IBM Plex Sans', sans-serif;}
  html[data-font="storybook"]{--font-display:'EB Garamond', serif;--font-ui:'Lato', sans-serif;}
  html[data-font="rounded"]{--font-display:'Nunito', sans-serif;--font-ui:'Nunito', sans-serif;}
  html[data-font="condensed"]{--font-display:'Archivo Narrow', sans-serif;--font-ui:'Archivo', sans-serif;}
  html[data-font="technical"]{--font-display:'Space Grotesk', sans-serif;--font-ui:'IBM Plex Sans', sans-serif;}
  html[data-font="physare"]{--font-display:'Helvetica Neue', Helvetica, Arial, sans-serif;--font-ui:'Helvetica Neue', Helvetica, Arial, sans-serif;}
  /* Montserrat over PT Sans: the Shared Spirits pairing. Their headings are
     Montserrat 700 and their body PT Sans; the brand mark follows the site's
     wordmark at 800. */
  html[data-font="montserrat"]{--font-display:'Montserrat', sans-serif;--font-ui:'PT Sans', sans-serif;}
  html[data-font="montserrat"] .brand-mark{font-weight:800;letter-spacing:-0.01em;}
  html[data-font="montserrat"] .page-title,html[data-font="montserrat"] h2,html[data-font="montserrat"] .section-title{font-weight:700;letter-spacing:-0.01em;}
  /* The house face is light, not bold: Helvetica at 600 reads as shouting on black. */
  html[data-font="physare"] .brand-mark{font-weight:400;letter-spacing:-0.025em;}
  html[data-font="physare"] .page-title,html[data-font="physare"] h2,html[data-font="physare"] .section-title{font-weight:500;letter-spacing:-0.015em;}

  /* Ink and linen — cool light, navy ink. The plain counterpart to Garden paper. */
  html[data-theme="ink"]{
    --ink:#232A33; --ink-soft:#4A5462;
    --paper:#F4F4F1; --paper-raised:#FFFFFF;
    --line:#DCDCD6; --line-strong:#C2C2BA;
    --mustard:#A9761F; --mustard-soft:#F0E1C6;
    --sage:#5E8A6B; --sage-soft:#DEE9E1;
    --brick:#B23A3A; --brick-soft:#F3DCDC;
    --blue:#2C5F8A; --blue-soft:#D9E5EF;
    --green-dot:#3F7C4E; --yellow-dot:#B98F1E;
    --text-muted:#7D8590; --subtle-bg:#FAFAF8;
    --violet:#5B5FA6; --violet-soft:#E0E1F2;
    --plum:#8A4F77; --plum-soft:#EEDFE9;
    --teal:#2F8078; --teal-soft:#D8EAE7;
    --sage-ink:#2F4F39; --mustard-ink:#6E4A11; --blue-ink:#1E4160;
    --violet-ink:#383B75; --brick-ink:#7C2727; --plum-ink:#5E3550;
    --teal-ink:#235B55;
  }

  /*
   * Harbour: the Shared Spirits look for the day's work, measured from the
   * venue features page. The PAGE is light: white paper, cards in #F4F7FC
   * with a faint blue hairline at 8px, steel blue (#056BB6) for headings,
   * the primary button and the icon discs, near-black titles, #7A7A7A body.
   * The RAIL and the LOGIN CARD keep the Back Office navy (#000540), so the
   * brand's dark side stays on screen without the reading strain of a whole
   * dark page. That is done by re-scoping the navy set of variables onto
   * .rail and .login-screen: every rule inside them already reads through
   * the variables, so nothing else has to know. The blue is both the accent
   * and the sage family, as Physare gives one yellow both jobs: this design
   * has one hue and a second would be invented.
   */
  html[data-theme="harbour"]{
    accent-color:#056BB6;
    --ink:#111111; --ink-soft:#3A3F4B;
    --paper:#FFFFFF; --paper-raised:#F4F7FC;
    --line:#CFE3F1; --line-strong:#A9C7E3;
    --mustard:#056BB6; --mustard-soft:#DCE9F6;
    --sage:#056BB6; --sage-soft:#E4EEF8;
    --brick:#C54343; --brick-soft:#F9DCDC;
    --blue:#056BB6; --blue-soft:#DCE9F6;
    --green-dot:#2E9E63; --yellow-dot:#F3B429;
    --text-muted:#7A7A7A; --subtle-bg:#EEF3FA;
    --violet:#6A63B8; --violet-soft:#E6E3F5;
    --plum:#8C5A85; --plum-soft:#EEE1EC;
    --teal:#2C7F8C; --teal-soft:#D9EBEE;
    --sage-ink:#0B4F86; --mustard-ink:#0B4F86; --blue-ink:#0B4F86;
    --violet-ink:#3F3A7A; --brick-ink:#8A2F29; --plum-ink:#5E3558;
    --teal-ink:#1F5A63;
    --panel-radius:8px;
    --btn-bg:#056BB6; --btn-ink:#FFFFFF; --btn-hover:#0B5A99; --btn-radius:10px;
  }
  /* The navy set, scoped to the rail and the sign-in card. The group heads
     read their tint families from here too, so every group is a navy band
     with a pale blue label rather than five pastel bands on navy. */
  html[data-theme="harbour"] .rail,html[data-theme="harbour"] .login-card{
    --ink:#FFFFFF; --ink-soft:#C9CBD9;
    --paper:#000540; --paper-raised:#1A2270;
    --line:#1B2160; --line-strong:#2E3578;
    --mustard:#FFBE2A; --mustard-soft:#2A2238;
    --sage:#7FC0F5; --sage-soft:#0F1657;
    --brick:#F08A80; --brick-soft:#0F1657;
    --blue:#7FC0F5; --blue-soft:#0F1657;
    --text-muted:#9AA0C8; --subtle-bg:#0F1657;
    --violet:#A9A2E8; --violet-soft:#0F1657;
    --plum:#D6A0C2; --plum-soft:#0F1657;
    --teal:#7FD6CC; --teal-soft:#0F1657;
    --sage-ink:#9ED9FF; --mustard-ink:#FFBE2A; --blue-ink:#9ED9FF;
    --violet-ink:#9ED9FF; --brick-ink:#9ED9FF; --plum-ink:#9ED9FF;
    --teal-ink:#9ED9FF;
    color:var(--ink);
  }
  /* The sign-in CARD is the Back Office (the logo plate's own navy, a gold
     button); the page around it is the light paper, so the card sits on the
     white the way their feature cards do. */
  html[data-theme="harbour"] .login-card{--btn-bg:#F3B429;--btn-ink:#000000;--btn-hover:#FFBE2A;--btn-radius:7px;background:#000540;border-color:#1B2160;box-shadow:0 12px 36px rgba(0,5,64,0.25);}
  html[data-theme="harbour"] .rail .tab.active{background:var(--paper-raised);border-color:var(--line-strong);color:#FFFFFF;}
  html[data-theme="harbour"] .rail .tab.active .count{color:#FFBE2A;}
  html[data-theme="harbour"] .page-title,html[data-theme="harbour"] .section-title,html[data-theme="harbour"] h2{color:#056BB6;}
  html[data-theme="harbour"] .badge.pending,html[data-theme="harbour"] .badge.st-waiting{color:#0B4F86;}
  html[data-theme="harbour"] .theme-card.selected{border-color:var(--mustard);}
  html[data-theme="harbour"] .theme-card-tick{color:var(--mustard);}
  /* The drawn storefront for a venue with no favicon, and the icon in a
     row, take the blue disc treatment the site gives its feature icons. */
  html[data-theme="harbour"] .company-logo-ph{color:#056BB6;}

  /* Newsprint — near-monochrome light. Colour appears only where it carries
     meaning, so status badges do more work. */
  html[data-theme="newsprint"]{
    --ink:#1A1A18; --ink-soft:#44443F;
    --paper:#F7F7F5; --paper-raised:#FFFFFF;
    --line:#DEDEDA; --line-strong:#BFBFB9;
    --mustard:#8F7031; --mustard-soft:#EFE6D5;
    --sage:#55705C; --sage-soft:#E2E7E2;
    --brick:#96453F; --brick-soft:#EEDFDD;
    --blue:#3C5C74; --blue-soft:#DFE6EB;
    --green-dot:#4A7355; --yellow-dot:#A8862A;
    --text-muted:#75756E; --subtle-bg:#FCFCFA;
    --violet:#55538A; --violet-soft:#E4E3EC;
    --plum:#7A5069; --plum-soft:#EBE1E7;
    --teal:#37716B; --teal-soft:#DEE9E7;
    --sage-ink:#2C3D31; --mustard-ink:#56421A; --blue-ink:#22384A;
    --violet-ink:#333155; --brick-ink:#5E2A26; --plum-ink:#4B2F40;
    --teal-ink:#1E443F;
  }

  /* Sandstone — warm neutral light. Garden paper without the green cast. */
  html[data-theme="sandstone"]{
    --ink:#332C24; --ink-soft:#574C40;
    --paper:#F4EFE6; --paper-raised:#FFFCF7;
    --line:#E2D9CB; --line-strong:#C9BCA8;
    --mustard:#A5741F; --mustard-soft:#F1E1C4;
    --sage:#6F8368; --sage-soft:#E3E8DC;
    --brick:#A9463A; --brick-soft:#F2DED9;
    --blue:#3E6479; --blue-soft:#DCE6EB;
    --green-dot:#4F7F55; --yellow-dot:#BE9425;
    --text-muted:#8B7F6E; --subtle-bg:#FAF6EF;
    --violet:#635A93; --violet-soft:#E4E1EF;
    --plum:#8B5271; --plum-soft:#EFDFE7;
    --teal:#357C71; --teal-soft:#D9E8E4;
    --sage-ink:#3B4A33; --mustard-ink:#684711; --blue-ink:#2A4453;
    --violet-ink:#3A3563; --brick-ink:#6F2A21; --plum-ink:#573044;
    --teal-ink:#1F4E46;
  }

  /* Slate — cool grey-blue dark. Replaces the old "dark" theme. */
  html[data-theme="slate"]{
    --ink:#E6EAF0; --ink-soft:#B9C2CD;
    --paper:#1B1F24; --paper-raised:#242A31;
    --line:#333B45; --line-strong:#46505C;
    --mustard:#D9A25A; --mustard-soft:#3D3320;
    --sage:#8FB09A; --sage-soft:#26332B;
    --brick:#E08078; --brick-soft:#3D2422;
    --blue:#6FA8D0; --blue-soft:#1F2E39;
    --green-dot:#6FC486; --yellow-dot:#E4BE58;
    --text-muted:#8994A1; --subtle-bg:#2C333B;
    --violet:#9B9BE0; --violet-soft:#2B2B40;
    --plum:#C08BB4; --plum-soft:#362534;
    --teal:#6BBDB4; --teal-soft:#1D3330;
    --sage-ink:#BCD4C4; --mustard-ink:#EBCB92; --blue-ink:#BBD9EC;
    --violet-ink:#C7C7EE; --brick-ink:#F0B4AC; --plum-ink:#E0BCD5;
    --teal-ink:#A8D9D2;
  }

  /*
   * Midnight: the Shared Spirits Back Office look, measured from their site.
   * Navy paper (#000540) with cards a shade deeper (#020234), gold headings
   * (#FFBE2A) and a gold button (#F3B429, black text, 7px radius), body
   * text in #B9B9B9, icon tiles in charcoal-plum (#33293C), Montserrat 700
   * over PT Sans. The gold is the accent family (focus rings, the active
   * count, pending, the primary button); every other family is lifted just
   * far enough to read on navy and no further, so a status still says
   * something without competing with the one brand colour. Their site also
   * uses a thin blue (#0CA7FA) card border; that is the blue family here.
   */
  html[data-theme="midnight"]{
    accent-color:#F3B429; color-scheme:dark;
    --ink:#FFFFFF; --ink-soft:#C9CBD9;
    --paper:#000540; --paper-raised:#020234;
    --line:#1B2160; --line-strong:#2E3578;
    --mustard:#F3B429; --mustard-soft:#2A2238;
    --sage:#6FCF97; --sage-soft:#0B2A44;
    --brick:#F08A80; --brick-soft:#3A1F33;
    --blue:#0CA7FA; --blue-soft:#061A55;
    --green-dot:#6FCF97; --yellow-dot:#FFBE2A;
    --text-muted:#B9B9B9; --subtle-bg:#0A0F4B;
    --violet:#A9A2E8; --violet-soft:#1B1A55;
    --plum:#D6A0C2; --plum-soft:#33293C;
    --teal:#7FD6CC; --teal-soft:#0B2E44;
    --sage-ink:#BFEFD1; --mustard-ink:#FFBE2A; --blue-ink:#9ED9FF;
    --violet-ink:#D6D2F7; --brick-ink:#FFC1B8; --plum-ink:#F0CDE3;
    --teal-ink:#BDEDE7;
    --btn-bg:#F3B429; --btn-ink:#000000; --btn-hover:#FFBE2A; --btn-radius:7px;
  }
  /* Pending and waiting wear the gold on the site's icon-tile plum rather
     than the dark-scheme amber above. Headings are gold, the way theirs are;
     the brand mark stays white so the gold means "a heading or an action". */
  html[data-theme="midnight"] .badge.pending,html[data-theme="midnight"] .badge.st-waiting{color:#FFBE2A;}
  html[data-theme="midnight"] .badge.paid{background:#F3B429;color:#000000;}
  html[data-theme="midnight"] .page-title,html[data-theme="midnight"] .section-title,html[data-theme="midnight"] h2{color:#FFBE2A;}
  html[data-theme="midnight"] .login-card{box-shadow:0 8px 32px rgba(0,0,0,0.45);}
  html[data-theme="midnight"] .tab.active{border-color:var(--line-strong);}
  html[data-theme="midnight"] .theme-card.selected{border-color:var(--mustard);}
  html[data-theme="midnight"] .theme-card-tick{color:var(--mustard);}
  html[data-theme="midnight"] .nav-category-head{--cat-tint:var(--subtle-bg);--cat-ink:#FFBE2A;--cat-accent:var(--mustard);}

  /* Dusk — deep indigo dark. */
  html[data-theme="dusk"]{
    --ink:#E4E3EE; --ink-soft:#B6B5C6;
    --paper:#15161F; --paper-raised:#1E2029;
    --line:#2E3040; --line-strong:#414459;
    --mustard:#DDA65E; --mustard-soft:#3B3120;
    --sage:#86B396; --sage-soft:#223026;
    --brick:#E58A83; --brick-soft:#3C2429;
    --blue:#7BAEE0; --blue-soft:#1E2C3E;
    --green-dot:#6BC384; --yellow-dot:#E5C264;
    --text-muted:#8A89A0; --subtle-bg:#262838;
    --violet:#A6A2EC; --violet-soft:#2A2846;
    --plum:#C892BC; --plum-soft:#34253A;
    --teal:#6FC3BC; --teal-soft:#1C3134;
    --sage-ink:#BDD8C6; --mustard-ink:#EFCE99; --blue-ink:#BEDBF1;
    --violet-ink:#CFCCF4; --brick-ink:#F2B8B2; --plum-ink:#E4C2DC;
    --teal-ink:#ABDDD8;
  }

  /* Ember — warm dark. The only one of the three darks without a cool cast. */
  html[data-theme="ember"]{
    --ink:#F0E7DE; --ink-soft:#CBBCAF;
    --paper:#1E1A17; --paper-raised:#292421;
    --line:#3D3530; --line-strong:#504640;
    --mustard:#E0A455; --mustard-soft:#45341D;
    --sage:#9CB08C; --sage-soft:#2C3527;
    --brick:#E5867A; --brick-soft:#432723;
    --blue:#86AEC4; --blue-soft:#263239;
    --green-dot:#83C48F; --yellow-dot:#E8C46A;
    --text-muted:#968779; --subtle-bg:#332C28;
    --violet:#B0A0D4; --violet-soft:#322C40;
    --plum:#C58FA8; --plum-soft:#3A2A32;
    --teal:#6FBDAE; --teal-soft:#21332F;
    --sage-ink:#C8D8BC; --mustard-ink:#F2CE96; --blue-ink:#BDD6E3;
    --violet-ink:#D6CCEC; --brick-ink:#F4B7AE; --plum-ink:#E5C3D2;
    --teal-ink:#ADDBD1;
  }

  /* Obsidian — near-black neutral. The highest-contrast of the darks. */
  html[data-theme="obsidian"]{
    --ink:#F2F3F5; --ink-soft:#C3C6CC;
    --paper:#0E0F11; --paper-raised:#17191C;
    --line:#282B30; --line-strong:#3A3E45;
    --mustard:#E5AE63; --mustard-soft:#3A2F1C;
    --sage:#93BCA0; --sage-soft:#1D2A21;
    --brick:#EC8B81; --brick-soft:#3A2220;
    --blue:#7DB6E2; --blue-soft:#182833;
    --green-dot:#79CE90; --yellow-dot:#EBC85E;
    --text-muted:#909499; --subtle-bg:#1D2024;
    --violet:#A9A6F0; --violet-soft:#242347;
    --plum:#D096C2; --plum-soft:#331F30;
    --teal:#71C8BE; --teal-soft:#152E2B;
    --sage-ink:#C3DDCB; --mustard-ink:#F3D3A0; --blue-ink:#C4DEF3;
    --violet-ink:#D3D1F7; --brick-ink:#F6BCB4; --plum-ink:#EAC6E0;
    --teal-ink:#AFE0D9;
  }

  /* Physare: black, charcoal and three greys, with ONE colour, the yellow of
     the Physare mark, carrying the accent (active tab, primary button, paid).
     Every other family is desaturated towards grey so a status still reads
     without breaking the monochrome. */
  html[data-theme="physare"]{
    /* Native controls (checkbox, radio, range, the date picker) take the
       yellow too; without accent-color they are browser blue, the one colour
       this theme must never show. */
    accent-color:#E9F26B; color-scheme:dark;
    --ink:#E8E8E6; --ink-soft:#A6A6A3;
    --paper:#0B0B0C; --paper-raised:#141416;
    --line:#232325; --line-strong:#3A3A3E;
    --mustard:#E9F26B; --mustard-soft:#26281A;
    --sage:#E9F26B; --sage-soft:#1F2210;
    --brick:#D08A82; --brick-soft:#2A1F1E;
    --blue:#9CA3AD; --blue-soft:#1C1E22;
    --green-dot:#E9F26B; --yellow-dot:#E9F26B;
    --text-muted:#6B6B69; --subtle-bg:#1A1A1C;
    --violet:#A9A9B5; --violet-soft:#1E1E24;
    --plum:#B3A3AE; --plum-soft:#221D21;
    --teal:#9DB0AD; --teal-soft:#1B2221;
    --sage-ink:#E9F26B; --mustard-ink:#E9F26B; --blue-ink:#D6D6D3;
    --violet-ink:#D6D6D3; --brick-ink:#E8B3AC; --plum-ink:#D6D6D3;
    --teal-ink:#D6D6D3;
  }

  /* Forest — deep green dark, the green-black the retired Nightshade had. */
  html[data-theme="forest"]{
    --ink:#E7EFE4; --ink-soft:#BCCBB7;
    --paper:#101A13; --paper-raised:#19251C;
    --line:#2A3A2C; --line-strong:#3C4E3E;
    --mustard:#D6A356; --mustard-soft:#3C3320;
    --sage:#8CBE97; --sage-soft:#213022;
    --brick:#E28B7E; --brick-soft:#3B2523;
    --blue:#7FB3CC; --blue-soft:#1B2F37;
    --green-dot:#71CB86; --yellow-dot:#E1C25D;
    --text-muted:#849081; --subtle-bg:#1E2C21;
    --violet:#A29ED8; --violet-soft:#292B40;
    --plum:#C08FAF; --plum-soft:#372936;
    --teal:#6BC0B0; --teal-soft:#173330;
    --sage-ink:#BFDBC6; --mustard-ink:#EDCE97; --blue-ink:#BFDAE9;
    --violet-ink:#CBC8EE; --brick-ink:#F1B9AF; --plum-ink:#E2C1D6;
    --teal-ink:#A7DCD2;
  }
  html[data-scheme="dark"] .badge.pending{color:#E8C98A;}
  html[data-scheme="dark"] .badge.fulfilled{color:#BFE0C6;}
  html[data-scheme="dark"] .badge.paid{color:#12281A;}
  html[data-scheme="dark"] .badge.consigned{color:#BFDCEA;}
  html[data-scheme="dark"] .badge.st-waiting{color:#E8C98A;}
  html[data-scheme="dark"] .badge.st-progress{color:#BFDCEA;}
  html[data-scheme="dark"] .badge.st-done{color:#BFE0C6;}
  html[data-scheme="dark"] .badge.sentiment-not{color:#F0B7AE;}
  html[data-scheme="dark"] .badge.sentiment-follow{color:#BFDCEA;}
  html[data-scheme="dark"] .badge.sentiment-nurture{color:#BFE0C6;}
  html[data-scheme="dark"] .badge.warn{color:#F0B7AE;}
  html[data-scheme="dark"] .badge.sentiment-connected{color:#12281A;}
  /* Physare badges stay in its own greys and yellow: the dark-scheme blues
     above would be the only blue on the page. Waiting and done wear the
     yellow; progress, consigned and follow-up are quiet grey. */
  html[data-theme="physare"] .badge.pending,html[data-theme="physare"] .badge.st-waiting{color:#E9F26B;}
  html[data-theme="physare"] .badge.fulfilled,html[data-theme="physare"] .badge.st-done,html[data-theme="physare"] .badge.sentiment-nurture{color:#E9F26B;}
  html[data-theme="physare"] .badge.consigned,html[data-theme="physare"] .badge.st-progress,html[data-theme="physare"] .badge.sentiment-follow{background:var(--subtle-bg);color:var(--ink);}
  body{transition:background-color 0.15s ease, color 0.15s ease;}
  *{box-sizing:border-box;}
  body{margin:0;background:var(--paper);color:var(--ink);font-family:var(--font-ui);-webkit-font-smoothing:antialiased;}
  .app{max-width:1180px;margin:0 auto;display:grid;grid-template-columns:220px 1fr;min-height:100vh;}
  @media (max-width:820px){ .app{grid-template-columns:1fr;} .rail{position:sticky;top:0;z-index:30;} }

  .rail{border-right:1px solid var(--line);padding:28px 18px;display:flex;flex-direction:column;gap:18px;background:var(--paper);}
  @media (max-width:820px){ .rail{border-right:none;border-bottom:1px solid var(--line);flex-direction:row;align-items:center;padding:14px 18px;gap:14px;} }

  .brand{display:flex;align-items:baseline;gap:8px;}
  .brand-mark{font-family:var(--font-display);font-weight:600;font-size:21px;letter-spacing:-0.01em;}
  /* The lockup: the name, and the vendor's byline small and spaced beneath it. */
  .brand-lockup{display:flex;flex-direction:column;line-height:1;}
  .brand-byline{display:block;font-family:var(--font-ui);font-size:9px;letter-spacing:0.22em;text-transform:uppercase;color:var(--text-muted);margin-top:5px;}
  .brand-sub{font-family:var(--font-mono);font-size:10px;color:var(--text-muted);letter-spacing:0.08em;text-transform:uppercase;margin-top:-12px;}
  /* The notification bell, at the right-hand end of the brand row. align-self
     overrides the row's baseline alignment, which would hang an icon off the
     title's baseline; the badge is its own positioned child. Its display is
     toggled inline by renderNotificationBell(), which is why the mobile rule
     below needs !important: that rail has its own bell. */
  .brand-bell{display:inline-flex;align-items:center;align-self:center;margin-left:auto;cursor:pointer;position:relative;color:var(--text-muted);}
  .brand-bell:hover{color:var(--ink);}
  .brand-bell .ic{width:19px;height:19px;}
  #notif-count{position:absolute;top:-6px;right:-8px;min-width:15px;height:15px;border-radius:8px;background:var(--brick);color:#fff;font-size:9px;line-height:15px;text-align:center;padding:0 3px;font-family:var(--font-mono);}
  @media (max-width:820px){ .brand-sub{display:none;} .brand{flex-shrink:0;} .brand-mark{font-size:17px;} .brand-bell{display:none !important;} }

  .tabs{display:flex;flex-direction:column;gap:2px;flex:1;}
  @media (max-width:820px){ .tabs{flex-direction:row;flex:1;flex-wrap:wrap;} }
  .tab{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:9px 12px;border-radius:var(--radius);font-size:14px;color:var(--ink-soft);cursor:pointer;white-space:nowrap;border:1px solid transparent;transition:background 0.12s ease;}
  .tab:hover{background:var(--sage-soft);}
  .tab.active{background:var(--paper-raised);border-color:var(--line);color:var(--ink);font-weight:500;}
  .tab .count{font-family:var(--font-mono);font-size:11px;color:var(--text-muted);}
  .tab.active .count{color:var(--mustard);}

  /* Outline icons, drawn from the sprite in index.html.
     `stroke:currentColor` is the whole reason this works across ten themes and
     five dark ones: the icon inherits .tab's colour, so it lightens on hover
     and darkens with .tab.active without a single extra rule. Never give an
     icon its own colour here or that stops being true.
     A .tab is display:flex with space-between, so the icon and label have to
     share ONE flex child (.tab-label) or the count gets pushed into the middle. */
  .ic{width:17px;height:17px;flex-shrink:0;fill:none;stroke:currentColor;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round;}
  .tab-label{display:flex;align-items:center;gap:9px;min-width:0;overflow:hidden;text-overflow:ellipsis;}
  /* Buttons are inline rather than flex, so the icon is aligned optically
     against the text baseline instead of being centred by the layout. */
  .btn .ic{vertical-align:-3.5px;margin-right:5px;width:15px;height:15px;}
  .page-title .ic{width:21px;height:21px;vertical-align:-3px;margin-right:8px;}

  .tab-group{position:relative;}
  .submenu{display:none;position:absolute;left:100%;top:0;margin-left:6px;background:var(--paper-raised);border:1px solid var(--line);border-radius:8px;min-width:170px;padding:4px;z-index:40;box-shadow:0 4px 14px rgba(31,58,52,0.12);}
  .tab-group:hover .submenu{display:block;}
  @media (max-width:820px){ .submenu{left:0;top:100%;margin-left:0;margin-top:4px;} }
  .submenu-item{padding:8px 10px;border-radius:5px;font-size:13px;color:var(--ink-soft);cursor:pointer;display:flex;align-items:center;gap:8px;white-space:nowrap;}
  .submenu-item:hover{background:var(--sage-soft);color:var(--ink);}
  .submenu-item .count{margin-left:auto;}

  /*
   * Theme picker. Each swatch has to show ITS OWN colours, not the active
   * theme's — a `data-theme` attribute on an element inside the page doesn't
   * pick up the html[data-theme=…] blocks, so the four colours each swatch
   * needs are restated here. Keep them in step with the theme blocks above; a
   * swatch that lies about a theme is worse than no swatch.
   */
  /* Five across, two down. The count is fixed rather than auto-fit because
     auto-fit lands on three or four columns at some widths, and ten themes
     across three columns leaves a ragged last row — which is the thing this
     grid exists to avoid. Both 5 and 2 divide ten exactly. */
  /* The sample under the font dropdown. Always rendered in the current choice,
     because browsers vary in how faithfully they style <option> text and the
     dropdown alone can't be trusted to show the difference. */
  .font-sample{display:flex;flex-direction:column;gap:3px;margin-top:12px;padding:12px 14px;background:var(--subtle-bg);border:1px solid var(--line);border-radius:var(--radius);}
  .font-sample-display{font-family:var(--font-display);font-size:19px;font-weight:600;color:var(--ink);}
  .font-sample-ui{font-family:var(--font-ui);font-size:13px;color:var(--ink-soft);}
  .font-sample-mono{font-family:var(--font-mono);font-size:12px;color:var(--text-muted);}


  .block-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:10px;}
  @media (max-width:900px){ .block-grid{grid-template-columns:repeat(1,1fr);} }
  /*
   * The block preview draws its own treatment rather than inheriting the
   * active one — the same reason the theme swatches restate their colours.
   * These are literal values, not the --panel-* variables, or every card would
   * show whichever style is already applied.
   */
  .block-preview{display:block;background:var(--paper);border-radius:5px;padding:7px;height:52px;}
  .block-preview-panel{display:flex;flex-direction:column;gap:4px;height:100%;background:var(--paper-raised);border:1px solid var(--line);border-radius:6px;padding:7px 8px;}
  .block-preview-title{display:block;height:6px;width:52%;border-radius:2px;background:var(--ink-soft);}
  .block-preview-line{display:block;height:4px;border-radius:2px;background:var(--line-strong);}
  .block-preview-line.short{width:60%;}
  .block-preview[data-blocks="flat"] .block-preview-panel{background:var(--subtle-bg);border-color:transparent;}
  .block-preview[data-blocks="elevated"] .block-preview-panel{border-color:transparent;border-radius:9px;box-shadow:0 2px 6px rgba(31,58,52,0.16);}
  html[data-scheme="dark"] .block-preview[data-blocks="elevated"] .block-preview-panel{box-shadow:0 2px 7px rgba(0,0,0,0.5);}
  .block-preview[data-blocks="ruled"] .block-preview-panel{background:transparent;border:none;border-radius:0;padding:0;}
  .block-preview[data-blocks="ruled"] .block-preview-title{border-bottom:1px solid var(--line);padding-bottom:4px;height:10px;width:100%;background:transparent;border-radius:0;}
  .block-preview[data-blocks="compact"] .block-preview-panel{border-radius:3px;padding:4px 5px;gap:3px;}

  .theme-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;}
  @media (max-width:900px){ .theme-grid{grid-template-columns:repeat(2,1fr);} }
  .theme-card{display:flex;flex-direction:column;gap:6px;text-align:left;background:var(--paper-raised);border:1px solid var(--line);border-radius:8px;padding:10px;cursor:pointer;font:inherit;color:inherit;}
  .theme-card:hover{border-color:var(--line-strong);}
  /* 2px so the selected card doesn't shift by a pixel when it becomes selected. */
  .theme-card.selected{border:2px solid var(--sage);padding:9px;}
  .theme-card-name{font-size:13px;font-weight:500;color:var(--ink);}
  .theme-card-tick{color:var(--green-dot);}
  .theme-card-note{font-size:11px;color:var(--text-muted);line-height:1.4;}
  .theme-swatch{display:flex;gap:4px;height:42px;border-radius:5px;border:1px solid var(--line);padding:5px;background:var(--sw-paper);}
  .theme-swatch-rail{width:26px;border-radius:3px;background:var(--sw-rail);}
  .theme-swatch-card{flex:1;border-radius:3px;background:var(--sw-card);border:1px solid var(--sw-line);}
  .theme-swatch[data-theme="garden"]{--sw-paper:#F5F1E8;--sw-rail:#E4E9DF;--sw-card:#FFFFFF;--sw-line:#DAD3C2;}
  .theme-swatch[data-theme="ink"]{--sw-paper:#F4F4F1;--sw-rail:#DEE9E1;--sw-card:#FFFFFF;--sw-line:#DCDCD6;}
  .theme-swatch[data-theme="harbour"]{--sw-paper:#FFFFFF;--sw-rail:#000540;--sw-card:#F4F7FC;--sw-line:#CFE3F1;}
  .theme-swatch[data-theme="slate"]{--sw-paper:#1B1F24;--sw-rail:#26332B;--sw-card:#242A31;--sw-line:#333B45;}
  .theme-swatch[data-theme="dusk"]{--sw-paper:#15161F;--sw-rail:#223026;--sw-card:#1E2029;--sw-line:#2E3040;}
  .theme-swatch[data-theme="ember"]{--sw-paper:#1E1A17;--sw-rail:#2C3527;--sw-card:#292421;--sw-line:#3D3530;}
  .theme-swatch[data-theme="newsprint"]{--sw-paper:#F7F7F5;--sw-rail:#E2E7E2;--sw-card:#FFFFFF;--sw-line:#DEDEDA;}
  .theme-swatch[data-theme="sandstone"]{--sw-paper:#F4EFE6;--sw-rail:#E3E8DC;--sw-card:#FFFCF7;--sw-line:#E2D9CB;}
  .theme-swatch[data-theme="obsidian"]{--sw-paper:#0E0F11;--sw-rail:#1D2A21;--sw-card:#17191C;--sw-line:#282B30;}
  .theme-swatch[data-theme="forest"]{--sw-paper:#101A13;--sw-rail:#213022;--sw-card:#19251C;--sw-line:#2A3A2C;}
  .theme-swatch[data-theme="midnight"]{--sw-paper:#000540;--sw-rail:#0A0F4B;--sw-card:#020234;--sw-line:#2E3578;}
  /* Physare keeps the yellow for what is ACTIVE; the group heads are all one
     grey so the rail is not two yellow bars and three grey ones. */
  html[data-theme="physare"] .nav-category-head{--cat-tint:var(--subtle-bg);--cat-ink:var(--ink-soft);--cat-accent:var(--line-strong);}
  html[data-theme="physare"] .nav-category-head.open,html[data-theme="physare"] .nav-category.expanded .nav-category-head{--cat-ink:var(--ink);}
  .theme-swatch[data-theme="physare"]{--sw-paper:#0B0B0C;--sw-rail:#141416;--sw-card:#1A1A1C;--sw-line:#3A3A3E;}

  .nav-category{display:flex;flex-direction:column;}
  /* A group head is a filled band; the tabs inside it are not. That contrast
     is the whole point — the rail carries seven groups and they were reading
     as just more tabs. --cat-tint/--cat-accent are set per group below, so
     this rule never needs to know which colour it is wearing. */
  .nav-category-head{display:flex;justify-content:space-between;align-items:center;padding:7px 10px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:0.06em;background:var(--cat-tint,transparent);color:var(--cat-ink,var(--text-muted));cursor:pointer;border-radius:var(--radius);transition:box-shadow 0.12s ease;}
  /* Hover draws a ring rather than changing the fill: a second tint per
     family would be seven more tokens in two themes, and the ring reads as
     affordance without shifting the colour somebody just learned. */
  .nav-category-head:hover{box-shadow:inset 0 0 0 1px var(--cat-accent,var(--line-strong));}
  /* Tints are keyed on the GROUP's configured tint (config/platform.php
     nav_groups), one per token family, so a new group picks a family rather
     than editing this file. Text on a tint comes from the same hue. */
  .nav-category-head[data-tint="sage"]{--cat-tint:var(--sage-soft);--cat-ink:var(--sage-ink);--cat-accent:var(--sage);}
  .nav-category-head[data-tint="mustard"]{--cat-tint:var(--mustard-soft);--cat-ink:var(--mustard-ink);--cat-accent:var(--mustard);}
  .nav-category-head[data-tint="blue"]{--cat-tint:var(--blue-soft);--cat-ink:var(--blue-ink);--cat-accent:var(--blue);}
  .nav-category-head[data-tint="violet"]{--cat-tint:var(--violet-soft);--cat-ink:var(--violet-ink);--cat-accent:var(--violet);}
  .nav-category-head[data-tint="brick"]{--cat-tint:var(--brick-soft);--cat-ink:var(--brick-ink);--cat-accent:var(--brick);}
  .nav-category-head[data-tint="plum"]{--cat-tint:var(--plum-soft);--cat-ink:var(--plum-ink);--cat-accent:var(--plum);}
  .nav-category-head[data-tint="teal"]{--cat-tint:var(--teal-soft);--cat-ink:var(--teal-ink);--cat-accent:var(--teal);}
  /* Neutral, not an eighth colour family: Admin is chrome rather than a
     feature area, so reading as grey is apt rather than a compromise. */
  .nav-category-head[data-tint="neutral"]{--cat-tint:var(--subtle-bg);--cat-ink:var(--ink-soft);--cat-accent:var(--line-strong);}
  .nav-caret{transition:transform 0.15s ease;font-size:10px;}
  .nav-caret.open{transform:rotate(90deg);}
  .nav-category-body{display:none;flex-direction:column;gap:2px;}
  .nav-category-body.open{display:flex;}
  @media (max-width:820px){ .nav-category-body.open{flex-direction:row;flex-wrap:wrap;} }

  .dropdown-widget{position:relative;}
  .dropdown-panel{display:none;position:absolute;left:100%;top:0;margin-left:6px;background:var(--paper-raised);border:1px solid var(--line);border-radius:8px;width:320px;max-height:460px;overflow-y:auto;z-index:40;box-shadow:0 4px 14px rgba(31,58,52,0.14);padding:12px;}
  .dropdown-panel.open{display:block;}
  @media (max-width:820px){ .dropdown-panel{left:auto;right:0;top:100%;margin-left:0;margin-top:6px;} }
  .dropdown-panel-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;gap:8px;}
  .dropdown-panel-title{font-family:var(--font-display);font-size:15px;font-weight:600;}

  .rail-foot{font-size:11px;color:var(--text-muted);font-family:var(--font-mono);}
  @media (max-width:820px){ .rail-foot{display:none;} }

  .main{padding:32px 36px 60px;}
  @media (max-width:820px){ .main{padding:20px 18px 48px;} }

  .page-head{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;margin-bottom:22px;flex-wrap:wrap;}
  .page-title{font-family:var(--font-display);font-size:28px;font-weight:600;margin:0;}
  .page-sub{font-size:13px;color:var(--ink-soft);margin:4px 0 0;}
  .back-link{font-size:12px;color:var(--ink-soft);cursor:pointer;display:inline-flex;align-items:center;gap:4px;margin-bottom:10px;}
  .back-link:hover{color:var(--ink);}

  .btn{font-family:var(--font-ui);font-size:13px;font-weight:500;padding:9px 16px;border-radius:var(--btn-radius);border:1px solid var(--btn-bg);background:var(--btn-bg);color:var(--btn-ink);cursor:pointer;}
  .btn:hover{background:var(--btn-hover);border-color:var(--btn-hover);}
  .btn.ghost{background:transparent;color:var(--ink);border-color:var(--line-strong);}
  .btn.ghost:hover{background:var(--paper-raised);}
  .btn.danger{background:transparent;color:var(--brick);border-color:var(--brick);}
  .btn.danger:hover{background:var(--brick-soft);}
  .btn.small{padding:5px 10px;font-size:12px;}
  .btn:disabled{opacity:0.4;cursor:default;}

  .collapse-toggle{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;padding:0;border-radius:var(--radius);border:1px solid var(--line-strong);background:transparent;color:var(--ink);cursor:pointer;}
  .collapse-toggle:hover{background:var(--paper-raised);}
  .collapse-arrow{display:inline-block;font-size:13px;line-height:1;transition:transform .15s ease;transform:rotate(-90deg);}
  .collapse-arrow.expanded{transform:rotate(0deg);}

  .toolbar{display:flex;gap:10px;align-items:center;margin:16px 0 18px;flex-wrap:wrap;}
  input[type=text], input[type=email], input[type=password], input[type=tel], input[type=date], input[type=time], input[type=number], input[type=url], textarea, select{
    font-family:var(--font-ui);font-size:13px;padding:8px 10px;border:1px solid var(--line-strong);border-radius:var(--radius);background:var(--paper-raised);color:var(--ink);
  }
  textarea{font-family:var(--font-ui);resize:vertical;}
  input:focus, textarea:focus, select:focus{outline:none;border-color:var(--mustard);box-shadow:0 0 0 2px var(--mustard-soft);}
  input.search{
    flex:1;min-width:180px;padding-right:32px;
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23808080' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'/%3E%3C/svg%3E");
    background-repeat:no-repeat;background-position:right 10px center;background-size:16px 16px;
  }

  .price-wrap{display:flex;align-items:center;border:1px solid var(--line-strong);border-radius:var(--radius);background:var(--paper-raised);padding:0 8px;}
  .price-wrap span{color:var(--ink-soft);font-size:13px;}
  .price-wrap input{border:none;background:transparent;padding:8px 4px;width:100%;-webkit-appearance:none;appearance:none;}
  .price-wrap input:focus{outline:none;box-shadow:none;border:none;}
  .price-wrap:focus-within{border-color:var(--mustard);box-shadow:0 0 0 2px var(--mustard-soft);}
  .price-wrap input::-webkit-inner-spin-button, .price-wrap input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0;}
  .price-wrap input[type=number]{-moz-appearance:textfield;}

  .modal-header-row{display:flex;justify-content:space-between;align-items:center;margin-bottom:18px;gap:12px;flex-wrap:wrap;}
  .modal-header-row h2{margin:0;}
  .inline-checkbox{display:flex;align-items:center;gap:6px;font-size:13px;font-weight:500;cursor:pointer;white-space:nowrap;}
  .inline-checkbox input{width:16px;height:16px;padding:0;}
  /* A bare class does NOT beat `.field label` / `.field input` (both 0,1,1),
     and those come LATER in this file, so inside any .field the label stayed
     display:block and the checkbox stretched to width:100% - rendering as a
     full-width box ABOVE its own text, which is why it appeared to land in
     the middle of a word. Broken in every .field since it shipped. The
     descendant selector is (0,2,1) and names every property the generic
     rules set, since whichever is missed keeps leaking through. Same trap
     the .gs-box .gs-input rule documents. */
  .field .inline-checkbox{display:flex;align-items:center;gap:6px;margin-bottom:0;
    font-size:13px;color:var(--ink);font-weight:500;cursor:pointer;white-space:nowrap;}
  .field .inline-checkbox input{width:16px;height:16px;padding:0;margin:0;}
  .checkbox-group{display:flex;gap:14px;flex-wrap:wrap;}


  .panel{background:var(--panel-bg);border:var(--panel-border);border-radius:var(--panel-radius);padding:var(--panel-pad);box-shadow:var(--panel-shadow);}
  .card-list{display:flex;flex-direction:column;border:var(--panel-border);border-radius:var(--panel-radius);overflow:hidden;background:var(--panel-bg);box-shadow:var(--panel-shadow);}
  .row{display:grid;gap:12px;align-items:center;padding:var(--row-pad);border-bottom:1px solid var(--line);cursor:pointer;}
  .row:last-child{border-bottom:none;}
  .row:hover{background:var(--subtle-bg);}
  .row.head{font-family:var(--font-mono);font-size:10px;text-transform:uppercase;letter-spacing:0.06em;color:var(--text-muted);cursor:default;}
  .row.head:hover{background:transparent;}
  .row.head .sortable{cursor:pointer;user-select:none;}
  /* Fixed-width row grids for figure tables: a name column, then numbers.
     Core rather than the reports module, because the Back Office and the
     portal draw their tables with them and a page without reports (the
     portal loads no module stylesheet) stacked every cell on its own line. */
  .row.rpt-3{grid-template-columns:minmax(0,2fr) minmax(0,1fr) minmax(0,1fr);}
  .row.rpt-4{grid-template-columns:minmax(0,2fr) minmax(0,1fr) minmax(0,1fr) minmax(0,1.4fr);}
  .row.rpt-5{grid-template-columns:minmax(0,2fr) minmax(0,1fr) minmax(0,1fr) minmax(0,1fr) minmax(0,1.4fr);}
  .row.rpt-6{grid-template-columns:minmax(0,2.2fr) minmax(0,1fr) minmax(0,1fr) minmax(0,1fr) minmax(0,1.1fr) minmax(0,1.4fr);}
  .row.head .sortable:hover{color:var(--ink);}
  /* Every fraction is minmax(0,...) because each .row is its own grid
     container, so a bare fr floors at that row's min-content width and a long
     company name resolves its columns differently from its neighbours. */

  /* A toolbar button is a TAP TARGET on a phone, and .btn.small renders 25px
     high, under the 30px floor the mobile navigation had to learn the hard
     way. Scoped to .toolbar rather than every .btn.small: the ones inside
     table rows are fine as they are, and growing those would make every row
     taller. Week navigation and Day/Week toggles are what somebody actually
     presses repeatedly with a thumb. */
  @media (max-width:760px){
    /*
     * The 30px floor, and it was scoped to TOOLBARS only.
     *
     * `.btn.small` renders 25px, and the buttons inside a list row are the
     * most-pressed controls on a phone: Mark delivered, Generate invoice and
     * Mark paid all sat under the floor the toolbar buttons were raised to.
     * Extended to the action cells, which is where the thumb actually goes.
     *
     * Still NOT every .btn.small - the ones inside dense table rows stay as
     * they are, because growing those makes every row taller and the standing
     * decision there was that a mouse target is fine at that size.
     */
    .toolbar .btn.small,.toolbar .seg button,
    .row .order-actions .btn.small,.order-actions .btn.small{min-height:32px;padding-top:7px;padding-bottom:7px;}
  }


  .primary-text{font-weight:500;}
  .sub-text{color:var(--ink-soft);font-size:13px;}
  .micro-text{color:var(--text-muted);font-size:11px;}
  .empty{padding:40px 20px;text-align:center;color:var(--ink-soft);}
  .empty h3{font-family:var(--font-display);font-size:19px;margin:0 0 6px;color:var(--ink);}
  .empty p{font-size:13px;margin:0 0 16px;}

  /* nowrap because a status label broken across two lines reads as two
     words rather than one state, and it makes its row taller than its
     neighbours, which is what makes a list look broken. */
  .badge{display:inline-block;font-size:11px;font-weight:500;padding:3px 9px;border-radius:20px;white-space:nowrap;}
  .badge.pending{background:var(--mustard-soft);color:#8A5A1E;}
  .badge.fulfilled{background:var(--sage-soft);color:#3E5340;}
  .badge.paid{background:var(--green-dot);color:#12281A;}
  .badge.consigned{background:var(--blue-soft);color:#2E5570;}
  /* Where a record moves through stages, in the same tinted language as the
     badges above: soft ground, text from the SAME hue. Deliberately generic
     rather than one family per feature, because several list screens
     ask the same four questions and two copies of these tints would
     drift. Waiting shares Pending's mustard; progress takes blue, the only
     family here that reads as movement rather than warning or success; and
     muted covers every way a record stops being live. */
  .badge.st-waiting{background:var(--mustard-soft);color:#8A5A1E;}
  .badge.st-progress{background:var(--blue-soft);color:#2E5570;}
  .badge.st-done{background:var(--sage-soft);color:#3E5340;}
  .badge.st-muted{background:var(--subtle-bg);color:var(--text-muted);}
  .badge.customer-created{background:var(--mustard-soft);color:#7A4E13;}
  .badge.sentiment-not{background:var(--brick-soft);color:#8C332E;}
  .badge.sentiment-follow{background:var(--blue-soft);color:#2E5570;}
  .company-tag-pill{position:relative;padding-right:16px;}
  .company-tag-remove{display:none;position:absolute;right:4px;top:50%;transform:translateY(-50%);cursor:pointer;font-weight:600;line-height:1;}
  .company-tag-pill:hover .company-tag-remove{display:inline;}
  .company-tag-pill:hover{padding-right:20px;}
  .badge.sentiment-nurture{background:var(--sage-soft);color:#3E5340;}
  .badge.sentiment-connected{background:var(--green-dot);color:#12281A;}
  .badge.warn{background:var(--brick-soft);color:#8C332E;}
  .badge.neutral{background:var(--subtle-bg);color:var(--text-muted);box-shadow:inset 0 0 0 1px var(--line);}
  .icon-chip{display:inline-flex;align-items:center;gap:5px;font-size:12px;color:var(--ink-soft);}

  .dot{display:inline-block;width:9px;height:9px;border-radius:50%;margin-right:9px;flex-shrink:0;}
  .dot.customer{background:var(--green-dot);}
  .dot.prospect{background:var(--yellow-dot);}
  .dot.lead{background:var(--blue);}
  .dot.inv-paid{background:var(--green-dot);}
  .dot.inv-unpaid{background:var(--yellow-dot);}
  .dot.inv-overdue{background:var(--brick);}
  .dot.inv-voided{background:var(--text-muted);}
  /* Paid but not yet cleared — distinct from both paid and unpaid, because it
     is genuinely neither. */
  .dot.inv-processing{background:var(--sage);}
  .name-with-dot{display:flex;align-items:center;}

  .section{margin-top:30px;}
  /*
   * The dashboard is a TWELVE COLUMN GRID, not a stack.
   *
   * Every panel was full width and most were mostly empty: Sales used about
   * 300px of a 1100px panel and Invoices about 340px. A panel is now only as
   * wide as its contents need, and Sales sits beside Invoices.
   *
   * `gap` replaces the old margin-top, so a panel carries no spacing of its
   * own and the grid owns the rhythm. A panel that kept its own margin would
   * push its whole row down and break the alignment.
   */
  .dashboard-grid{ display:grid; grid-template-columns:repeat(12,minmax(0,1fr)); gap:18px; align-items:stretch; }
  .dashboard-section{background:var(--paper-raised);border:1px solid var(--line);border-radius:10px;padding:20px 22px;grid-column:span 12;}

  /*
   * align-items:STRETCH, so panels sharing a row match heights.
   *
   * The first version used `start`, on the reasoning that a panel saying
   * "nothing due" should be one line tall rather than as tall as the table
   * beside it. That was wrong on a grid: ragged bottoms read as broken, where
   * an even row reads as deliberate. The cost is honest -- a short panel now
   * carries empty space -- and it is the better trade at this width.
   */

  /*
   * A column of two short panels beside a tall one.
   *
   * The last child grows, so the stack ends flush with the panel beside it
   * rather than leaving the hole at the bottom that stretching one panel was
   * meant to solve. It is a grid CHILD, so it takes the same data-span rules
   * as a panel and needs no width logic of its own.
   */
  .dashboard-stack{ display:flex; flex-direction:column; gap:18px; grid-column:span 12; min-width:0; }
  .dashboard-stack > .dashboard-section:last-child{ flex:1; }

  /*
   * Customize mode. Each panel gets a control bar (drag handle, arrows, its
   * name, size, hide) and its own contents go inert, so a click meant for a
   * control never opens a record. The handle carries touch-action:none, or
   * a finger dragging it would scroll the page instead.
   */
  .dashboard-grid.is-editing > .dashboard-section{ border:1.5px dashed var(--line-strong); }
  .dashboard-grid.is-editing .dash-panel-body{ pointer-events:none; opacity:.72; }
  /* Drag and drop: the whole panel is the grip, a lifted copy follows the
     pointer, and a dashed gap marks where it will land. */
  .dashboard-grid.is-editing > .dashboard-section{ cursor:grab; user-select:none; }
  .dashboard-section.is-placeholder{ border:2px dashed var(--ink-soft) !important; background:transparent; box-shadow:none; }
  .dashboard-section.is-placeholder > *{ visibility:hidden; }
  .dashboard-section.is-placeholder.is-new{ min-height:96px; }
  .dash-ghost{ position:fixed; left:0; top:0; z-index:1000; pointer-events:none; margin:0; overflow:hidden;
    box-shadow:0 18px 40px rgba(0,0,0,.28); opacity:.94; border-radius:10px; }
  .dash-ghost.dash-tray-chip{ background:var(--paper-raised); }
  body.dash-dragging, body.dash-dragging *{ cursor:grabbing !important; user-select:none; }
  #dashboard-tray.is-drop-target .dash-tray{ border:2px dashed var(--brick); background:var(--brick-soft); }
  .dash-tray-hint{ margin-left:8px; color:var(--text-muted); font-size:11px; }
  .dash-tray-chip{ cursor:grab; touch-action:none; }
  .dash-controls{ display:flex; align-items:center; gap:6px; margin:-8px -8px 12px; padding:6px 8px;
    border-radius:8px; background:var(--paper); font-size:12px; color:var(--ink-soft); }
  .dash-controls button{ font:inherit; font-size:12px; color:var(--ink); background:transparent; border:1px solid var(--line-strong);
    border-radius:6px; padding:3px 7px; cursor:pointer; line-height:1.2; }
  .dash-controls button:hover{ background:var(--paper-raised); }
  .dash-handle{ cursor:grab; touch-action:none; }
  .dash-handle:active{ cursor:grabbing; }
  .dash-title{ font-weight:600; color:var(--ink); min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
  .dash-sizes{ display:inline-flex; margin-left:auto; }
  .dash-sizes button{ border-radius:0; margin-left:-1px; }
  .dash-sizes button:first-child{ border-radius:6px 0 0 6px; margin-left:0; }
  .dash-sizes button:last-child{ border-radius:0 6px 6px 0; }
  .dash-sizes button.on{ background:var(--ink); border-color:var(--ink); color:var(--paper); }
  .dash-hide{ margin-left:4px; }
  .dash-controls .dash-hide:only-of-type{ margin-left:auto; }
  .dash-arrow{ display:none; }
  .dash-tray{ margin-top:18px; padding:14px 16px; border:1px solid var(--line); border-radius:10px; background:var(--paper-raised); }
  .dash-tray-head{ font-size:12px; color:var(--ink-soft); margin-bottom:10px; }
  .dash-tray-chip{ font:inherit; font-size:13px; padding:6px 12px; margin:0 6px 6px 0; border-radius:20px;
    border:1px solid var(--line-strong); background:transparent; color:var(--ink); cursor:pointer; }
  .dash-tray-chip:hover{ background:var(--paper); }
  /* A count tile: one number, the whole panel a button to the list behind it. */
  .dash-tile{ display:flex; flex-direction:column; align-items:flex-start; gap:4px; width:100%; font:inherit; color:inherit;
    background:none; border:none; padding:0; text-align:left; cursor:pointer; }
  /* A tile keeps its own height beside a tall panel rather than stretching to match it. */
  .dashboard-section.is-tile{ padding:16px 20px; align-self:start; }
  /* On a phone the arrows replace the handle: dragging inside a long scrolling page is fiddly. */
  @media (max-width:759px){
    .dash-handle{ display:none; }
    .dash-arrow{ display:inline-block; }
    .dash-controls button{ min-height:34px; min-width:34px; }
    .dash-sizes{ display:none; }
    .dash-controls .dash-hide{ margin-left:auto; }
  }

  /*
   * Inside the stack, metric cards are pinned to TWO columns rather than left
   * to auto-fit.
   *
   * The stack column is about 296px at 1250px wide, so auto-fit's 140px
   * minimum plus the gap does not fit two and drops to one -- which put On
   * hand above Consignment at exactly the width where the row had space for
   * them side by side. The panel always carries two cards, so the count is
   * known and worth stating rather than inferring from arithmetic that changes
   * with the viewport.
   *
   * Above the mobile breakpoint only: at 375px two columns would be about
   * 130px each, which is narrower than the figures want.
   */
  @media (min-width:760px){
    .dashboard-stack .metric-grid{ grid-template-columns:repeat(2,minmax(0,1fr)); }
  }

  @media (min-width:1100px){
    .dashboard-stack[data-span="4"]{ grid-column:span 4; }
    .dashboard-section[data-span="4"]{ grid-column:span 4; }
    .dashboard-section[data-span="6"]{ grid-column:span 6; }
    .dashboard-section[data-span="8"]{ grid-column:span 8; }
  }

  /*
   * The middle band, 760 to 1100: the halves pair up but the thirds do not,
   * because a third of a 900px page is about 240px and narrower than three
   * metric cards can sit in. Reading order survives either way, since the grid
   * places in source order.
   *
   * `data-mid-span` is how a panel says it needs the whole row HERE and not at
   * full width. Two do: Production needed, whose eight of twelve leaves a
   * four-wide remainder nothing else can fill, and Marketing, the odd one out
   * among five half-width panels -- five can never pair evenly, so exactly one
   * has to go wide. Without it Marketing sat alone with 306px beside it.
   */
  @media (min-width:760px) and (max-width:1099px){
    .dashboard-section[data-span="4"],
    .dashboard-stack[data-span="4"],
    .dashboard-section[data-span="6"]{ grid-column:span 6; }

    .dashboard-section[data-mid-span="12"],
    .dashboard-stack[data-mid-span="12"]{ grid-column:span 12; }
  }

  /* One line under a rule, rather than a row of filled pills.
     Two pills plus a trailing sentence read as three unrelated fragments; a
     severity dot, a count and a label scan left to right as one list. The rule
     is what separates it from the grid below without needing a card. */
  .alerts-strip{display:flex;align-items:center;gap:18px;flex-wrap:wrap;
    margin-bottom:20px;padding-bottom:12px;border-bottom:1px solid var(--line);}
  .alert-item{display:inline-flex;align-items:center;gap:7px;border:0;background:transparent;
    font:inherit;font-size:13px;color:var(--ink-soft);cursor:pointer;padding:0;}
  .alert-item:hover{color:var(--ink);}
  /* The dot carries the severity, so the label keeps the page's own text
     colour and the line reads as prose rather than as a warning banner. */
  .alert-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0;}
  .alert-item.overdue .alert-dot{background:var(--brick);}
  .alert-item.pending .alert-dot{background:var(--mustard);}
  .alert-item .alert-count{font-family:var(--font-display);font-weight:600;font-size:14px;color:var(--ink);}
  .alert-item.overdue .alert-count{color:var(--brick);}
  .alert-item.pending .alert-count{color:var(--mustard);}
  .alert-pill{display:flex;align-items:center;gap:7px;background:var(--paper-raised);border:1px solid var(--line);border-radius:20px;padding:7px 14px;font-size:12px;color:var(--ink-soft);cursor:pointer;}
  .alert-pill:hover{border-color:var(--line-strong);}
  .alert-pill .alert-count{font-family:var(--font-display);font-weight:600;font-size:14px;color:var(--ink);}
  .alert-pill.warn{border-color:var(--brick);}
  .alert-pill.warn .alert-count{color:var(--brick);}
  /* Stated rather than merely absent: an empty strip reads as a failed load.
     Pushed to the end of the line, because what is clear is the least urgent
     thing on it and should not sit between two things that are not. */
  .alerts-clear{display:flex;align-items:center;font-size:12px;color:var(--text-muted);margin-left:auto;}


  .section-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;}
  .section-title{font-family:var(--font-display);font-size:18px;font-weight:600;margin:0;}

  /*
   * 140px, not 160.
   *
   * A half-width panel gives the grid about 497px, and three columns at 160
   * need 504 -- seven pixels short, so a three-card panel wrapped 2-then-1 and
   * orphaned the third card beside a hole. That only appeared once the
   * dashboard went to two columns: at full width there was always room.
   *
   * 140 was measured rather than guessed: three fit in 444, and the longest
   * label in use ("Accounts receivable") still sits on one line.
   */
  .metric-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;margin-top:16px;}
  .metric-card{background:var(--paper-raised);border:1px solid var(--line);border-radius:8px;padding:14px 16px;}
  .metric-card.clickable{cursor:pointer;}
  .metric-card.clickable:hover{border-color:var(--line-strong);}
  .metric-label{font-size:12px;color:var(--ink-soft);display:flex;justify-content:space-between;align-items:center;gap:6px;}
  .metric-value{font-family:var(--font-display);font-size:22px;font-weight:600;margin-top:2px;}
  /* Ageing bands for the Receivables report belong to a reports module; not carried. */

  .metric-value.warn{color:var(--brick);}


  table.data{width:100%;border-collapse:collapse;background:var(--paper-raised);border:1px solid var(--line);border-radius:8px;overflow:hidden;font-size:13px;}
  table.data th{text-align:left;font-family:var(--font-mono);font-size:10px;text-transform:uppercase;letter-spacing:0.05em;color:var(--text-muted);font-weight:400;padding:9px 12px;border-bottom:1px solid var(--line);}
  table.data td{padding:9px 12px;border-bottom:1px solid var(--line);}
  table.data tr:last-child td{border-bottom:none;}
  table.data tr:hover td{background:var(--subtle-bg);}

  .filter-menu{position:relative;}
  /* Capped and scrollable: the companies panel carries eleven controls, and an
     uncapped one runs off the bottom of a laptop screen with the Clear button
     on the part nobody can reach. */
  .filter-menu-panel{display:none;position:absolute;top:100%;right:0;margin-top:6px;background:var(--paper-raised);border:1px solid var(--line);border-radius:8px;box-shadow:0 4px 10px rgba(0,0,0,0.1);z-index:60;padding:12px;min-width:272px;max-width:300px;max-height:min(70vh,560px);overflow-y:auto;flex-direction:column;gap:2px;}
  /* Grouped rather than one unbroken column of fields: the headings are what
     let somebody find "how many sites" without reading every label. */
  .filter-section{display:flex;flex-direction:column;gap:7px;padding:9px 0;border-bottom:1px solid var(--line);}
  .filter-section:last-of-type{border-bottom:none;}
  .filter-section:first-of-type{padding-top:2px;}
  .filter-section-head{font-size:10px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--ink-soft);opacity:.75;}
  .filter-section .field{margin:0;gap:3px;}
  .filter-section .field label{font-size:11px;}
  .filter-section .sub-text{font-size:11px;line-height:1.35;}
  /* A min/max pair on one line. Two stacked full-width fields for what is
     obviously one range is what made the old panel need scrolling. */
  .filter-range{display:grid;grid-template-columns:minmax(0,1fr) auto minmax(0,1fr);align-items:center;gap:5px;}
  .filter-range-input{width:100%;min-width:0;font-size:12px;padding:5px 7px;}
  .filter-range-sep{font-size:10px;color:var(--ink-soft);opacity:.7;}
  /* Segmented control. Replaces the "Show archived instead" checkbox, which sat
     alone above a column of dropdowns and read as an afterthought. Active and
     Archived are two views of one list, and a two-state control says that where
     a lone checkbox does not. */
  .seg{display:flex;border:1px solid var(--line);border-radius:6px;overflow:hidden;background:var(--paper);}
  .seg button{flex:1 1 0;min-width:0;border:none;background:transparent;color:var(--ink-soft);
    font-size:11px;font-weight:600;padding:5px 6px;cursor:pointer;white-space:nowrap;
    border-right:1px solid var(--line);font-family:inherit;}
  .seg button:last-child{border-right:none;}
  /* `.seg button` is flex:1 1 0 with white-space:nowrap, so a label longer than
     its share of the row overflows the button rather than wrapping: "Email
     campaigns" was clipped mid-word. Where the labels are words rather than a
     two-state toggle, the buttons size to content instead. The
     `.auto-group-head .seg` precedent, and the reason that rule exists too. */
  /* The buttons hug their labels, so the CONTAINER has to as well: `.seg` is
     display:flex, which is block-level and fills its parent, leaving a long
     empty box trailing off to the right of the last button. Only bites where
     a seg is a direct child of the page; the three inside a `.section-head`
     are flex items already, and a flex item's inline-flex is blockified, so
     this leaves them exactly as they were. */
  .seg.seg-auto{display:inline-flex;}
  .seg.seg-auto button{flex:0 0 auto;}
  .seg button:hover{background:var(--paper-raised);color:var(--ink);}
  .seg button.active{background:var(--sage-soft);color:var(--sage-ink);}
  .filter-menu-badge{display:inline-block;background:var(--blue);color:#fff;border-radius:10px;font-size:11px;line-height:1;padding:2px 6px;margin-left:4px;}
  .filter-menu-actions{border-top:1px solid var(--line);padding-top:8px;margin-top:2px;}

  .combo{position:relative;}
  .combo-control{position:relative;}
  /* The right padding has to clear the magnifier and chevron sitting on top
     of the input, and it must be written as a DESCENDANT selector.

     `.combo-input` alone is (0,1,0) and the generic
     `input[type=text], …` control rule is (0,1,1), so that rule's `padding`
     SHORTHAND won and the 56px was silently discarded: every combobox in
     the app had 10px of right padding, and any label long enough to fill
     the field ran underneath the icons. Reported on a quote's service line
     as "Custom Mold Fee (Unit, flat rat" with the chevron sitting in the
     text. The FOURTH time this trap has bitten, after .sp-thumb,
     .gs-box .gs-input and .field .inline-checkbox.

     The whole padding is named rather than just the right side, so the
     shorthand cannot come back and reset the other three. */
  .combo-control .combo-input{width:100%;padding:8px 56px 8px 10px;}
  .combo-icons{position:absolute;right:8px;top:50%;transform:translateY(-50%);display:flex;gap:4px;color:var(--ink-soft);cursor:pointer;}
  .combo-panel{display:none;position:absolute;top:100%;left:0;right:0;margin-top:4px;background:var(--paper-raised);border:1px solid var(--line);border-radius:8px;box-shadow:0 4px 10px rgba(0,0,0,0.1);z-index:70;max-height:160px;overflow-y:auto;}
  .combo-option{padding:8px 10px;font-size:13px;cursor:pointer;}
  .combo-option:hover{background:var(--sage-soft);}
  .combo-group-label{padding:6px 10px 2px;font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted);font-family:var(--font-mono);}
/* A team heading that is ALSO a choice, with its people indented under it.
   Weight is what separates the two, not the indent alone: a person and a team
   must never read as the same kind of thing. */
.combo-option.combo-opt-team{font-weight:600;}
.combo-option.combo-opt-member{padding-left:26px;}
/* Kept on screen only because one of its people matched the search. */
.combo-option.combo-opt-context{color:var(--text-muted);font-weight:500;}
/* Heads the people on no team. Not a thing work can be assigned to. */
.combo-option.combo-opt-label{color:var(--text-muted);cursor:default;}
.combo-option.combo-opt-label:hover{background:transparent;}
  /* Checkbox filter dropdown: several real filters are "any of several"
     (Nick's or Sara's, overdue or due soon), which a single <select> can only
     ever express one answer to. */
  /* Multi-select filter. The control itself is a real .combo (same input, same
     magnifier and chevron), so it reads as the control people already know from
     every single-select filter; only the panel differs, carrying checkboxes. */
  .fms{position:relative;}
  /* EVERY rule here is scoped under .fms — a bare .fms-option (0,1,0) LOSES to
     the global `.field label` (0,1,1), which sets display:block and a bottom
     margin. That is exactly what broke the first version: the checkbox and its
     text stacked with a gap between every row. */
  .fms .fms-panel{display:none;position:absolute;top:100%;left:0;right:0;margin-top:4px;
    background:var(--paper-raised);border:1px solid var(--line);border-radius:8px;
    box-shadow:0 4px 10px rgba(0,0,0,0.1);z-index:70;max-height:220px;overflow-y:auto;padding:4px;}
  .fms label.fms-option{display:flex;align-items:center;gap:8px;margin:0;padding:6px 8px;
    font-size:12px;font-weight:400;color:var(--ink);cursor:pointer;border-radius:5px;line-height:1.3;}
  .fms label.fms-option:hover{background:var(--sage-soft);}
  .fms label.fms-option input[type="checkbox"]{margin:0;flex:0 0 auto;width:14px;height:14px;
    accent-color:var(--sage-ink);cursor:pointer;}
  .fms .fms-option-label{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  .combo-empty{padding:10px;font-size:12px;color:var(--ink-soft);}
  .combo-disabled .combo-control .combo-input{background:var(--subtle-bg);color:var(--text-muted);cursor:not-allowed;}
  .combo-disabled .combo-icons{cursor:not-allowed;opacity:0.5;}


  .overlay{position:fixed;inset:0;background:rgba(31,58,52,0.35);display:flex;align-items:flex-start;justify-content:center;padding:40px 16px;overflow-y:auto;z-index:100;}
  .modal{background:var(--paper-raised);border-radius:10px;padding:24px 26px;width:100%;max-width:560px;border:1px solid var(--line);}
  .modal h2{font-family:var(--font-display);font-size:20px;margin:0 0 18px;font-weight:600;}
  .field{margin-bottom:14px;position:relative;}
  .field label{display:block;font-size:12px;color:var(--ink-soft);margin-bottom:5px;font-weight:500;}
  .field input, .field textarea, .field select{width:100%;}
  .field-row{display:grid;grid-template-columns:1fr 1fr;gap:12px;}
  .field-row.three{grid-template-columns:1fr 1fr 1fr;}


  /* ---------- wide desktop forms ---------- */
  /*
   * Editing a company meant scrolling a long single column inside a 560px box
   * on a 1920px screen. **Width alone fixes nothing**: measured, the company
   * form's 15 fields are 1197px tall at 560px AND at 900px, because a stacked
   * column is a stacked column. Pairing is what removes the scroll (1197 ->
   * 801, which fits the ~820px a 900px-tall laptop gives); width is only what
   * makes pairing possible.
   *
   * Mobile-first, the `.auto-builder` precedent: ONE column below 900px, so
   * this work is provably inert on a phone. That is deliberate rather than
   * cautious - at 375px an existing `.field-row.three` already renders 89px
   * fields, so anything pairing more on a phone would make it worse.
   */
  .modal.wide{max-width:900px;}

  .form-grid{display:grid;grid-template-columns:1fr;gap:0 14px;}
  @media (min-width:900px){
    .form-grid{grid-template-columns:1fr 1fr;}
  }
  /* A field opts OUT to full width. Per the owner's choice that is text areas:
     each full-width field costs 34-68px of height, and four of them put the
     company form back over the fold. An existing `.field-row` spans too, so no
     form has to be un-paired first. */
  .form-grid > .full,
  .form-grid > .field-row{grid-column:1 / -1;}
  /* Or a long value refuses to shrink and pushes its column past its track:
     the `.auto-col` lesson, and the condition rows before it. */
  .form-grid > *{min-width:0;}
  /* Slightly tighter than a stacked form: with two columns the eye travels
     less vertically, so 14px between rows reads as loose. */
  @media (min-width:900px){
    .form-grid .field{margin-bottom:11px;}
    /* A variation is 12 fields. Stacked three-up that is five sub-rows EACH,
       which is why a three-variation product ran past two screens. At 900px
       the selling fields fit four across and the six measurements fit six
       across, taking a variation from five rows to three. Below 900px both
       stay three-up, exactly as they were. */
    /* Named with BOTH classes: `.field-row.three` is (0,2,0) and a lone class
       is (0,1,0), so the bare selector loses on specificity no matter where it
       sits in the file. The fourth time this trap has bitten here. */
    /* Three columns for a form whose fields are all short: an employee is 20
       of them, and at two columns that is ten rows however wide the modal is. */
    .form-grid.tri{grid-template-columns:repeat(3, minmax(0,1fr));}
  /* A section BLOCK is the grid child where a form is already paired inside its
     own rows: seasonal settings is seven h3-headed sections, so the sections are
     what pair rather than the fields. Start-aligned, because sections have
     genuinely different heights and stretching one leaves its heading floating
     in the middle of a tall box. */
  .form-grid > .ss-block{align-self:start;}
  .form-grid > .ss-block > h3:first-child{margin-top:0;}
    .form-grid.tri > .span-2{grid-column:span 2;}
    /* `.full` spans whatever the grid is, two columns or three. */
    .form-grid.tri > .full{grid-column:1 / -1;}


  }

  /* A quiet group heading. Mechanically reflowing fields in DOM order leaves
     orphans and organises nothing; grouping is what makes a long form
     readable. Sized to cost ~26px so three of them still leave the company
     form inside the fold. */
  .form-section{grid-column:1 / -1;margin:6px 0 8px;font-size:11px;font-weight:600;
    text-transform:uppercase;letter-spacing:0.06em;color:var(--ink-soft);
    padding-bottom:5px;border-bottom:1px solid var(--line);}
  .form-grid > .form-section:first-child{margin-top:0;}

  .field-error-msg{position:absolute;top:0;right:0;color:var(--brick);font-size:11px;font-weight:600;margin:0;white-space:nowrap;}
  .field.field-invalid input,
  .field.field-invalid select{border-color:var(--brick) !important;}
  .modal-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:20px;}
  .hint{font-size:12px;color:var(--text-muted);margin-top:20px;font-family:var(--font-mono);}
  .pagination{display:flex;justify-content:center;gap:12px;align-items:center;margin-top:14px;padding-bottom:16px;}

  .login-screen{min-height:100vh;display:flex;align-items:center;justify-content:center;background:var(--paper);}
  .login-card{background:var(--paper-raised);border:1px solid var(--line);border-radius:10px;padding:36px 34px;width:100%;max-width:360px;box-shadow:0 4px 20px rgba(31,58,52,0.08);}
  .login-card .brand-mark{font-family:var(--font-display);font-size:22px;font-weight:600;color:var(--ink);display:block;margin-bottom:4px;}
  .login-card .brand-byline{font-size:11px;letter-spacing:0.24em;margin:0 0 10px;}
  .login-card .login-sub{font-size:13px;color:var(--ink-soft);margin:0 0 24px;}
  /* Links on the two pieces of chrome that never had a colour: browser blue on black reads as a broken page. */
  .login-card a,.rail-foot a{color:var(--ink-soft);}
  .login-card .btn{width:100%;margin-top:6px;}
  .login-error{background:var(--brick-soft);color:#8C332E;font-size:13px;padding:10px 12px;border-radius:var(--radius);margin-bottom:16px;display:none;}
  .login-error.visible{display:block;}


/* ---------- global search ----------
   The panel is absolutely positioned under the box, so .gs-wrap is the
   positioning context. z-index above the page but below modals, which own the
   screen when they are open. */
.gs-wrap{position:relative;margin:0 0 18px;}
.rail-foot .gs-wrap{margin:0;}
/* The dashboard's mount lives in the page header, so it is a fixed-width
   control rather than a field spending a whole row on an empty box. Global
   search already answers from the rail and from the keyboard, so this is the
   third door to one room and has no business being the widest. */
.page-head-actions{display:flex;align-items:center;gap:10px;flex-shrink:0;}
/* A two-word label must never break across two lines: "Add / lead" reads as
   two controls and makes that button taller than the two beside it. */
.page-head-actions .btn{white-space:nowrap;}
/* The search takes whatever is left between the title and the buttons, rather
   than a fixed width. Both neighbours size to their content, so this is the
   only element that can absorb the slack; at a fixed width the header carried
   a gap that moved every time a button appeared or a title got longer.
   min-width stops it collapsing to nothing once three buttons are present. */
.gs-wrap.gs-compact{margin:0;flex:1;min-width:200px;}
/* Matched to the button beside it rather than left to its own content
   height: 33px next to a 36px button reads as a control that did not finish
   loading. The box already centres its children, so the extra 3px is shared. */
.gs-wrap.gs-compact .gs-box{padding:7px 10px;min-height:36px;}
.gs-wrap.gs-compact .gs-box .gs-input{font-size:13px;line-height:18px;}
/* Aligned to the box's RIGHT edge and wider than the box: 260px of results is
   unreadable, and the box now sits near the right of the page, so a panel of
   this width anchored left would hang off the edge. The rail mount solves the
   same problem the other way round, being anchored at the left. */
/* Anchored to the box's RIGHT edge, and sized independently of it. The box is
   elastic now, so a panel matching its width would be unreadable at 200px and
   needlessly wide at 700. Right-anchored because the box sits against the
   buttons, so a left-anchored panel of a fixed width would hang off the page.
   The rail mount solves the same problem the other way round. */
.gs-wrap.gs-compact .gs-panel{left:auto;right:0;width:auto;min-width:340px;max-width:520px;}
.gs-kbd{font-family:var(--font-mono);font-size:10px;color:var(--text-muted);
  border:1px solid var(--line);border-radius:4px;padding:1px 5px;flex-shrink:0;white-space:nowrap;}
/* On a phone the header wraps, so the control takes the line it landed on
   rather than sitting at 260px with dead space beside it. */
/* On a phone the header wraps, so the search takes a line of its own rather
   than being squeezed between a title and three buttons. */
@media (max-width:760px){
  .page-head-actions{width:100%;flex-wrap:wrap;}
  .gs-wrap.gs-compact{flex-basis:100%;min-width:0;}
  .gs-wrap.gs-compact .gs-panel{left:0;right:0;width:auto;min-width:0;max-width:none;}
  /* The strip stacks here, so pushing the clear note right strands it on a
     line of its own with the whole width beside it. It reads as part of the
     list when it simply follows. */
  .alerts-clear{margin-left:0;}
}
.gs-box{display:flex;align-items:center;gap:8px;background:var(--paper-raised);
  border:1px solid var(--line);border-radius:var(--radius);padding:8px 11px;}
.gs-box:focus-within{border-color:var(--line-strong);}
.gs-box .ic{color:var(--text-muted);flex-shrink:0;}
/* .gs-box .gs-input, not .gs-input.
   The generic `input[type="text"], ...` control rule is (0,1,1) and a bare
   class is (0,1,0), so it WON: the input drew its own 1px border, white
   background, 6px radius and 8px padding INSIDE the box's border, which is a
   literal box in a box. It had been that way since global search shipped and
   was merely less obvious at full width. Every property the generic rule sets
   has to be named here, or whichever is missed keeps leaking through. */
.gs-box .gs-input{flex:1;border:0;background:transparent;padding:0;border-radius:0;
  font:inherit;font-size:14px;line-height:20px;color:var(--ink);outline:none;min-width:0;height:auto;}
.gs-clear{border:0;background:transparent;font-size:18px;line-height:1;color:var(--text-muted);cursor:pointer;padding:0 2px;}
.gs-panel{position:absolute;left:0;right:0;top:calc(100% + 4px);z-index:60;}
.gs-results{background:var(--paper-raised);border:1px solid var(--line);border-radius:var(--radius);
  box-shadow:0 6px 20px rgba(0,0,0,.10);overflow:hidden;max-height:60vh;overflow-y:auto;}
.gs-hint{margin:0;padding:14px 13px;font-size:13px;color:var(--text-muted);}
.gs-scope-chip{border:1px solid var(--line);background:var(--subtle-bg);border-radius:10px;
  font:inherit;font-family:var(--font-mono);font-size:11px;color:var(--ink-soft);
  padding:1px 7px;margin:2px 1px 0 0;cursor:pointer;}
.gs-scope-chip:hover{border-color:var(--line-strong);}
/* The active scope, stated above the results so the narrowing is never
   invisible: a search that quietly looked in one place would read as a search
   that found little. */
.gs-scopebar{display:flex;align-items:center;gap:8px;padding:7px 13px;
  background:var(--blue-soft);color:var(--ink);font-size:12px;}
.gs-scope-clear{border:0;background:transparent;font:inherit;font-size:12px;
  color:var(--blue);cursor:pointer;text-decoration:underline;padding:0;}
.gs-group + .gs-group{border-top:1px solid var(--line);}
.gs-group-head{display:flex;justify-content:space-between;gap:10px;padding:7px 13px;
  background:var(--subtle-bg);font-size:11px;color:var(--ink-soft);}
/* The navigation path, which is the answer to "how do I find this again".
   Stated once per group rather than repeated on every row. */
.gs-path{color:var(--text-muted);white-space:nowrap;}
.gs-row{display:block;width:100%;text-align:left;border:0;background:transparent;font:inherit;
  color:inherit;cursor:pointer;padding:8px 13px;border-top:1px solid var(--line);}
.gs-row:hover,.gs-row.active{background:var(--subtle-bg);}
.gs-row-title{display:block;font-size:14px;}
.gs-row-sub{display:block;font-size:12px;color:var(--text-muted);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.gs-more{padding:6px 13px;font-size:11px;color:var(--text-muted);border-top:1px solid var(--line);}
/* In the rail the panel would be clipped by the narrow column, so it opens to
   the right of it at a readable width. */
.rail-foot .gs-panel{left:0;width:340px;right:auto;}

/* ---------- mobile navigation (nothing below changes the desktop rail) ----------
 *
 * Twenty-three tabs in a wrapping strip measured 1123px on an 812px screen, so
 * the menu was taller than the phone and every screen started below it. The
 * bell, the user's name and Log out live in .rail-foot, which is display:none
 * at this width, so a phone could not sign out at all.
 *
 * Both new elements default to display:none, so above 820px they contribute
 * nothing: no layout, no flex participation, no stacking context.
 */
.rail-mobile{display:none;}
.nav-pin{display:none;}

@media (max-width:820px){
  /* A COLUMN again. The row-with-wrapping layout is what produced the wall:
     the brand took the left, and twenty-three tabs wrapped down the right. */
  /* Capped so an expanded group can never fill the screen and leave no content
     visible at all — the menu scrolls within itself past that point. A guard,
     not a layout: nothing reaches it until a group is opened. */
  .rail{flex-direction:column;align-items:stretch;gap:10px;padding:12px 16px;
    max-height:80vh;overflow-y:auto;}
  .brand{flex-shrink:0;}

  /* .tabs is a wrapping ROW at this width, which is what lets eight group
     headings sit on three lines instead of eight. This block is not a tab
     though, so it takes a full-width band of its own at the top rather than
     wrapping in among them — without the basis it landed mid-strip and
     stranded whichever heading wrapped next to it. */
  /* A deliberate TWO-COLUMN GRID rather than a wrap. Wrapping mixed two
     visual languages in one ragged flow — icon-and-label tabs and coloured
     uppercase group headings — so Payouts landed beside CRM and Settings was
     orphaned on a line of its own. A grid gives every entry the same cell and
     the block reads as one thing. */
  .tabs{display:grid;grid-template-columns:1fr 1fr;gap:6px;align-content:start;}
  .rail-mobile{display:flex;flex-direction:column;gap:8px;grid-column:1/-1;order:-1;}

  /* Dashboard is the home of the whole tool and was sitting in the wrap as one
     chip among thirteen. Full width, raised, and first. */
  .tabs > .tab[data-view="dashboard"]{grid-column:1/-1;background:var(--paper-raised);
    border-color:var(--line);font-weight:500;font-size:15px;padding:13px 12px;}
  .tabs > .tab[data-view="dashboard"].active{background:var(--sage-soft);
    border-color:var(--sage);color:var(--sage-ink);}

  /* Every other entry is one cell of equal weight, whether it is a loose tab
     or a group heading. The loose tabs need a surface of their own: beside
     the tinted group headings they read as floating text rather than as
     cells, which is most of what made the block look unfinished. */
  .tabs > .tab, .nav-category-head{min-height:44px;border-radius:var(--radius);}
  .tabs > .tab{background:var(--subtle-bg);border-color:var(--line);}
  /* Settings is the gear in the bar at this width, so the grid cell would be a
     second way to the same place. */
  .tabs > .tab[data-view="settings"]{display:none;}
  .tabs > .tab.active{background:var(--paper-raised);border-color:var(--line-strong);}
  .nav-category.expanded{grid-column:1/-1;}
  .nav-category-body.m-open{margin-top:4px;}

  /* The account row the rail foot cannot show at this width. */
  .rail-m-account{display:flex;align-items:center;gap:10px;
    font-size:12px;color:var(--text-muted);font-family:var(--font-mono);}
  #rail-m-user{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}

  /* The toggle that folds the whole menu away. Reads as a control rather than
     a label, because it is the one thing on this bar somebody presses to
     change what they are looking at. */
  .rail-m-menu{display:inline-flex;align-items:center;gap:6px;height:36px;padding:0 10px;
    background:var(--paper-raised);border:1px solid var(--line);border-radius:var(--radius);
    font-family:inherit;font-size:12px;color:var(--ink);cursor:pointer;flex:0 0 auto;}
  .rail-m-menu .ic{width:16px;height:16px;}

  /* Collapsed means GONE. Everything folds away except one bar: the toggle,
     who you are, the bell and Log out. The pinned and recent chips go too —
     they are a fast path, but a fast path that is permanently in the way is
     just more chrome, and the whole point of collapsing is to see the tool.
     The brand goes as well: on a phone toolbar you know which app you are in,
     and it was costing a line of its own. */
  .rail.menu-collapsed{max-height:none;overflow:visible;padding:8px 16px;gap:0;}
  .rail.menu-collapsed .brand,
  .rail.menu-collapsed .rail-m-search,
  .rail.menu-collapsed #rail-m-lists,
  .rail.menu-collapsed .tabs > .tab,
  .rail.menu-collapsed .nav-category{display:none;}
  .rail-m-account-actions{display:flex;align-items:center;gap:6px;flex:0 0 auto;}
  /* Sized as real targets. The bell was 25px and Log out 15px tall, sitting
     6px apart — small enough to mis-tap, and one of them signs you out. */
  .rail-m-icon{position:relative;display:inline-flex;align-items:center;justify-content:center;
    min-width:36px;height:36px;background:none;border:0;padding:0;cursor:pointer;
    color:var(--ink-soft);line-height:0;}
  .rail-m-badge{position:absolute;top:-2px;right:-4px;min-width:14px;height:14px;padding:0 3px;
    border-radius:7px;background:var(--brick);color:#fff;font-size:9px;line-height:14px;text-align:center;}
  .rail-m-logout{display:inline-flex;align-items:center;height:36px;padding:0 6px;
    color:var(--ink-soft);text-decoration:none;}

  .rail-m-search{width:100%;font-family:inherit;font-size:14px;padding:9px 11px;
    border:1px solid var(--line);border-radius:var(--radius);
    background:var(--paper-raised);color:var(--ink);}
  .rail-m-search:focus{outline:none;border-color:var(--line-strong);}

  /* ONE "Quick access" block, built from the SAME cell as the menu grid below
     it. As chips this was a third visual language on a panel that already has
     a full-width Dashboard and a two-column grid, so the panel read as three
     stacked ideas rather than one. Two headings became one for the same
     reason: pinned and recent are a single list of shortcuts, told apart by
     whether the star is lit. */
  .rail-m-quick-group{display:flex;flex-direction:column;gap:5px;}
  .rail-m-group-head{font-family:var(--font-mono);font-size:10px;letter-spacing:0.08em;
    text-transform:uppercase;color:var(--text-muted);}
  .rail-m-quick{display:grid;grid-template-columns:1fr 1fr;gap:6px;}
  .rail-m-quick-cell{display:flex;align-items:center;justify-content:space-between;gap:8px;
    min-height:44px;padding:9px 10px 9px 12px;border:1px solid var(--line);
    border-radius:var(--radius);background:var(--paper-raised);
    font-size:13px;color:var(--ink-soft);cursor:pointer;}
  .rail-m-quick-cell.active{border-color:var(--line-strong);color:var(--ink);font-weight:500;}
  .rail-m-quick-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  /* The star is a target of its own inside the cell, so pinning and navigating
     never compete for the same pixels. */
  .rail-m-quick-pin{display:inline-flex;align-items:center;justify-content:center;
    flex:0 0 auto;min-width:28px;height:28px;margin:-4px -4px -4px 0;
    color:var(--line-strong);line-height:0;}
  .rail-m-quick-pin.pinned{color:var(--mustard);}
  .rail-m-quick-pin .ic{width:14px;height:14px;}

  /* Desktop's open state is neutralised and the mobile one drives the accordion.
     Source order matters here: .m-open must win when both classes are present. */
  .nav-category-body.open{display:none;}
  .nav-category-body.m-open{display:flex;flex-direction:column;flex-wrap:nowrap;}

  /* Filtering hides with a class, never with style.display, which
     applyNavVisibility() owns — the two writing the same property would let a
     cleared search reveal a tab somebody is not allowed to see. */
  .tab.nav-filtered-out{display:none !important;}
  .nav-category.nav-group-filtered-out{display:none !important;}

  /* Every row a comfortable target, and the pin far enough from the label that
     one is not mistaken for the other. */
  .tab{padding:11px 12px;}
  .nav-category-head{padding:10px 12px;}
  .nav-pin{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;
    background:none;border:0;padding:4px;margin:-4px -4px -4px 0;cursor:pointer;
    color:var(--line-strong);line-height:0;}
  .nav-pin .ic{width:14px;height:14px;}
  .nav-pin.pinned{color:var(--mustard);}
  /* No pin on a LOOSE top-level tab. Dashboard, Reports, Finance, Payouts and
     Settings are already one tap away with nothing to expand, so a shortcut to
     them saves nothing and five extra stars make the strip read as busy. */
  .tabs > .tab .nav-pin{display:none;}
  /* The count sits between the label and the pin and must not be pushed out. */
  .tab .count{flex:0 0 auto;}
}



/*
 * Money and identifiers in a tabular face. `.font-mono` is used on the
 * marketing hub's spend, revenue and ROAS columns, which are right-aligned
 * figures with decimals - the exact case a proportional face ruins, and the
 * reason a mono face is in the type stack at all.
 *
 * `--font-mono` deliberately never changes with the chosen font set, because
 * it is doing alignment work rather than expressing taste.
 */
.font-mono, .mono{ font-family:var(--font-mono); font-variant-numeric:tabular-nums; }

/* The quiet "optional" marker beside a field label. */
.optional{ font-weight:400; font-size:11px; color:var(--text-muted); text-transform:none; letter-spacing:0; }

/*
 * A neutral callout inside a modal, used by the work-order step panel to
 * explain what Begin and Finish will do. Shaped on `.postage-note` below so
 * the two read as the same kind of aside rather than two inventions.
 */
.alert{
  border:1px solid var(--line); border-left:3px solid var(--line-strong);
  border-radius:var(--radius); padding:10px 12px;
  font-size:12px; color:var(--text-muted);
}
.alert.info{ border-left-color:var(--sage); }


/* ---------- Settings card launcher ---------- */
/*
 * Settings opens on five cards rather than nine stacked sections. Each card
 * lists what is inside it: a tile saying only "Business" makes somebody open
 * all five to find the tax rates, which costs more than the scroll it replaced.
 *
 * auto-fit rather than a fixed column count, so this needs no media query of
 * its own: it lands on three columns wide, two on a tablet and one on a phone,
 * and the mobile rail's own breakpoint is left entirely alone.
 */
.settings-cards{
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(240px, 1fr));
  gap:14px;
}
.settings-card{
  display:flex;
  flex-direction:column;
  /* A <button> does not stretch to its grid row the way a div does, so cards
     in the same row came out at different heights depending on how many items
     they list. Measured at three distinct heights before this line. */
  height:100%;
  align-items:flex-start;
  gap:6px;
  text-align:left;
  width:100%;
  font:inherit;
  color:var(--ink);
  cursor:pointer;
  background:var(--panel-bg);
  border:1px solid var(--panel-border);
  border-radius:var(--panel-radius);
  padding:var(--panel-pad);
  /* Reads the block-style tokens like .panel does, so all five block styles
     apply to these without a second set of rules to keep in step. */
  box-shadow:var(--panel-shadow);
}
.settings-card:hover{ border-color:var(--line-strong); }
.settings-card-head{ display:flex; align-items:center; gap:8px; }
.settings-card-title{ font-weight:600; font-size:15px; }
.settings-card-blurb{ display:block; }
/*
 * One item per line, not an inline run.
 *
 * Inline with middot separators was tried first and wrapped badly: a card whose
 * list runs to two lines leaves the separator dangling at the end of the first
 * one ("Theme · Font · Blocks ·"), which reads as a missing item. Stacked has
 * no separator to strand, and it is what makes the list scannable rather than
 * something to read as a sentence.
 */
.settings-card-items{
  display:flex;
  flex-direction:column;
  gap:2px;
  margin-top:4px;
}
.settings-card-items span{
  font-size:12px;
  color:var(--text-muted);
  line-height:1.5;
}

/* ---------- Modal close button ---------- */
/*
 * Every modal gets a close control in its top right, injected centrally rather
 * than written into all 136 overlays by hand.
 *
 * Escape and clicking the backdrop already closed these, and both still do.
 * Neither is discoverable: somebody who does not know them has no visible way
 * out of a dialog, which is the one place a UI must never leave a person
 * guessing.
 *
 * `.modal` needs position:relative for the button to anchor to, which the base
 * rule does not set. Declared here rather than edited into the base rule so
 * the whole feature is one block that can be removed in one piece.
 */
.modal{position:relative;}
.modal-x{
  position:absolute;
  top:10px;
  right:10px;
  /* 32px is the hit area; the glyph inside is 15px. Small enough to read as
     chrome, large enough to hit with a thumb. */
  width:32px;
  height:32px;
  display:flex;
  align-items:center;
  justify-content:center;
  padding:0;
  border:0;
  border-radius:var(--radius);
  background:transparent;
  color:var(--text-muted);
  cursor:pointer;
  /* Above the modal's own content, so a full-width header or a wide table
     cannot render on top of it. */
  z-index:1;
}
.modal-x:hover{ background:var(--subtle-bg); color:var(--ink); }
.modal-x:focus-visible{ outline:2px solid var(--blue); outline-offset:1px; }
.modal-x .ic{ width:15px; height:15px; }
/*
 * Keep a long title off the button. The heading is still the first child (the
 * button is APPENDED, not prepended, precisely so `h2:first-child` rules and
 * `.modal h2` margins are untouched), so this cannot shift any existing
 * layout.
 */
.modal > h2:first-child{
  padding-right:30px;
  /* Only bites an unbreakable word longer than the heading box, which a real
     title never is. Cheap insurance: without it such a word ignores the
     padding above and runs under the button. */
  overflow-wrap:anywhere;
}


/* ---------------------------------------------------------------------------
 * The app dock: a launcher down the right-hand side, DESKTOP ONLY.
 *
 * The app was capped at 1180px and centred, so on a 1920px screen roughly
 * 370px was margin split evenly either side -- which is why the left looked as
 * wasted as the right. The cap lifts on a wide screen and the dock is what the
 * reclaimed column is for.
 *
 * Hidden below 1280px, so it contributes NO layout on a tablet or a phone: the
 * mobile rail already solves navigation there, and a third column at 375px
 * would push the content off screen.
 * ------------------------------------------------------------------------- */
.dock{ display:none; }

@media (min-width:1280px){
  /* No cap: the owner asked for the app to fill the window, so on a 1920px
     screen there is no margin either side (upstream capped at 1560px to keep
     line lengths readable on a cinema monitor; the portal lifts its cap the
     same way in portal.css). */
  .app{ max-width:none; grid-template-columns:220px minmax(0,1fr) 168px; }

  .dock{
    display:block;
    border-left:1px solid var(--line);
    background:var(--paper);
  }

  /* STICKY rather than fixed. Fixed would take the dock out of flow and let
     the dashboard slide underneath it; sticky keeps the grid column reserving
     the space, so the content can never end up behind the tiles. */
  .dock-inner{
    position:sticky; top:0;
    max-height:100vh; overflow-y:auto;
    padding:28px 12px;
    display:flex; flex-direction:column; gap:14px;
  }
}

.dock-heading{
  font-size:10px; letter-spacing:0.06em; text-transform:uppercase;
  color:var(--text-muted); margin-bottom:2px;
}

/* Two columns. Three fitted, but the labels then wrapped to two lines on most
   of them, which made the rows uneven and the whole dock taller. */
.dock-grid{ display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:12px 6px; }

.dock-recent{ border-top:1px solid var(--line); padding-top:12px; }

.dock-tile{
  display:flex; flex-direction:column; align-items:center; gap:5px;
  background:none; border:0; padding:2px 0; cursor:pointer;
  font-family:var(--font-ui); color:var(--ink-soft);
}
.dock-tile:hover{ color:var(--ink); }
.dock-tile:hover .dock-icon{ border-color:var(--line-strong); }

/* The rounded square. position:relative so the badge can hang off its corner. */
.dock-icon{
  position:relative;
  width:46px; height:46px; border-radius:13px;
  border:1px solid var(--line);
  background:var(--paper-raised);
  display:flex; align-items:center; justify-content:center;
}
.dock-icon .ic{ width:22px; height:22px; }

/* One tint per pinned tile, so the six are told apart without reading six
   labels. Text from the SAME hue as the fill, the standing rule: --ink on a
   colour reads as a mistake. Recents stay neutral on purpose -- they are
   transient, and colouring them would make the dock read as twelve equally
   important things. */
.dock-tint-violet{ background:var(--violet-soft); border-color:transparent; color:var(--violet-ink); }
.dock-tint-sage{   background:var(--sage-soft);   border-color:transparent; color:var(--sage-ink); }
.dock-tint-mustard{background:var(--mustard-soft);border-color:transparent; color:var(--mustard-ink); }
.dock-tint-blue{   background:var(--blue-soft);   border-color:transparent; color:var(--blue-ink); }
.dock-tint-brick{  background:var(--brick-soft);  border-color:transparent; color:var(--brick-ink); }
.dock-tint-plum{   background:var(--plum-soft);   border-color:transparent; color:var(--plum-ink); }

/* The count. Solid rather than tinted: this one is meant to be seen from the
   other side of the screen, which is the whole job of a notification badge. */
.dock-badge{
  position:absolute; top:-5px; right:-5px;
  min-width:19px; height:19px; padding:0 5px;
  border-radius:999px;
  background:var(--brick); color:#fff;
  font-size:11px; font-weight:500; line-height:19px;
  text-align:center;
  border:2px solid var(--paper);
}
.dock-badge[hidden]{ display:none; }

.dock-label{
  font-size:10px; line-height:1.25; text-align:center;
  max-width:66px;
  /* Two lines then clip. A third line makes one tile taller than the five
     beside it, which is the uneven-row problem the grid exists to avoid. */
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
  overflow:hidden;
}



/* ---------- core additions for the modular shell ---------- */

/* Below 760px every list collapses to one column. In GnomeWork each feature
   row was named here one by one; the shell states the rule once and a module
   whose row must NOT collapse opts out in its own stylesheet. */
@media (max-width:760px){
  .row:not(.head){grid-template-columns:1fr !important;gap:4px;}
  .row.head{display:none;}
}

/* The brand accent, emitted by the Blade shell from the tenant's brand JSON,
   reaches the chrome through one token. A tenant with no accent set inherits
   the theme's own. */
:root{--brand-accent:var(--mustard);}
.brand-mark{color:var(--ink);}
.brand-logo{height:30px;width:auto;max-width:170px;display:inline-block;vertical-align:middle;}
/* On the login card the logo stands in for the name, so it is larger and centred. */
.login-card .login-logo{display:block;height:40px;max-width:240px;margin:0 auto 10px;}
.brand-lockup .brand-logo{margin-bottom:2px;}

/* The count beside a tab. Rendered by the shell from the manifest as
   <span class="count" data-count="id">, filled by updateCounts(). */
.tab .count[data-count]:empty{display:none;}

/* An empty rail while modules load, or a tenant with nothing enabled. */
.rail-empty{font-size:12px;color:var(--text-muted);padding:10px 12px;}

/* The shell reports a manifest view with no registered renderer here. */
.view-missing{padding:40px 20px;text-align:center;color:var(--ink-soft);}

/* A "?" beside a label, with the explanation behind it rather than in a
   paragraph under every field (helpTipHtml in platform.js). */
.help-tip{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;
  border-radius:50%;border:1px solid var(--line-strong);background:transparent;color:var(--ink-soft);
  font-family:var(--font-ui);font-size:11px;font-weight:600;line-height:1;cursor:pointer;padding:0;
  margin-left:6px;vertical-align:middle;flex:0 0 auto;}
.help-tip:hover,.help-tip[aria-expanded="true"]{background:var(--ink-soft);color:var(--paper);border-color:var(--ink-soft);}
.help-bubble{display:none;position:absolute;z-index:220;max-width:320px;background:var(--paper-raised);
  border:1px solid var(--line-strong);border-radius:var(--radius);box-shadow:0 6px 20px rgba(0,0,0,0.14);
  padding:10px 12px;font-size:12px;line-height:1.55;color:var(--ink);}

/* A module's notice at the top of every screen (the demo's banner), drawn by the server with the page. */
.shell-notice{background:#000540;color:#E8ECFA;border-radius:8px;padding:9px 14px;margin:0 0 16px;font-size:13px;}
