/* Canonical creo.css (2026-09-14, css lane phase 1: single source).
   Home: sagebrush-timeclock cloud-api/app/assets/creo.css. Creosote OS
   vendors this file byte-for-byte at apps/web/src/creo.css; its contract
   test (creoDesignSource.contract.test.ts) pins the sha256 of this exact
   file. Change it here, then copy the identical bytes to both repos and
   update both hash pins in the same commit. Do not edit the two copies
   separately. */

/* font-face: a declared web font face. Ruling: unattributed */
/* Official Figtree (Google Fonts v9), the variable face, self-hosted.
   Weight 400 is the face; 700 exists for the few headings that ask for it,
   and the 400-700 range covers both so no weight clamps.

   V-6 (PAYROLL-DEPLOYED-TODAY-ADVERSARIAL-AUDIT-2026-09-03). This block used
   to name five sources across three faces and every one of them failed, so
   the entire app rendered in the browser's default sans-serif from
   2026-08-25 until this fix. In order: a `data:` URI, which the live
   `font-src 'self'` Content-Security-Policy forbids; `./fonts/` and
   `/fonts/` woff2 paths, which 404; and two fixed-weight faces pointing at
   `.ttf` files that 404 at every path. The file is served, and has always
   been served, at `/assets/figtree-latin.woff2` -- and nothing referenced
   that path. It does now, and it is the only source, because a fallback that
   404s is not a fallback. */
@font-face {
  font-family: "Figtree";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src:url("/assets/figtree-latin.woff2") format("woff2");
}

/* V-7 (adversarial audit 2026-09-03). A Tailwind v4 `@theme` block stood
   here. It is SOURCE syntax that a build step expands, it was shipped
   unbuilt, and a browser reads it as an unrecognised at-rule and drops it --
   so it never defined anything. Every token it named is declared by hand in
   the `:root` block immediately below, which is what the app has actually
   been running on. Two declarations of one concept, one of them inert: the
   inert one is gone and `:root` is the single source. The `@import
   "tailwindcss"` line above it is gone for the same reason -- the browser
   resolved it to /assets/tailwindcss, got a 404 with a JSON content type,
   refused it as a stylesheet and logged an error on every page load. */

/* V2 white ground (Matt, 2026-09-03 evening review, note 72, on the live
   payroll pages): "last time I reviewed this, I asked for you to make the
   other pages not look like this gray overlay on the white background. But
   originally it was everything would be the white background. Now you
   basically have this white frame that I don't even want on multiple
   surfaces because you did the opposite of what I asked for."

   Ruling: the page ground is white everywhere, no gray card fill, no white
   frame. So the four SURFACE tokens below carry ONE value and a card, panel,
   table or section separates from the ground by the shared --line hairline
   and by spacing, never by a fill of its own. That is the whole mechanism:
   no page-local background override exists anywhere in the app (every
   surface fill in this file resolves to one of these tokens, and no page
   module declares a background of its own), so one value here is the entire
   change and no page can drift from it.

   V2b (Matt, 2026-09-04 morning ruling, DECISIONS-LEDGER.md:1098), verbatim:
   "when i say white i mean fafafa". The four SURFACE tokens below now carry
   ONE value -- #fafafa, not #ffffff -- for that reason and no other: "white"
   in the 2026-09-03 ruling above meant #fafafa, so the V2 branch's literal
   #ffffff was a misreading of the same ruling, not a second design change.
   This SUPERSEDES, for Team: the 2026-08-29 paper-primary ruling
   (DECISIONS-LEDGER.md:1030, "the paper color needs to be the primary color
   for most backgrounds") -- already superseded once by V2 above, and still
   superseded now that the ground is #fafafa rather than the #eae9e8 that
   ruling named; and the 2026-07-31 #ffffff rule in DESIGN-REFERENCES.md,
   which V2 shipped literally. #fafafa is a near-white, not the #eae9e8 gray
   card overlay and not the #ffffff V2 shipped; it is the value Matt's own
   theme settings have given the page background since the 2026-08-01
   DESIGN-REFERENCES.md correction, now the SHARED Team ground too.

   --field used to be #eae9e8 (paper era) and was the "white frame" Matt
   names: it filled .sb-panel--active, which standard 3.5 pushes to every
   selected-object detail surface, so a near-white slab appeared on multiple
   surfaces at once over the paper ground. It now equals the ground. The
   active surface keeps the one divider hairline standard 3.5 gives it, and
   selection stays explicit in words and accessible state, never in fill
   alone.

   --hair stays #eae9e8: it fills CONTROLS (.btn, .icon-btn, .chip-held,
   .photo, .rpt-chip, toggle states), which the standard's secondary-button
   rule sets on that value and which are not page, card, panel or section
   surfaces. --nav stays #d6d2ce because SAGEBRUSH-TEAM-DESIGN-STANDARD.md
   rules the sidebar that tone by name; it was three hardcoded #d6d2ce
   literals and is now one token, so the sidebar cannot drift per page
   either. */
/* Ground ruling, Matt, 2026-09-14 (supersedes the V2 and V2b paragraphs
   above for the ground): "i want the eae9e8 color for default background,
   fafafa for text inputs and places we want to contrast on the background
   in a more visible way (like the text inputs)." So --paper, --card and
   --face carry #eae9e8 (page ground, cards and panels sit on it separated by
   the hairline), and --field carries #fafafa (text inputs and any surface
   that must read lighter than the ground). The 2026-09-04 change to #fafafa
   ground came from reading note 72 as "white ground"; that reading is
   withdrawn. */
/* Field ruling, Matt, 2026-09-14 (the 13-group shared-look rulings, group 2;
   supersedes the #fafafa value in the paragraph above, and only that value):
   "Text inputs are white (#ffffff) on the EAE9E8 ground, with OS's border,
   44px height and padding." --field is that one white, so every surface that
   consumes it -- text inputs, textareas, selects, contenteditables, and the
   active work surface .sb-panel--active -- moves from #fafafa to #ffffff
   together. The ground itself does not move: --paper, --card and --face stay
   #eae9e8. */
/* Correction, Matt, 2026-09-22 (supersedes the "Field ruling" paragraph
   above): "I would never say FFFFFF for anything. It would always be FAFAFA.
   ... an agent misunderstood white when I said white means FAFAFA." The
   #ffffff value was an agent misreading, not a ruling. --field is #fafafa,
   the value the 2026-09-14 ground ruling itself named for text inputs. */
:root {
  --ink: #231f20;
  --head: #231f20;
  --paper: #eae9e8;
  --card: #eae9e8;
  --face: #eae9e8;
  --field: #fafafa;
  --nav: #d6d2ce;
  /* Creosote OS's sheet named the ground `--canvas` and the sidebar tone
     `--sidebar`. Both are aliases of the tokens above, not second values
     (13-group fold, Matt, 2026-09-14). */
  --canvas: var(--paper);
  --sidebar: var(--nav);
  --act: #c75d31;
  --on-action: #fff;
  --clay: #e8c4b0;
  --rust: #c75d31;
  --shadow: #231f20;
  --muted: #231f20;
  --line: #d6d2ce;
  /* Store identity colors (Matt, 2026-09-14: "have things color coded"; one
     color per store, used everywhere that store appears: summary band cell,
     chart series, queue row mark). The 13-group ruling later the same day
     made them the brand accents -- "Store colors are the brand accents:
     Sagebrush Coffee terracotta #c75d31, Hackberry berry #bd4251, Unroasted
     green #abb772" -- replacing the peach, lavender and cream this block
     carried first. Each value is the brand's own accent, so a store reads the
     same color here as it does on its own brand surfaces. */
  --store-hbt: #bd4251;
  --store-sbc: #c75d31;
  --store-sbu: #abb772;
  /* Shared hairline and compact-summary primitives. Schedule and dashboard
     surfaces can consume these without inventing page-specific measurements. */
  --divider: var(--line);
  --divider-width: 1px;
  --summary-gap: var(--divider-width);
  --hair: #eae9e8;
  --ok: #026735;
  --attn: #b9ad35;
  --stop: #bf3533;
  --space: 8px;
  /* V1 (2026-09-03). The payroll workspace's own rhythm tokens, declared
     once so the five steps and their siblings cannot drift apart: the step
     cell's fixed height at desktop/tablet and on a phone, and the single
     gap between the step strip, a control strip under it, and the first
     work surface. */
  /* 104px is what a two-line label plus a two-line deliverable line plus
     the cell's own padding actually measures at 16px/1.25. At 92px the
     tablet-width cells overflowed their own fixed height by 10px, which put
     each cell's number badge at a different height depending on how many
     lines its label happened to take -- the very drift the fixed height is
     here to stop. */
  --sb-step-cell-height: 104px;
  --sb-step-cell-height-compact: 64px;
  --sb-workspace-rhythm: 24px;
  --radius: 2px;
  --type: 16px;
  --type-sub: 26px;
  --type-head: 32px;
  --motion-quick: 150ms;
  --motion-fast: 250ms;
  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --rpt-chip-w: 132px;
  --rpt-chip-h: 44px;
  --rpt-gap: 8px;
  --rpt-panel-w: 280px;
  --rpt-chart-w: 720px;
  --rpt-chart-h: 220px;
  --nest-indent: 48px;
  --nest-rail: 3px;
  --nest-rail-child: 8px;
  /* Data-identity colors (Matt, 2026-08-31: a sanctioned token CATEGORY,
     "alternate... mission based or something like that colors", shared across
     every Creosote app; promoted from Creosote Studio 2026-09-02 on Matt's
     "promote"). Values are the hub-brand tokens they name. --tea-page,
     --unroasted-page, and --unroasted-text are brand-literal tints allowed
     only inside authored brand surfaces such as an email preview (Matt,
     2026-09-02); they never paint app chrome, where paper stays the ground. */
  --berry: #bd4251;
  --unroasted-green: #abb772;
  --shop-orange: #9e4416;
  --tea-page: #ffffff;
  --unroasted-page: #fffdf9;
  --unroasted-text: #4c3728;
  /* Full contract: SAGEBRUSH-TEAM-DESIGN-STANDARD.md
     Caret = expands. Indent = child. Rail = identity. Delta = sign. */
}

/* Components throughout the app use author-level grid and flex display
   rules. Those declarations otherwise outrank the browser's built-in
   [hidden] rule and can leave a supposedly closed or panel-specific control
   visible. Hidden is an application state, so it wins everywhere. */
[hidden],
body.sb-session-loading #sagebrush-shell,
body.sb-session-loading [data-auth-required],
body.sb-signed-out #sagebrush-shell,
body.sb-signed-out [data-auth-required],
.schedule-menu-anchor > .box[hidden],
#view-by-list[hidden],
#view-by-row[hidden],
#message[hidden],
#publish-reason[hidden],
#wage-status[hidden],
#setup-warnings[hidden],
#clipboard-status[hidden],
.sb-overlay[hidden] {
  display: none !important;
}

/* base-reset: the shared cross-browser element reset (html, body, headings, form controls, focus ring). Ruling: unattributed */
@layer base {
  * {
    box-sizing: border-box;
  }

  html {
    color-scheme: light;
    font-synthesis: none;
  }

  html,
  body,
  #app {
    margin: 0;
    min-height: 100%;
    max-width: 100%;
    overflow-x: clip;
    background: var(--face);
    color: var(--ink);
    font-family: "Figtree", sans-serif;
    font-size: var(--type);
    font-weight: 400;
    line-height: 1.35;
  }

  h1,
  h2,
  h3,
  h4,
  strong,
  b,
  th,
  button,
  summary {
    font-weight: 400;
  }

  h1 {
    color: var(--head);
    font-size: var(--type-head);
    line-height: 1.1;
    letter-spacing: -0.03em;
    text-wrap: balance;
  }

  h2 {
    color: var(--head);
    font-size: var(--type-sub);
    line-height: 1.15;
    letter-spacing: -0.02em;
    text-wrap: balance;
  }

  button,
  input,
  select,
  textarea {
    font-family: inherit;
    font-size: inherit;
    line-height: inherit;
    color: inherit;
    border: 0;
  }

  a {
    color: var(--act);
  }

  :focus-visible {
    outline: 3px solid var(--act);
    outline-offset: 2px;
  }

  button:not(:disabled),
  [role="button"]:not(:disabled) {
    cursor: pointer;
  }
}

.shell {
  display: grid;
  grid-template-columns: 228px minmax(0, 1fr);
  min-height: 100dvh;
  max-width: 100%;
  overflow-x: clip;
  background: linear-gradient(
    to right,
    #d6d2ce 0 228px,
    var(--face) 228px 100%
  );
  color: var(--ink);
}

/* sb-signed: the signed-out placeholder state. Ruling: unattributed */
/* Authentication is a page boundary, not a sidebar status. Never reveal an
   authenticated page while the session is unresolved or signed out. The
   signed-out notice becomes the whole page and owns the Google sign-in
   control, so private schedule, payroll, and employee content cannot remain
   visible behind it. */

body.sb-signed-out #sagebrush-shell-notice {
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 12px;
  min-height: 100dvh;
  width: 100%;
  margin: 0;
  padding: 24px;
  border: 0;
  background: var(--face);
  text-align: center;
}

body.sb-signed-out #sagebrush-shell-notice > * {
  max-width: 520px;
  margin: 0;
}

.topbar,
.sb-review-wizard ~ p:empty,
.sb-review-wizard ~ * > p.muted:empty,
.sb-data-table--responsive td[data-label]::before,
.sb-status:empty,
.sb-callout:empty,
.main > p[role="status"][aria-live]:empty,
.sb-date-strip .sb-date-compact,
.nav-backdrop,
.person-fan-toolbar:empty {
  display: none;
}

.nav {
  background: var(--nav);
  color: var(--ink);
  box-sizing: border-box;
  padding: 16px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  position: sticky;
  top: 0;
  height: 100dvh;
  min-height: 100dvh;
  align-self: start;
  overflow: auto;
}

/* logo: the shared logo primitive. Ruling: unattributed */
.logo {
  display: block;
  width: auto;
  max-width: 100%;
  height: 64px;
  object-fit: contain;
  object-position: left center;
  margin: 0 0 16px;
}

.nav > .logo {
  width: 100%;
  max-width: none;
  height: auto;
  margin: 20px 0 16px;
}

.app-switch {
  position: relative;
  width: 100%;
  margin: 0;
}

.switch-btn,
.nav a.navlink,
.nav button.navlink,
.app-menu button {
  list-style: none;
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 44px;
  padding: 0 10px;
  border-radius: var(--radius);
  background: transparent;
  color: var(--ink);
  text-decoration: none;
  text-align: left;
  text-transform: lowercase;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.switch-btn,
.app-menu button {
  background: transparent;
  color: var(--act);
}

.app-menu button.is-on,
.app-menu button:hover,
.app-menu button.is-on:hover,
.sb-date-presets button.is-on,
.cell-add-rail .cell-add:focus-visible,
.cell-add-rail .cell-add:hover {
  background: var(--hair);
  color: var(--act);
}

.switch-btn[aria-expanded="true"],
.sb-date-dates button.in {
  background: var(--hair);
}

/* switch-btn: a toggle/switch-style button. Ruling: unattributed */
/* Matt's 2026-08-28 ruling (DECISIONS-LEDGER): the app-switch trigger IS the
   sidebar's heading in every Creosote app - the current app's name renders
   at sub-head scale in the action color. Ordered pushed into this shared
   file so every app (Team included) picks it up. */
.switch-btn {
  font-size: var(--type-sub);
  color: var(--act);
}

.nav a.navlink,
.nav button.navlink,
.app-menu button,
.switch-btn {
  font-weight: 400;
}

.nav a.is-on,
.nav button.navlink.is-on {
  background: transparent;
  color: var(--act);
  font-weight: 400;
}

.nav a.navlink:hover,
.nav button.navlink:hover,
.nav a.is-on:hover,
.nav button.navlink.is-on:hover,
.fields .chip[aria-pressed="true"],
.fields .chip[aria-pressed="true"]:hover,
.fields .chip[aria-pressed="true"]:focus-visible,
.tabs button.is-on,
.sb-tabs button.is-on,
.day.is-on,
.sb-date-dates button.on,
a.who:hover,
a.who.is-on,
.frequent-suggestion.is-on,
.rpt-chip.is-on,
.rpt-option.is-on {
  background: var(--act);
  color: var(--on-action);
}

.switch-btn:hover {
  background: transparent;
  color: var(--act);
}

.app-menu {
  position: absolute;
  z-index: 20;
  left: 0;
  right: 0;
  top: calc(100% + 4px);
  display: grid;
  gap: 8px;
  border-radius: var(--radius);
  background: var(--nav);
  padding: 0;
}

/* One fixed slot, right-aligned in the heading row it belongs to, on every
   page. It used to be `position: fixed` at a randomly chosen coordinate,
   which is how it came to sit on the word "devices" and, after the first V1
   pass, on the "payroll" and "team" headings themselves. */
.sidebar-dot[aria-hidden="true"] {
  flex: 0 0 10px;
  align-self: center;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--act);
  pointer-events: none;
  animation: sidebar-dot-bounce 620ms var(--ease-out) both;
}

@keyframes sidebar-dot-bounce {
  0% { transform: translateY(-10px) scale(0.82); opacity: 0; }
  58% { transform: translateY(3px) scale(1.04); opacity: 1; }
  78% { transform: translateY(-2px) scale(0.98); opacity: 1; }
  100% { transform: translateY(0); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .sidebar-dot[aria-hidden="true"] {
    animation: none;
  }
}

/* dests: the shared dests primitive. Ruling: unattributed */
.dests {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.nav-home,
.sb-editor-form .dialog-head {
  margin-bottom: 4px;
}

.nav-group {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.nav-group + .nav-group,
.stack-toggle + .people,
.stack-toggle + .muted,
.sb-dialog-subheading {
  margin-top: 8px;
}

/* V1 (2026-09-03, defect 5 second pass). The heading row is the dot's slot:
   a flex row whose label takes the slack and whose indicator holds a fixed
   width at the right edge, vertically centred. The dot cannot overlap the
   heading text because it is no longer drawn over the sidebar at all -- it is
   a child of the row, in its own track. */
.nav-group-label {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 4px 10px;
  color: var(--ink);
  font-size: var(--type-sub);
  line-height: 1.15;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: lowercase;
}

.nav-group-label > span:first-child {
  min-width: 0;
  flex: 1 1 auto;
}

.nav-group-items {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 0 0 0 8px;
  padding: 0 0 0 8px;
  border-left: 3px solid var(--act);
}

.nav-group-items .navlink,
.tl-bar--scheduled,
.sb-list-trigger:hover,
.sb-list-trigger:focus-visible,
.sb-list-trigger[aria-expanded="true"],
.sb-list-trigger.is-open,
.sb-action-list > button:hover,
.sb-action-list > button:focus-visible,
.sb-action-list > a:hover,
.sb-action-list > a:focus-visible,
.schedule-text-list [role="option"]:hover,
.schedule-text-list [role="option"]:focus-visible,
.schedule-text-list [role="option"][aria-selected="true"],
.schedule-mode-picker .sb-list-trigger.is-open,
.schedule-mode-picker .sb-list-trigger:focus-visible,
.schedule-mode-menu button:hover,
.schedule-mode-menu button:focus-visible,
.schedule-mode-menu button[aria-selected="true"],
.fan-body .table,
.rpt-chip.sb-list-trigger:hover,
.rpt-chip.sb-list-trigger:focus-visible,
.rpt-chip.sb-list-trigger[aria-expanded="true"],
.rpt-panel.sb-action-list .rpt-option:hover,
.rpt-panel.sb-action-list .rpt-option:focus-visible {
  background: transparent;
}

/* who-block: an identity block (avatar/initials + name) inside a row. Ruling: unattributed */
.who-block {
  margin-top: auto;
  flex-shrink: 0;
  position: sticky;
  bottom: 0;
  z-index: 2;
  background: var(--nav);
  padding-top: 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* A nav taller than the window reaches down the page and scrolls WITH the
   page (Matt, 2026-09-04: "can't you just have the sidebar go further down
   the page?"); it never clips its last items behind the account block and
   never scrolls on its own (in-page scrolling is locked out everywhere).
   The app adds is-tall from a measurement (nav.scrollHeight > innerHeight,
   desktop only); a nav that fits keeps the viewport-fixed column above.
   The .shell gradient already paints the sidebar color the full page
   height, so the column reads as one surface all the way down. */
@media (min-width: 901px) {
  .nav.is-tall {
    position: static;
    height: auto;
    min-height: 100dvh;
    overflow: visible;
  }
  .nav.is-tall .who-block {
    position: static;
  }
}

/* who: an identity (name/photo) cell. Ruling: unattributed */
.who {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr);
  gap: 12px;
  align-items: center;
  padding: 4px 2px 8px;
}

.who strong,
.who .muted {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* main: the shared main primitive. Ruling: unattributed */
/* The page wrapper: the one column every served page puts its head and its
   sections inside. `.sb-page` is the Creosote name for it and `.main` is the
   pre-Creosote alias every page still carries, so the geometry is declared
   once here rather than twice. First page that needed the sb-* name defined:
   manager_home_page.py (STYLE-BLOCKS-CREO, 2026-09-09) -- six pages already
   ship `class="main sb-page"` and were reaching for a name creo.css never
   declared. */
.main,
.sb-page {
  padding: 24px 28px 48px;
  max-width: none;
  min-width: 0;
  overflow-x: clip;
  text-align: left;
}

/* Shared page canvas name six manager pages already carry beside `.main`.
   First needed by the config page so home, requests, notes, settings, and
   people keep one wrapper class. Layout stays the `.main` padding. */
.sb-page,
.sb-page-section,
.sb-confirm-question,
.sb-pay-parent,
.sb-nest-group,
.sb-nest-parent,
.sb-summary-total,
.sb-record-copy,
.queue-row.sb-decision-row--request > *,
.sb-decision-row.sb-decision-row--request > *,
.sb-parallel-workflow > *,
.timesheet-daily-queue,
.timesheet-correction-detail,
.rules-category-content,
.sb-date-calendar-panel,
.template-role-name > div,
.account-form fieldset,
.btn-tight {
  min-width: 0;
}

/* page-head: the page header: eyebrow line, h1, and right-aligned page actions. Ruling: unattributed */
/* .sb-page-head is the sb-* name for the same header (slice 3, 2026-09-23).
   Every rule below that dresses .page-head dresses .sb-page-head too, so a
   new page composes the shared head without adding a legacy .page-head use
   (template contract, rule C). One header look, two names while older pages
   migrate. */
/* Tab or panel section on a settings page. First needed by config. */

.page-head,
.sb-page-head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: flex-end;
  gap: 16px;
  margin-bottom: 40px;
}

/* B10, 2026-09-03. A flex child's default `min-width: auto` refuses to shrink
   below its content, so a wide reading line in the title block pushed the
   action column past the right edge of the page instead of squeezing it: with
   the period picker in that column (note 16) the control left the viewport
   entirely at 1024px. Both children shrink, and the column wraps to its own
   row rather than overflowing, on every page that uses the shared head. */
.page-head > *,
.sb-page-head > * {
  min-width: 0;
}

.page-head > :first-child,
.sb-page-head > :first-child {
  flex: 1 1 min(100%, 420px);
}

/* B10 (note 16, 2026-09-03): the period picker that lives in a page head
   rather than in a control area above the working content. One shared slot
   so every payroll step -- and any later page that scopes its whole body to
   one period -- puts the control in the same place, at the same size, and
   opens its calendar the same way. The overlay is right-aligned here
   because the head anchors it at the right edge of the page, where the
   shared `.picker` default of `left: 0` would run it off screen. It is a
   modifier ON the shared `.schedule-date-picker-anchor`, never a second
   anchor: the geometry stays declared once, in that one rule. */
.sb-period-picker > .sb-date-picker {
  left: auto;
  right: 0;
}

/* Below the 900px breakpoint the head's action column wraps to the left of
   the page, so anchoring the calendar to the trigger's RIGHT edge would run
   it off the left of the screen (measured: left -203px at 390px wide). It
   anchors to the trigger's left edge there instead, which is the same
   behaviour every other `.picker` on the site already has. */
@media (max-width: 900px) {
  .sb-period-picker > .sb-date-picker {
    left: 0;
    right: auto;
  }
}

/* V1 defect 17 (note 54, 2026-09-03). The one shared confirmation's
   ARRANGEMENT: the question and its two answers on one line, lowercase,
   terracotta clickable text for both answers, no fill and no second colour.
   This rule says nothing about where the confirmation sits, which is why the
   employee requests page can still use it for an in-flow cancel row while
   `web_shell.py` uses it inside a popover (`.sb-confirm.sb-overlay` below). */
.sb-confirm {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  min-width: 0;
  color: var(--ink);
  text-align: left;
}

/* CONFIRM-POPOVER (Matt, 2026-09-08): "even if it's a pop-up that just says
   right below it that just says confirm like some two click option to make
   sure that I want to do it." `.sb-overlay` is what lifts the confirmation
   out of normal flow. It is fixed to the trigger's own rect (web_shell.py's
   `placeConfirm` writes left/top, and only left/top), so the trigger keeps
   its slot, no row on the page changes size or position while the question
   is open, and no ancestor's overflow can clip it. The BOX -- card ground,
   one thin line, the same 2px radius, the same shadow -- is not restated
   here: it comes from the shared overlay rule this joins, beside
   `.sb-action-list`, so there is one overlay treatment on the site and not
   two that drift. 60 matches the dialog menus, which is what a confirmation
   opened from inside a dialog's action row has to clear. */
.sb-confirm.sb-overlay {
  position: fixed;
  z-index: 60;
  max-width: min(340px, calc(100vw - 16px));
  padding: 8px 10px;
}

/* The reason-collecting variant: one inline field on the shared text-entry
   ground, never a browser prompt box. */
.sb-confirm-field {
  min-width: 0;
  flex: 1 1 14ch;
  min-height: 44px;
  padding: 0 12px;
  background: var(--field);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--ink);
  font: inherit;
}

/* sb-page: the page content wrapper geometry. Ruling: unattributed */
/* Shared manager-page surface primitives. These keep roster, settings, and
   schedule-adjacent pages on the same quiet EAE9E8 canvas. Controls that are
   actual buttons remain centered; labels and readings stay left aligned. */
.sb-page-actions,
.sb-section-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem 1rem;
}

/* sb-section: the shared section primitive. Ruling: unattributed */
.sb-section-actions {
  justify-content: flex-end;
  margin-bottom: 16px;
}

/* Shared four-stage review navigator. It is deliberately a quiet line in the
   page hierarchy, not a second navigation shell or a row of dashboard cards.
   Every stage names its deliverable and all four remain visible while the
   workflow reflows without an inner scrolling region. */
.sb-review-wizard {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  margin: 0 0 var(--sb-workspace-rhythm);
  border-block: 1px solid var(--divider);
}

/* V1 (defect 8). One vertical rhythm for the whole workspace. The step strip
   always ends the same distance above whatever comes next, a control strip
   directly under it keeps that same gap, and the first work surface never
   adds a margin of its own. Measured before this rule, the first surface sat
   24px under the strip on steps 1 and 3, 84px on step 4 and 106px on step 5. */
.sb-review-wizard + *,
.sb-review-wizard ~ * > .box:first-child,
.sb-review-wizard ~ .box:first-of-type,
.sb-panel > :first-child {
  margin-top: 0;
}

.sb-review-wizard ~ .filter-row,
.sb-review-wizard ~ .sb-filter-row,
.sb-review-wizard ~ .sb-callout,
.sb-review-wizard ~ .muted,
.sb-review-wizard ~ * > .sb-callout:first-child,
.sb-review-wizard ~ * .filter-row.schedule-text-list,
.sb-review-wizard ~ * > .days.sb-date-strip {
  margin-bottom: var(--sb-workspace-rhythm);
}

/* An empty reading line still took its margins, which put one page's first
   surface a paragraph lower than its neighbour's. */

/* V1 (2026-09-03, visual audit defect 3). Three things made the strip
   different on every page and every width: the state-dot track was `auto`,
   so a page whose dots had not loaded gave its labels more room and drew a
   SHORTER cell than the page beside it (measured at 1024px: 127px on tips
   against 169px on the review steps); the label was allowed to break inside
   a word (`overflow-wrap: anywhere` below), so "punch requests" rendered as
   "punch request / s"; and the cell had only a minimum height, so a label
   that wrapped to three lines made its own cell taller than its neighbours.
   The dot track is now a fixed 14px, words never split, the label and its
   deliverable line are each clamped to two lines, and the cell carries one
   fixed height, so every step cell on every payroll page is the same box. */
.sb-review-wizard > a {
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr) 14px;
  gap: 10px;
  align-items: center;
  min-width: 0;
  min-height: var(--sb-step-cell-height);
  height: var(--sb-step-cell-height);
  padding: 10px 12px;
  color: var(--ink);
  text-decoration: none;
  border-bottom: 2px solid transparent;
}

.sb-review-wizard > a + a,
.sb-date-strip .schedule-day + .schedule-day {
  border-left: 1px solid var(--divider);
}

/* Matt, 2026-09-10: "I don't like the framed numbers on the wizard look."
   The number keeps its fixed slot so the five stages align; it carries no
   frame. */
.sb-review-wizard > a > span:first-child {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* Matt, 2026-09-14 (13-group ruling, group 4): five steps on one row stay
   Team's, the rest of the wizard takes Creosote OS's layout -- the label and
   its deliverable line are plain blocks, not a two-line -webkit clamp. The
   cell still owns one fixed height and words still never split, so a long
   label is cut by the cell rather than ellipsized mid-word. */
.sb-review-wizard strong,
.sb-review-wizard small {
  display: block;
  overflow: hidden;
  overflow-wrap: normal;
  word-break: normal;
  line-height: 1.25;
}

.sb-review-wizard small {
  margin-top: .15rem;
  color: var(--muted);
  font-size: var(--type);
  line-height: 1.25;
  opacity: .7;
}

/* The dot occupies its own fixed track whether or not it is drawn yet, so
   the label width -- and therefore the cell height -- never depends on how
   far the status read has got. */
.sb-review-wizard > a > [data-payroll-state],
.sb-date-strip [data-day-state] {
  width: 14px;
  height: 14px;
}

/* V1 (2026-09-03, second pass). A title never clips. Measured at 1024px, the
   five-across strip left 62px for the label and "summary" needs 68, so the
   `overflow: hidden` that stops the clamp overflowing ate its last letter.
   Below 1200px the number badge and the gaps give the width back -- 86px,
   comfortably past the longest single word any step title carries -- and the
   deliverable line is the one allowed to run out of room first. */
@media (max-width: 1200px) {
  .sb-review-wizard > a {
    grid-template-columns: 20px minmax(0, 1fr) 10px;
    gap: 6px;
    padding: 10px 8px;
  }

  .sb-review-wizard > a > span:first-child {
    width: 20px;
    height: 20px;
  }

  .sb-review-wizard > a > [data-payroll-state] {
    width: 10px;
    height: 10px;
  }

  .sb-review-wizard strong {
    overflow: visible;
  }
}

.sb-review-wizard > a:hover,
.sb-review-wizard > a:focus-visible,
.sb-review-wizard > a[aria-current="step"] {
  color: var(--act);
  border-bottom-color: var(--act);
}

.sb-review-wizard > a[aria-current="step"] > span:first-child,
button.queue-row:hover strong,
.queue-row[aria-pressed="true"] strong,
.queue-row.is-selected strong,
.queue-row[aria-current="true"] strong,
.sb-decision-row[aria-pressed="true"] strong,
.sb-decision-row[aria-pressed="true"] .employee-link,
.sb-list-select:hover,
.sb-list-select:focus-visible,
.sb-ink-list-row:hover,
.sb-ink-list-row:focus-visible,
.sb-ink-list-row[aria-expanded="true"],
.sb-ink-list-row.is-open,
.sb-disclosure > summary:hover,
.sb-disclosure[open] > summary,
.secondary-details > summary:hover,
.secondary-details[open] > summary,
.sb-clickable-text:hover,
.sb-clickable-text:focus-visible,
.sb-clickable-text[aria-expanded="true"],
.dash-link:hover,
.dash-link:focus-visible,
.schedule-tool-link:hover,
.schedule-tool-link:focus-visible,
.sb-action-list > button:hover,
.sb-action-list > button:focus-visible,
.sb-action-list > a:hover,
.sb-action-list > a:focus-visible,
.schedule-text-list [role="option"]:hover,
.schedule-text-list [role="option"]:focus-visible,
.rpt-panel.sb-action-list .rpt-option:hover,
.rpt-panel.sb-action-list .rpt-option:focus-visible,
.sb-filter-choices > button:hover,
.sb-filter-choices > button:focus-visible,
.sb-filter-choices > button.is-on,
.shift-state,
.sb-employee-link:hover,
.sb-employee-link:focus-visible,
.employee-link:hover,
.employee-link:focus-visible {
  color: var(--act);
}

/* Ratified payroll-close P3 and P4 compositions, 2026-08-30. These are the
   exact shared selectors from the final reference implementation. */
.sb-review-wizard .warn {
  justify-self: end;
}

/* change: the shared change primitive. Ruling: unattributed */
.change {
  display: grid;
  /* B7-mount, 2026-09-03. Every track used to be unshrinkable -- 150px, then
     max-content, 24px, max-content -- so the reading could not narrow with the
     thing holding it. Measured at 390 px on step 1's punch-request card: the
     grid's scrollWidth 392 against a clientWidth of 332, and the right column
     ("6.92 h . est. +$35.94") ending 43 px past the card's own right edge.
     The tracks now size the way .sb-reading-grid's already do -- a floor of
     zero and a flexible cap -- which is what lets that grid reflow at narrow
     width instead of overflowing. One rule in the shared stylesheet, so punch
     requests, the person-day card and manager requests all get it; the page
     that showed the defect gets no rule of its own. */
  /* SW2, 2026-09-04. The "from" track was still `max-content`, which is not a
     flexible cap: a long reading (the manager request's shift exchange, "Sat,
     Sep 5 . Sep 5, 2026 at 10:00a to Sep 5, 2026 at 5:30p") took every pixel
     the row had and left the "to" track its 0 floor, so the value AFTER the
     arrow rendered one character per line down the edge of the card. Both
     value tracks are flexible now, which is what the note above says these
     tracks were changed to be; the label track and the arrow are unchanged. */
  grid-template-columns:
    minmax(0, 150px) minmax(0, 1fr) 24px minmax(0, 1fr);
  gap: 6px 12px;
  align-items: baseline;
  font-variant-numeric: tabular-nums;
  margin: 16px 0;
}

/* A grid item's automatic minimum size is its min-content width, which would
   push the cell back out past the track the line above just capped. Zero is
   the same floor .sb-reading-grid's own 1fr tracks give their cells. */
.change > * {
  min-width: 0;
}

/* The reading that carries the long value wraps rather than runs off, exactly
   as .sb-reading-value does. */
.change > :nth-child(4n) {
  overflow-wrap: anywhere;
}

.change .from {
  color: var(--ink);
  opacity: .65;
}

/* arrow: a small directional arrow glyph. Ruling: unattributed */
.change .arrow {
  opacity: .65;
  text-align: center;
}

.timeline,
.sb-coverage {
  display: grid;
  gap: 6px;
  margin: 8px 0 4px;
}

.tl-row,
.sb-coverage-row {
  display: grid;
  grid-template-columns: 110px minmax(0, 1fr);
  gap: 10px;
  align-items: center;
}

.tl-row .nm,
.sb-coverage-name {
  font-size: 14px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  opacity: .8;
}

.tl-track,
.sb-coverage-track {
  position: relative;
  height: 16px;
  border-block: 1px solid var(--divider);
}

.tl-bar,
.sb-coverage-bar {
  position: absolute;
  top: 3px;
  bottom: 3px;
  background: var(--line);
}

.tl-bar.is-you,
.sb-coverage-bar--you {
  background: var(--clay);
  box-shadow: inset 0 0 0 1px var(--act);
}

.tl-bar.is-open-end {
  border-right: 2px dashed var(--stop);
}

/* Slice 3 day bars (2026-09-23, employee timesheet at /timesheet). One
   day's schedule over its worked time on one shared track, the web twin of
   the iPhone time card's TimecardDayBars. The track spans the day's first to
   last instant on either side, so staying past the schedule shows past the
   end of the dashed bar. Each bar's role colour is the payload's own hex,
   set per bar as --bar-color, and its place on the track as --bar-start and
   --bar-span (percent of the track); those three data values are the only
   inline values, and every visual rule is here. The role colours are
   the pale schedule fills, so every bar carries an ink edge for contrast on
   the ground: DASHED for the schedule, SOLID for worked. A run nobody has
   clocked out of yet (.sb-day-bar--open) is the attn tone, as on the phone,
   so it never reads as hours worked to the end of the track. State is also
   said in words on the row beneath; the bars are decorative
   (aria-hidden). */
.sb-day-bars {
  display: grid;
  gap: 4px;
  margin: 8px 0;
}

.sb-day-bar-track {
  position: relative;
  height: 12px;
  background: var(--field);
  box-shadow: inset 0 0 0 1px var(--line);
  border-radius: var(--radius);
}

.sb-day-bar {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--bar-start, 0);
  width: var(--bar-span, 0);
  min-width: 2px;
  box-sizing: border-box;
  background: var(--bar-color, var(--line));
  border: 1px solid var(--ink);
  border-radius: var(--radius);
}

.sb-day-bar--scheduled {
  border-style: dashed;
}

.sb-day-bar--open {
  background: var(--attn);
}

.tl-scale,
.sb-coverage-scale {
  display: flex;
  justify-content: space-between;
  font-size: 12px;
  padding-left: 120px;
  opacity: .7;
  margin-bottom: 12px;
}

/* manager requests: same-day coverage graphic, formerly page-local tl-* */

/* B8 day-timeline primitives (cloud-api/app/day_timeline.py, 2026-09-03).
   Carried forward from agent/payroll-pass1-20260901 (f682d00, unmerged),
   which first drew these on the manager timesheets page's team timeline.
   They belong here rather than on that page (or on day_timeline.py's own
   generated markup) because the day-timeline component is page-independent
   and reused by more than one surface (the manager review card and, in
   parallel, lane B7's person-day card); any surface that shows "what
   happens if this pending change is approved" over a recorded bar reuses
   the same look instead of a page inventing its own. Job fill/border
   colors are set inline per bar from schedule_builder.ROLE_STYLE_SEED via
   day_timeline.py's roleStyleFor(), never hard-coded here. */
.sb-timeline-bar--proposed {
  position: absolute;
  top: 1px;
  bottom: 1px;
  background: transparent;
  border: 2px solid var(--act);
  box-sizing: border-box;
  border-radius: var(--radius);
  pointer-events: none;
}

/* B8B scheduled row (cloud-api/app/day_timeline.py, 2026-09-03): the
   person's published shift for the day, whether or not a punch was ever
   recorded against it, drawn as its own .tl-row above "as recorded". It
   reuses the base .tl-bar rectangle and the same job-colored inset border
   every other bar draws inline, but with no fill, so the solid recorded bar
   in the row beneath it reads against what was planned. */

.sb-timeline-tick {
  position: absolute;
  top: -3px;
  bottom: -3px;
  width: 2px;
  background: var(--ink);
  pointer-events: none;
}

.sb-timeline-tick--old {
  opacity: .55;
}

.sb-timeline-legend {
  display: flex;
  align-items: center;
  gap: 14px;
  font-size: 12px;
  opacity: .75;
  margin: 2px 0 8px;
}

.sb-timeline-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.sb-timeline-legend-swatch {
  display: inline-block;
  width: 16px;
  height: 10px;
  border-radius: var(--radius);
  box-sizing: border-box;
}

.sb-timeline-legend-swatch--recorded {
  background: var(--line);
}

.sb-timeline-legend-swatch--proposed {
  background: transparent;
  border: 2px solid var(--act);
}

/* sb-popover: a positioned popover panel. Ruling: unattributed */
/* Shared hover/focus popover. The trigger is a real <button> (keyboard
   focusable) positioned absolutely, so this popover -- itself absolutely
   positioned against that button -- only needs display toggled by CSS,
   with no JS event wiring for show/hide. */
.sb-popover-trigger {
  position: absolute;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}

.sb-popover {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  background: var(--card);
  border: 1px solid var(--divider);
  border-radius: var(--radius);
  padding: 6px 8px;
  font-size: 12px;
  line-height: 1.4;
  white-space: nowrap;
  box-shadow: 0 12px 32px rgb(35 31 32 / 0.18);
  z-index: 20;
  display: none;
  text-align: left;
}

.sb-popover-trigger:hover .sb-popover,
.sb-popover-trigger:focus .sb-popover,
.sb-popover-trigger:focus-visible .sb-popover,
.day b,
.sb-config-choice,
.chip-title,
.sales-chart {
  display: block;
}

button.queue-row,
button.person,
button.map-row,
.template-lock-cycle,
.pay-leaf,
.pay-child {
  cursor: pointer;
}

.queue-row[aria-pressed="true"],
.queue-row.is-selected,
.queue-row[aria-current="true"],
.sb-decision-row[aria-pressed="true"] {
  box-shadow: inset var(--nest-rail) 0 0 var(--act);
}

/* sb-panel: a bordered content panel (queue-and-detail, dialogs). Ruling: unattributed */
.sb-panel {
  background: var(--card);
  color: var(--ink);
  border-radius: var(--radius);
}

/* A single selected or open object may become the active work surface without
   turning the surrounding page into a card workspace. */
.sb-panel--active {
  padding: 16px;
  background: var(--field);
  border: var(--divider-width) solid var(--divider);
}

.sb-panel > :last-child,
.dash-alert:last-child,
.dash-head .rpt-toolbar {
  margin-bottom: 0;
}

/* Editable text fields (Matt, 2026-09-13, verbatim): "any text editable field
   needs to be fafafa in all of creo". Matt, 2026-09-14: this rule lives in the
   shared sheet, not in an app sheet, because "unified css is required".
   Every text-editable surface consumes --field (#fafafa) here. Focus rings,
   validation cues, buttons, checkboxes, radios, ranges, files, and colors are
   untouched. Same block as Creosote OS creo.css (PR146). */
.sb-field,
.sb-filter-row input,
.sb-filter-row select,
.sb-kiosk-search input,
input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="color"]):not([type="image"]):not([type="hidden"]),
textarea,
select,
[contenteditable]:not([contenteditable="false"]),
[role="textbox"],
[role="combobox"]:not(button) {
  background-color: var(--field);
}

/* Shared operational surfaces. These are deliberately content-agnostic so a
   decision queue, an audit table, or a balance table can keep the same
   rhythm on every Team page. */
.sb-section-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-width: 0;
  /* The standard already says this head "wraps naturally on small screens";
     without flex-wrap it could not, and a section head carrying a real
     control (D15's month chooser) pushed that control off the right edge at
     390px, where .main's overflow-x: clip hid it. Fixed on the shared
     primitive, not on the page. */
  flex-wrap: wrap;
}

/* sb-label: the shared label primitive. Ruling: unattributed */
.sb-label-trigger-row .sb-list-select,
.sb-section-head .sb-list-select {
  max-width: 100%;
}

.sb-section-head h2,
.sb-section-head h3,
.sb-section-head h4 {
  min-width: 0;
  margin: 0;
}

/* V1 (defect 6). The head wraps only when there is genuinely no room: the
   heading takes the slack and the disclosure door holds its intrinsic width,
   so "show" stays an inline door beside its heading instead of dropping to a
   line of its own the moment the heading needs two lines. */
.sb-section-head > :first-child {
  flex: 1 1 auto;
  min-width: 0;
}

.sb-section-head > :last-child:not(:first-child),
.sched-view-by-head .schedule-view-by-label,
.pay-table .person-cell > .avatar,
.pay-table .person-cell > .initials {
  flex: 0 0 auto;
}

.sb-section-head--compact,
.rules-category-content h2,
.pay-edit h2 {
  margin: 0 0 12px;
}

/* fact-grid: the shared fact grid primitive. Ruling: unattributed */
/* Canonical Creosote reading hierarchy. `.sb-reading` is one labelled
   figure inside `.sb-reading-grid`; legacy `.fact` classes remain as
   compatibility aliases while older pages migrate to the sb-* markup. The
   item wrapper carries no geometry of its own -- the grid places it and the
   label/value rules dress its children -- so it is declared here as the
   scope those two rules read, exactly as `.fact` always was. First pages
   that needed the sb-* name declared: employee_time_off_page.py and
   employee_notifications_page.py (STYLE-BLOCKS-CREO, 2026-09-09). */
/* Matt, 2026-09-14 (13-group ruling, group 10): reading grids take Creosote
   OS's hairline grid -- a 1px gap over a divider-colored ground, so the gap
   itself draws the lines between facts -- "with no fixed column cap: columns
   fit their content so nothing spills to a second row unless it must". A 9rem
   minimum keeps four to seven facts on one row at 1,200px and up and wraps
   only when the width truly runs out. */
/* People redesign, 2026-09-22. The gap-over-divider ground painted every
   unfilled track: three facts in a two-column grid left a solid divider-tone
   block in the fourth slot, the loudest thing on the People record. Each tile
   now draws its own hairline (a 1px spread shadow that fills the 1px gap it
   shares with its neighbours), and the grid clips the outer edge, so the lines
   between facts are unchanged and an empty slot shows the surface beneath. */
.sb-reading-grid,
.fact-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
  gap: 1px;
  margin: 12px 0;
  background: transparent;
  overflow: clip;
  min-width: 0;
}

.sb-reading-grid > *,
.fact-grid > * {
  box-shadow: 0 0 0 1px var(--divider);
}

.sb-reading-label,
.sb-reading dt,
.sb-reading > span,
.fact dt,
.fact > span,
.sb-record-label,
.sb-record-meta {
  display: block;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.2;
}

/* sb-reading: the canonical reading hierarchy (labelled values). Ruling: unattributed */
.sb-reading-value,
.sb-reading dd,
.fact dd {
  margin: 0;
  color: var(--ink);
  font-size: var(--type);
  line-height: 1.3;
  overflow-wrap: anywhere;
}

/* Canonical reading sentence (D15, 2026-09-03). The one plain-words sentence
   that sits under a section heading, or under a table, and says what the
   numbers in it mean -- the density standard's "tables get a reading
   sentence". It is body text on the page ground, never a boxed callout and
   never a page-local muted paragraph, so the same explanation reads the same
   wherever a table needs one. Several may stack when a table has more than
   one column worth explaining. */
.sb-reading-sentence {
  margin: 0 0 8px;
  max-width: 78ch;
  color: var(--ink);
  font-size: var(--type);
  line-height: 1.4;
  text-align: left;
}

.sb-reading-sentence + .table-wrap,
.sb-reading-sentence + table {
  margin-top: 12px;
}

/* sb-check: a checklist item control. Ruling: unattributed */
/* Labeled choice-chip group. First needed by manager_timesheets_page. */
.sb-choice-field,
.sb-check-list {
  display: grid;
  gap: 8px;
  min-width: 0;
}

/* Tips close-out check list. First needed by manager_timesheets_page. */

/* One tips-check row. First needed by manager_timesheets_page. */
.sb-check-item {
  display: grid;
  gap: 4px;
  min-width: 0;
}

/* Tips-check checkbox label. First needed by manager_timesheets_page. */
.sb-check-pick {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

/* The four rules below name the grouped pay-table nest so one set of class
   names covers every page that draws one. Each row becomes a grid or block
   under `.sb-data-table--responsive` (see the stacked-table block far below),
   where `min-width: 0` is what stops a long cell from pushing the row wider
   than the shell. First needed by manager_timesheets_page. */

/* Seven equal weekday columns, Sun through Sat. Hours sit in fixed slots
   so a missing day never shifts its neighbours. Originating use is the
   time-off decision dialog's paid-hours strip. */
.sb-week-strip-stack {
  display: grid;
  gap: 8px;
  margin: 0 0 12px;
}

.sb-week-strip {
  table-layout: fixed;
  width: 100%;
  margin: 0;
}

.sb-week-strip th,
.sb-week-strip td {
  text-align: center;
  padding: 6px 4px;
}

/* Canonical compact records. These support employee files, role/pay rows,
   audit records, and other operational lists without page-specific skins. */
.sb-record-list,
.person + .person,
.person-block + .person-block,
.queue-row + .queue-row,
.table td,
.shift + .shift,
.mail-row + .mail-row,
.audit-block + .audit-block,
.fan-block + .fan-block {
  border-top: 1px solid var(--divider);
}

/* ROT-DOOR (Matt, 2026-09-09): the templates rotation door is the first <ul> consumer -- strip the default list indent and markers, .sb-record-row already carries the grid layout. */
.sb-record-list:is(ul) {
  list-style: none;
  margin: 0;
  padding: 0;
}

.sb-record-grid {
  display: grid;
  gap: 8px 20px;
  margin: 12px 0 16px;
}

.sb-record-grid--two {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* Matt, 2026-09-14 (13-group ruling, group 7): "five-column record rows with
   business facts" is the shared row -- a name column plus four fact columns,
   .6rem/1rem gaps, .75rem .5rem padding, and no default control chrome so a
   row that is a button reads as a row. A row that carries only a name and one
   action has no third child and collapses to the two tracks Team renders
   today (see `.sb-record-row:not(:has(> :nth-child(3)))` below).
   `cursor: pointer` is not declared here for the same reason as the decision
   row above. */
.sb-record-row {
  appearance: none;
  display: grid;
  grid-template-columns: minmax(11rem, 1.4fr) repeat(4, minmax(6rem, .8fr));
  gap: .6rem 1rem;
  align-items: center;
  width: 100%;
  min-height: 72px;
  padding: .75rem .5rem;
  border: 0;
  border-bottom: 1px solid var(--divider);
  background: transparent;
  color: var(--ink);
  text-align: left;
}

/* Team compact record row: "flexible primary plus trailing action", two
   tracks rather than the five-column fact layout above. */
.sb-record-row--compact {
  grid-template-columns: minmax(0, 1fr) auto;
  min-height: 44px;
  padding: 4px 0;
}

/* Person-day work rows carry the same role edge as their timeline bars. */
.sb-record-row--role {
  box-shadow: inset 5px 0 0 var(--role-edge);
  padding-left: 10px;
}

.sb-record-row.is-expired {
  box-shadow: inset 3px 0 0 var(--stop);
  padding-left: 12px;
}

/* Matt 2026-09-08: the row's edit is a text link in its own column at the
   right edge, never a box over the readings. */
.sb-record-row > .open-day-editor {
  justify-self: end;
  align-self: start;
  white-space: nowrap;
  margin-top: 2px;
}

.sb-record-title {
  display: block;
  margin-top: 3px;
  color: var(--head);
  line-height: 1.25;
}

.sb-record-meta,
.sb-record-note {
  margin: 4px 0 0;
}

.sb-record-note {
  color: var(--ink);
  font-size: 13px;
}

/* A sentence inside a note row wraps with the row (the schedule's rotation
   band lists one seat per line inside a day cell). */
.sb-record-note > .warn {
  display: inline;
  white-space: normal;
}

.sb-record-note > .warn > i {
  display: inline-block;
  vertical-align: middle;
  margin-right: 6px;
}

.sb-record-row.is-attention .sb-record-meta,
.dash-alert strong,
.dash-alert div,
.events-add:hover,
.signal.is-alert b {
  color: var(--rust);
}

/* Matt 2026-09-08: a row's trailing slot lays its reading and its actions
   side by side in fixed order (reading, then each button), never one over
   another. */
.sb-record-action {
  display: inline-flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  white-space: nowrap;
}

/* A compact row's trailing reading pairs sit inline beside each other rather
   than as padded, backgrounded `.sb-reading` tiles, so this strips the tile
   treatment down to the label/value stack. */
.sb-record-reading {
  color: var(--head);
  white-space: nowrap;
  background: none;
  padding: 0;
  gap: .15rem;
  min-width: 5rem;
}

.sb-record-action .btn {
  min-width: 96px;
}

/* PEOPLE-PTO (Matt, 2026-09-10). A trailing slot that carries MORE THAN ONE
   reading needs fixed columns, not an inline-flex run: with intrinsic widths
   the second reading starts at a different x on every row (a signed "-8.00 h"
   is narrower than "+90.00 h"), which is the ragged alignment Matt's fixed-slot
   law rules out. Same tracks on every row, at every width. Originating use is
   the People record's time off ledger: change, then balance after. */
.sb-record-action--columns {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 96px;
  gap: 8px 16px;
  align-items: start;
}

.sb-record-row:has(> .sb-record-link) {
  grid-template-columns: minmax(0, 1fr) auto var(--sb-record-link-slot, 2.75rem);
}

.sb-record-link {
  grid-column: -1;
  justify-self: end;
  align-self: start;
  min-width: 0;
  text-align: right;
}

/* sb-empty: the shared empty primitive. Ruling: unattributed */
.sb-empty-state {
  margin: 0;
  padding: 1rem .5rem;
  border-top: 1px solid var(--divider);
}

@media (max-width: 700px) {
  .sb-record-grid--two,
.sb-record-row,
.dash-labor,
.dash-sales,
.dash-split,
.crew-rotation-board,
.person-profile-workspace,
.person-profile-records,
.person-profile-group .sb-reading-grid,
.access-editor-grid {
    grid-template-columns: 1fr;
  }

  .sb-record-row:has(> .sb-record-link) {
    grid-template-columns: minmax(0, 1fr) auto var(--sb-record-link-slot, 2.75rem);
  }

  .sb-record-action {
    flex-wrap: wrap;
    justify-content: flex-start;
  }

  .sb-record-action .btn {
    width: 100%;
  }

  /* EW1, 2026-09-04. `.sb-decision-row` is avatar, content, status. On a
     phone the status track's own words ("waiting on a decision") took most of
     the row and squeezed the content column to three or four characters a
     line, so a time-off request read as a vertical ribbon. Below 700px the
     status moves under the content. Scoped away from `.queue-row`, which is
     the fixed-height scan row and keeps its own 104px status track at every
     width, so no row that carries both classes changes. */
  .sb-decision-row:not(.queue-row) {
    grid-template-columns: 40px minmax(0, 1fr);
    align-items: start;
    row-gap: 6px;
  }

  .sb-decision-row:not(.queue-row) > .warn {
    grid-column: 2;
  }

}

.sb-decision-surface,
.sb-decision-row + .sb-decision-row,
.sb-data-table thead tr:first-child th,
.table.sb-grouped-table > tbody + tbody tr:first-child td {
  border-top: var(--divider-width) solid var(--divider);
}

.sb-decision-queue {
  display: grid;
  gap: 0;
}

/* Matt, 2026-09-14 (13-group ruling, group 6): decision rows take Creosote
   OS's look -- a button row with no default control chrome, its own hairline
   under it, .25rem gaps and .85rem .5rem padding. `cursor: pointer` is not
   declared here on purpose: the shared base layer already gives every real
   button one, and a Team decision row that is a plain `div` must not show a
   clickable cursor for a click it does not take. */
.sb-decision-row {
  appearance: none;
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr) auto;
  gap: .25rem;
  align-items: center;
  min-width: 0;
  min-height: 56px;
  padding: .85rem .5rem;
  border: 0;
  border-bottom: 1px solid var(--divider);
  background: transparent;
  color: var(--ink);
  text-align: left;
}

/* manager requests: name, fact, category, status, and next action stay in
   fixed columns beside the photo. Two classes beat `.queue-row`'s later
   40px / 1fr / 104px scan-row grid so those slots cannot collapse. */
.queue-row.sb-decision-row--request,
.sb-decision-row.sb-decision-row--request {
  grid-template-columns: 40px minmax(92px, 1.2fr) minmax(90px, 0.95fr) minmax(64px, 0.72fr) minmax(96px, 1.25fr) minmax(44px, 0.44fr);
  column-gap: 10px;
  height: auto;
  min-height: 64px;
  cursor: pointer;
}

/* manager requests: the fact slot carries the dates, which is the reason the
   column exists. `.sb-queue-meta`'s scan-row ellipsis cut "Sun, Sep 20 to
   Tue, Sep 22" down to "Sun, Sep 2..." in a 95px slot, so inside this row the
   fact wraps within its fixed slot instead of hiding half of itself. */
.queue-row.sb-decision-row--request > .sb-queue-meta,
.sb-decision-row.sb-decision-row--request > .sb-queue-meta {
  overflow: visible;
  white-space: normal;
  text-overflow: clip;
}

/* manager requests: attention sentence owned by the request decision row */
.sb-decision-warn,
.dash-head h2,
.crew-member-person label,
.crew-member-pattern label,
.dialog-head h2,
.history-list p,
.ask-strip .search {
  margin: 0;
}

.queue-row.sb-decision-row--request > .sb-decision-warn,
.sb-decision-row.sb-decision-row--request > .sb-decision-warn {
  grid-column: 2 / -1;
}

/* TIMEOFF-PAGE, Matt 2026-09-10. The approvals list on Time off is the same
   decision row, carrying six facts in fixed slots: person, dates, hours,
   kind, the decision, and who decided it and when. Two classes, the same way
   `--request` does it, so it beats `.queue-row`'s later scan-row grid. */
.queue-row.sb-decision-row--approval,
.sb-decision-row.sb-decision-row--approval {
  grid-template-columns: minmax(150px, 1.15fr) minmax(118px, 1fr) minmax(58px, 0.5fr) minmax(96px, 0.85fr) minmax(92px, 0.75fr) minmax(132px, 1.15fr);
  column-gap: 10px;
  height: auto;
  min-height: 64px;
}

.queue-row.sb-decision-row--approval > *,
.sb-decision-row.sb-decision-row--approval > * {
  min-width: 0;
}

/* The hours slot is a figure column even outside a table: same tabular
   figures `.table .num` gives every other hours column, so the decimals
   line up down the list. */
.queue-row.sb-decision-row--approval > :nth-child(3),
.sb-decision-row.sb-decision-row--approval > :nth-child(3) {
  font-variant-numeric: tabular-nums;
}

/* The dates slot exists to hold the dates; a scan-row ellipsis that cut
   "Sun, Sep 20 to Tue, Sep 22" in half would defeat the column. Same fix
   `--request` already carries for the same reason. */
.queue-row.sb-decision-row--approval > .sb-queue-meta,
.sb-decision-row.sb-decision-row--approval > .sb-queue-meta {
  overflow: visible;
  white-space: normal;
  text-overflow: clip;
}

/* LAYOUT-1 (Matt, 2026-09-23), tips gate queue: a failed check's fact line
   names every ticket it touches (day, count, tips, who was clocked in) and
   can run to a full sentence or more. `.queue-row`'s base 64px is a fixed
   `height`, not a floor, and nothing on the row clips overflow, so a wrapped
   fact line broke out of its 64px box and visually covered the queue rows
   below it and the card underneath ("the week, per employee") instead of
   growing the row to hold it. `--wrap` keeps the base icon/body/status grid
   (unlike `--request`/`--approval`, which redraw their own columns) and only
   frees the height, the same override those two already carry. */
.queue-row.sb-decision-row--wrap,
.sb-decision-row.sb-decision-row--wrap {
  height: auto;
  min-height: 64px;
}

/* Below the width six fixed slots need, the row stacks: the person keeps the
   first line and the five facts read under the name, each still in its own
   full-width slot rather than reflowing into a ragged wrap. */
@media (max-width: 1080px) {
  .queue-row.sb-decision-row--approval,
  .sb-decision-row.sb-decision-row--approval {
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
    row-gap: 4px;
    padding-block: 12px;
  }

  /* The shared narrow-width decision row puts its first child in a 40px
     avatar rail and everything else in a second column. This row's first
     child is the whole identity field (avatar, name, role), which does not
     fit a 40px rail, so the stack is one column here and every slot claims
     it. */
  .queue-row.sb-decision-row--approval > *,
  .sb-decision-row.sb-decision-row--approval > * {
    grid-column: 1;
    grid-row: auto;
  }
}

/* manager requests: `.sb-parallel-workflow--queue-detail` is EQUAL columns
   (B9 defect 9), so the queue lane is half the page: 570px at 1440 and 490px
   at 1280. Six fixed slots need 506px, so below 1340 the row keeps photo and
   name on the first line and stacks fact, category, status and next action
   under the name rather than running its last two slots outside the lane. */
@media (max-width: 1340px) {
  .queue-row.sb-decision-row--request,
  .sb-decision-row.sb-decision-row--request {
    grid-template-columns: 40px minmax(0, 1fr);
    align-items: start;
    row-gap: 6px;
  }

  .queue-row.sb-decision-row--request > :nth-child(n + 3),
  .sb-decision-row.sb-decision-row--request > :nth-child(n + 3) {
    grid-column: 2;
  }
}

/* sb-data: the shared data primitive. Ruling: unattributed */
/* Matt, 2026-09-14 (13-group ruling, group 5): data tables take Creosote
   OS's look -- collapsed borders, one hairline under every cell, .75rem .5rem
   padding and middle-aligned text -- with numbers right-justified
   (`.num`, below). */
.sb-data-table {
  width: 100%;
  border-collapse: collapse;
  background: var(--card);
  color: var(--ink);
}

.sb-data-table th,
.sb-data-table td {
  padding: .75rem .5rem;
  border-right: var(--divider-width) solid var(--divider);
  border-bottom: var(--divider-width) solid var(--divider);
  text-align: left;
  vertical-align: middle;
}

.sb-data-table th:first-child,
.sb-data-table td:first-child {
  border-left: var(--divider-width) solid var(--divider);
}

/* Link row (Matt, 2026-09-14, directory versus queue): in a directory table
   the whole row opens the record. `is-link` on the tr gives the pointer and
   the control hover tone; the name cell still carries the real anchor. */
.sb-data-table tbody tr.is-link { cursor: pointer; }
.sb-data-table tbody tr.is-link:hover td,
.sb-data-table tbody tr.is-link:focus-within td { background: var(--hair); }
/* Wide text column (2026-09-14): a directory column that carries a phrase
   (relationship, last sold with its qualifier) keeps enough width to read on
   one or two lines instead of wrapping into a tall row. The responsive table
   is `table-layout: fixed` from 901px, where only a width on the header cell
   counts, so this is a share of the table, not a minimum. */
.sb-data-table th.is-wide { width: 13%; }
/* Directory table (2026-09-14): a many-column directory (Catalog: nine
   columns) gives its name column a quarter of the table, not the 40% the
   two-column responsive table reserves for its primary column, so the short
   columns keep room for a word like "Guatemala" or "unavailable". */
.sb-data-table--directory th.sb-primary-column,
.sb-data-table--responsive.sb-data-table--directory th.sb-primary-column,
.main table.sb-data-table--responsive.sb-data-table--directory th.sb-primary-column { width: 25%; }

.sb-data-table th {
  background: var(--card);
  color: var(--ink);
  font-size: .8rem;
  font-weight: 400;
  opacity: .65;
}

/* Canonical scan column (D15, 2026-09-03, note 38). When two columns of one
   table measure nearly the same thing and only one of them answers the
   manager's question, that one is the scan column: it is placed FIRST of the
   pair and carries the approved --clay fill, the same token .tl-bar.is-you
   already uses to mean "this is the one that is yours". No second text
   colour, no bold weight, no invented shade -- the reading sentence under the
   table still says in words which column to scan and why. Mark the th and
   every td in that column. */
.sb-data-table th.sb-scan-column,
.sb-data-table td.sb-scan-column,
.table th.sb-scan-column,
.table td.sb-scan-column,
.table tr.is-open td,
.swatch.is-prior {
  background: var(--clay);
}

/* Canonical identity/name column (2026-09-07). Opt-in: mark the matching
   th and td. Packet 1 already gives `.main table.sb-data-table--responsive`
   `table-layout: fixed` at min 901px; this width rule is written to that
   same table-mode query so it cannot leak into the 900px labeled-record
   reflow. The header cell is 40% and the remaining short columns share
   60%. Suited to one long identity column plus several short ones (six
   columns is the originating Reference case). Not a global first-child
   width, not a page selector, and it does not change sorting or aria-sort. */
@media (min-width: 901px) {
  .sb-data-table--responsive th.sb-primary-column,
  .sb-data-table--responsive td.sb-primary-column,
  .main table.sb-data-table--responsive th.sb-primary-column,
  .main table.sb-data-table--responsive td.sb-primary-column {
    width: 40%;
  }
}

/* A reading wider than its container (TIPS-STEP4, 2026-09-09).

   `.sb-table-scroll` exists so a table can be wider than the page and scroll
   inside itself. Packet 1's `table-layout: fixed` fights that: it divides the
   CONTAINER into equal tracks whatever the content is, so a fourteen-column
   reading got 80px a column and broke "expected/hr" into "ex-pected /hr".
   Design law: nothing wraps mid-word. Opt a table in when it is inside a
   scroller and its columns should size to what they hold. */
.main .sb-table-scroll > table.sb-wide-table,
.sb-table-scroll > table.sb-wide-table {
  table-layout: auto;
  width: max-content;
  min-width: 100%;
}

/* Canonical identity/name column for a WIDE table (TIPS-STEP4, 2026-09-09).
   The sibling of `.sb-primary-column` above, and opt-in the same way: mark
   the th and every matching td. `.sb-primary-column` is 40%, which suits one
   long identity column beside several short ones; a ten-column money table
   crushes every figure at that width, so this one is a fixed track instead.

   It has to set a WIDTH, not a min-width. Packet 1 gives `.main table
   .sb-data-table--responsive` `table-layout: fixed` at min 901px, and fixed
   layout ignores a cell's min-width: the column that carried an ellipsised
   name inside a `min-width: 0` grid was measured at 36px, so the name
   rendered at zero width and the "employee" header broke one letter per
   line. Matt's words on that exact defect: "names wrapping one letter per
   line". Written to the same width query as `.sb-primary-column` so it
   cannot leak into the labeled-record reflow below 901px. */
.sb-identity-column,
.dash-labor .stat b,
.dash-sales .stat b,
.sb-date-strip .sb-date-full,
.sb-date-strip .sb-date-compact,
.sched-forecast-total > span,
.sched-forecast-day > span,
.sb-summary-cell > span,
.pay-table td,
.table.rpt-table .num {
  white-space: nowrap;
}

@media (min-width: 901px) {
  .sb-data-table--responsive th.sb-identity-column,
  .sb-data-table--responsive td.sb-identity-column,
  .main table.sb-data-table--responsive th.sb-identity-column,
  .main table.sb-data-table--responsive td.sb-identity-column {
    width: 11rem;
  }
}

/* Canonical heat strip (D15, 2026-09-03, note 37: "a week heat strip (day by
   hour, tips per bar-hour)"). One row per day, one column per slot the server
   returned. Intensity is the HEIGHT of a single terracotta fill against the
   busiest slot -- never a lighter or darker terracotta, which section 1 of the
   design standard forbids -- so the strip needs no invented shades. Every
   square is focusable and carries its exact figure as its accessible name, so
   the strip is readable in words and not only by eye. */
.sb-heat-strip {
  display: grid;
  gap: 2px;
  margin: 12px 0 4px;
}

.sb-heat-row {
  display: grid;
  grid-template-columns: 92px minmax(0, 1fr);
  gap: 8px;
  align-items: center;
}

.sb-heat-label {
  color: var(--ink);
  font-size: 12px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.sb-heat-track {
  display: grid;
  gap: 1px;
  min-width: 0;
}

.sb-heat-cell {
  position: relative;
  display: block;
  height: 18px;
  min-width: 0;
  background: var(--card);
  border-bottom: 1px solid var(--divider);
}

.sb-heat-cell:focus-visible {
  outline: 2px solid var(--act);
  outline-offset: 1px;
}

.sb-heat-fill {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  display: block;
  background: var(--act);
}

.sb-heat-scale .sb-heat-tick {
  display: block;
  min-width: 0;
  color: var(--ink);
  font-size: 11px;
  white-space: nowrap;
}

/* V1 (2026-09-03, defect 12). Between the phone reflow at 900px and full
   desktop there is a band where the 228px sidebar leaves the content column
   under 800px while a many-column data table still has to fit in it. At
   1024px the tips-rate table measured 825px against a 740px column and
   `.table-wrap`'s `overflow-x: hidden` simply ate the last columns --
   clipped data, which section 3.5 bans. Tightening the shared cell padding
   in that band buys the width back rather than hiding it. */
@media (max-width: 1200px) {
  .table.sb-data-table th,
  .table.sb-data-table td,
  .sb-data-table th,
  .sb-data-table td {
    padding: 10px 8px;
  }
}

/* Shared compact-table contract. Responsive data tables become labeled
   records instead of forcing a nested horizontal scroller or widening the
   page. Page markup supplies the labels because only the page knows each
   column's meaning.

   B13: this family's widest measured member (the tips daily matrix -
   employee + 7 weekday columns + week total) has a natural min-content
   width around 813px, which still clips at tablet widths under a 700px
   activation (.table-wrap hides the overflow instead of scrolling it, so
   the clipped columns simply vanish). Raised to 900px so the reflow always
   wins before the clip does, with headroom for real employee names. */
@media (max-width: 900px) {
  .sb-data-table--responsive:not(.pay-table) {
    display: block;
    width: 100%;
  }

  .sb-data-table--responsive:not(.pay-table) thead,
.audit-head {
    display: none;
  }

  .sb-data-table--responsive:not(.pay-table) tbody,
  .sb-data-table--responsive:not(.pay-table) tfoot,
  .sb-data-table--responsive:not(.pay-table) tr {
    display: grid;
    width: 100%;
    min-width: 0;
  }

  .sb-data-table--responsive:not(.pay-table) tr {
    gap: 4px;
    padding: 8px 0;
    border-bottom: var(--divider-width) solid var(--divider);
  }

  .sb-data-table--responsive:not(.pay-table) td {
    display: grid;
    grid-template-columns: minmax(96px, 34%) minmax(0, 1fr);
    gap: 8px;
    min-width: 0;
    padding: 2px 0;
    border: 0;
    overflow-wrap: break-word;
    word-break: normal;
  }

  .sb-data-table--responsive:not(.pay-table) td[data-label]::before {
    content: attr(data-label);
    display: block;
    color: var(--ink);
  }
}

/* sb-editor: the shared editor primitive. Ruling: 2026-09-13 Matt "wider working area" */
.sb-editor-dialog {
  background: var(--card);
  color: var(--ink);
}

/* Matt, 2026-09-13 (MATT-DECISIONS.md, "wider working area"): the editor
   form fills whatever column it sits in. A dialog that wants 520px sizes
   itself (.sb-editor-dialog already does), not the form. */
.sb-editor-form,
.sb-editor-form.sb-form--wide {
  width: 100%;
  max-width: none;
  margin: 0;
}

/* 13-group fold, 2026-09-14 (group 2, text fields and forms). The editor form
   lays its labelled fields out itself, one gap between them, rather than
   leaving each field to carry a margin. Declared as its own rule so the block
   above keeps the exact text Team's people-page editor contract pins. */
.sb-editor-form {
  display: grid;
  gap: .75rem;
  min-width: 0;
}

/* editor-section: the shared editor section primitive. Ruling: unattributed */
.sb-editor-form .editor-section {
  margin: 0;
  padding: 0;
  border: 0;
}

.sb-editor-form .editor-section h3 {
  margin: 0 0 8px;
  color: var(--head);
  font-size: var(--type-lg);
  font-weight: 400;
}

/* check: the shared check primitive. Ruling: unattributed */
.sb-editor-form .check-group {
  margin: 0;
  padding: 0;
  border: 0;
  display: grid;
  gap: 8px;
}

.sb-editor-form .check-group legend {
  margin: 0 0 6px;
  padding: 0;
  color: var(--ink);
  font-size: var(--type);
}

/* dialog-actions: the shared dialog actions primitive. Ruling: unattributed */
.sb-editor-form .editor-actions,
.sb-editor-form .dialog-actions {
  margin-top: 4px;
}

/* sb-status: a status line (state + numbers, no sentences). Ruling: unattributed */
/* Matt, 2026-09-14 (13-group ruling, group 11): the status line keeps
   Creosote OS's 2px action-colored left rail, 1rem margins and tighter
   padding. */
.sb-status {
  display: block;
  margin: 1rem 0;
  min-height: 2rem;
  padding: .35rem .75rem;
  border-top: var(--divider-width) solid var(--divider);
  border-left: 2px solid var(--act);
  background: var(--card);
  color: var(--ink);
  text-align: left;
}

/* error: an inline error message. Ruling: unattributed */
.sb-status.error {
  border-left: 4px solid var(--stop);
}

/* V1 second pass (coordinator item 8, 2026-09-03). A page with no period
   basis to report -- tip settings -- was painting the empty pill anyway. An
   empty reading is not a reading. */

.sb-callout,
.schedule-message,
#publish-reason:not([hidden]) {
  display: block;
  margin: 0 0 16px;
  padding: 10px 12px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-left: 4px solid var(--act);
  border-radius: var(--radius);
  color: var(--ink);
  text-align: left;
}

/* sb-dialog: the shared modal dialog shell. Ruling: unattributed */
.sb-dialog {
  width: min(760px, calc(100vw - 28px));
  max-width: calc(100vw - 28px);
  max-height: calc(100dvh - 28px);
  margin: auto;
  padding: 0;
  border: var(--divider-width) solid var(--divider);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--ink);
  box-shadow: 0 24px 70px rgb(35 31 32 / 0.28);
}

.sb-dialog::backdrop,
.shift-dialog::backdrop {
  background: rgb(35 31 32 / 0.48);
}

.sb-dialog-body {
  padding: 20px;
}

.sb-dialog-close {
  min-width: 0;
  width: auto;
}

/* sb-list: the shared list primitive. Ruling: unattributed */
.sb-list-select {
  position: relative;
  min-height: 44px;
  padding: 0 12px;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--ink);
  font: inherit;
  text-align: left;
}

/* sb-ink: the shared ink primitive. Ruling: unattributed */
.sb-ink-list-row {
  color: var(--ink);
  text-align: left;
}

/* sb-disclosure: a collapsible "more detail" disclosure. Ruling: unattributed */
/* Disclosure summaries are openable text lists, not boxed buttons. The
   browser still supplies the disclosure affordance, while the shared
   schedule treatment keeps the label left aligned and terracotta on hover or
   while open. */
.sb-disclosure > summary,
.secondary-details > summary {
  color: var(--ink);
  cursor: pointer;
  text-align: left;
}

/* sb-reserved: the shared reserved primitive. Ruling: unattributed */
/* One reserved text line that keeps sibling columns aligned when only
   some columns have words. Used by the schedule week header rotation
   line (ROT-VIEW). Never a page-local height rule. */
.sb-reserved-line {
  display: block;
  min-height: 1.25em;
  line-height: 1.25;
  color: var(--ink);
}

.page-head h1,
.sb-page-head h1 {
  margin: 0;
  font-size: var(--type-head);
  line-height: 1.1;
  font-weight: 400;
  color: var(--head);
}

/* eyebrow: the small caps label line above a page or section heading. Ruling: unattributed */
.page-head .eyebrow {
  margin: 0 0 4px;
  color: var(--muted);
  font-size: var(--type);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: lowercase;
}

.eyebrow {
  margin: 0 0 4px;
  color: var(--muted);
  font-size: var(--type);
  letter-spacing: 0;
  text-transform: lowercase;
}

.page-head p,
.sb-page-head p {
  margin: 6px 0 0;
  color: var(--muted);
  font-size: var(--type);
  line-height: 1.3;
  font-weight: 400;
}

/* dialog-tabs: the shared dialog tabs primitive. Ruling: unattributed */
.row,
.dialog-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

/* Canonical vertical rhythm between a decision or action row and a panel
   that expands beneath it in the same card - an inline editor, a revealed
   form, a disclosure toggle. Every such reveal reuses this one value
   instead of a page margin or an implicit flush zero gap. */
.sb-stack-gap,
.audit-list {
  margin-top: 16px;
}

.schedule-control-area {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
  margin: 0 0 24px;
}

.schedule-mode-row,
.schedule-date-row {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  width: 100%;
}

.schedule-primary-controls {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  width: 100%;
}

.schedule-date-trigger {
  min-width: 168px;
  justify-content: center;
  text-align: center;
}

.schedule-date-picker-anchor {
  position: relative;
  display: inline-flex;
  align-items: center;
}

.schedule-view-by-label {
  color: var(--ink);
  white-space: nowrap;
  text-align: left;
}

.schedule-view-by-trigger {
  min-width: 148px;
  padding: 6px 10px;
}

.schedule-menu-anchor {
  position: relative;
  display: flex;
  align-items: center;
}

.schedule-menu-anchor > .box {
  position: absolute;
  z-index: 40;
  top: calc(100% + 4px);
  left: 0;
  width: min(360px, calc(100vw - 24px));
  margin: 0;
}

/* Author display rules override the browser's built-in [hidden] rule. Keep
   every schedule popover truly absent until its controller opens it. */

.schedule-menu-anchor > #tools-menu {
  display: grid;
  gap: 2px;
  min-width: 220px;
}

.schedule-tool-link {
  display: block;
  width: 100%;
  padding: 6px 0;
  border: 0;
  background: transparent;
  font: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

.schedule-tool-link:hover,
.schedule-tool-link:focus-visible,
a.person,
a.map-row,
a.btn {
  text-decoration: none;
}

.schedule-primary-controls > .schedule-menu-anchor:last-child,
.fan-toggle .muted {
  margin-left: auto;
}

.schedule-message.error-text {
  border-left-color: var(--stop);
}

/* btn: the shared button (secondary gray, primary orange, and the tight-padding variant). Ruling: unattributed */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  min-width: 160px;
  padding: 0 14px;
  background: var(--hair);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  cursor: pointer;
  color: var(--ink);
  text-align: center;
}

.btn:hover {
  background: var(--face);
  color: var(--act);
}

.btn-primary {
  background: var(--act);
  color: var(--on-action);
  border-color: var(--act);
  font-weight: 400;
}

.btn-primary:hover,
.btn-primary:focus-visible,
.btn-primary:active {
  background: var(--act);
  color: var(--on-action);
  border-color: var(--act);
}

.btn:disabled,
.choice:disabled,
.sb-action-list > button:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

#publish:disabled {
  opacity: 1;
  background: var(--hair);
  border-color: var(--line);
  color: var(--ink);
}

/* is-published: the shared is published primitive. Ruling: unattributed */
#publish.is-published:disabled {
  opacity: 0.55;
  background: var(--act);
  border-color: var(--act);
  color: var(--on-action);
}

/* choice: a clickable pill/toggle control (date range, filter, view-by). Ruling: unattributed */
.choice {
  display: inline-flex;
  align-items: center;
  justify-content: flex-start;
  min-height: 44px;
  padding: 0 14px;
  border-radius: var(--radius);
  background: transparent;
  border: 0;
  color: var(--ink);
  text-align: left;
  cursor: pointer;
}

.choice:hover {
  background: transparent;
  color: var(--act);
}

.choice.is-on {
  background: transparent;
  color: var(--act);
}

/* Page status belongs to the page anatomy, not to whichever component first
   needed a message. Empty live regions disappear; populated ones use the same
   compact card and divider treatment as Schedule's message area. */
.main > p[role="status"][aria-live] {
  min-height: 0;
  margin: -24px 0 16px;
  padding: 8px 12px;
  border-top: var(--divider-width) solid var(--divider);
  background: var(--card);
  color: var(--ink);
}

/* Shared plain-list primitives used by schedule and other compact surfaces. */
.sb-label-trigger-row {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.sb-list-anchor,
.template-shift-item {
  position: relative;
  min-width: 0;
}

/* sb-action: the shared action primitive. Ruling: unattributed */
.sb-list-anchor > .sb-action-list {
  position: absolute;
  z-index: 40;
  top: calc(100% + 4px);
  right: 0;
  min-width: 220px;
}

/* A list that opens under a trigger at the START of a full-width row (the
   day card's add) hangs from the left edge, under its own link. */
.sb-list-anchor--start > .sb-action-list {
  left: 0;
  right: auto;
}

/* A cell-menu is placed from the trigger rect (fixed). The shared anchor
   still owns the stacking context; it must not switch that menu to
   absolute and invalidate those coordinates. First needed by the schedule
   employee menu. */
.sb-list-anchor > .cell-menu.sb-action-list {
  position: fixed;
  top: auto;
  right: auto;
  min-width: 180px;
}

.sb-list-trigger {
  display: inline-flex;
  align-items: center;
  justify-content: flex-start;
  min-height: 44px;
  padding: 8px 10px;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

/* The shared overlay box. Everything that floats over the page on this site
   is this box: one card ground, one thin line, the 2px radius, one shadow.
   The confirm popover joined it on 2026-09-08 rather than restating four
   values that would then have two places to drift. */
.sb-action-list,
.sb-confirm.sb-overlay,
.sb-date-picker.sb-overlay {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 12px 32px rgb(35 31 32 / 0.18);
}

.sb-action-list {
  display: grid;
  gap: 4px;
  padding: 4px;
}

/* Dialog menus must sit above the shared sticky action row. Keeping this
   contract here lets every dialog-backed text list share one stacking rule. */
.shift-dialog .sb-action-list,
.shift-dialog .schedule-mode-menu {
  z-index: 60;
}

.sb-action-list > button,
.sb-action-list > a {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  width: 100%;
  min-height: 44px;
  margin: 0;
  padding: 0 12px;
  border: 0;
  background: transparent;
  font: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

/* sb-summary: the shared summary primitive. Ruling: unattributed */
.sb-summary-band {
  display: grid;
  gap: var(--summary-gap);
  background: var(--divider);
  border-top: var(--divider-width) solid var(--divider);
}
/* Band with as many equal cells as fit (Reporting store totals, source
   freshness). The schedule band sets its own day columns. */
.sb-summary-band--auto {
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
}

/* Canonical Creosote clickable-text component. Legacy class names below are
   aliases for compatibility, not separate visual definitions. */
.sb-clickable-text,
.dash-link,
.schedule-tool-link,
.sb-list-trigger,
.sb-action-list > button,
.sb-action-list > a,
.schedule-text-list [role="option"],
.rpt-panel.sb-action-list .rpt-option {
  color: var(--act);
  font: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}

/* sb-clickable: the shared clickable primitive. Ruling: unattributed */
button.sb-clickable-text {
  border: 0;
  background: transparent;
}

.schedule-text-list,
.schedule-mode-picker {
  position: relative;
}

.employee-menu-trigger.sb-list-trigger {
  min-height: 0;
  padding: 0;
}

.schedule-text-list [role="listbox"] {
  position: absolute;
  z-index: 40;
  top: calc(100% + 4px);
  left: 0;
  min-width: 180px;
  padding: 4px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
  box-shadow: 0 12px 32px rgb(35 31 32 / 0.18);
}

.schedule-text-list [role="option"] {
  display: block;
  width: 100%;
  min-height: 44px;
  padding: 0 12px;
  background: transparent;
  border: 0;
  text-align: left;
}

/* grid: the shared grid primitive. Ruling: unattributed */
.grid {
  display: grid;
  gap: 24px;
}

/* sb-parallel: the shared parallel primitive. Ruling: unattributed */
/* Shared Creosote composition for two related lanes that benefit from being
   visible together. The page remains the only scroll container and the lanes
   stack in normal flow when simultaneous visibility stops helping. */
/* Matt, 2026-09-14 (13-group ruling, group 12): "the 43/57 queue-and-detail
   split" is the shared split. The queue lane reads a list, the detail lane
   reads a record, and the record needs the wider half. This supersedes the
   2026-09-03 V1 defect-9 equal-columns decision for both the base composition
   and the queue-detail modifier below. */
.sb-parallel-workflow {
  display: grid;
  grid-template-columns: minmax(15rem, .8fr) minmax(18rem, 1.2fr);
  gap: clamp(1rem, 3vw, 2rem);
  align-items: start;
}

/* V1 (2026-09-03, defect 9). B9's own acceptance line is "left list plus
   right card, EQUAL column widths"; the .7/1.3 split left the queue column
   at 458px against a 760px card at 1440px, and squeezed it to 300px at
   1024px, where the queue's own text then broke inside words. Equal columns
   at every width the two lanes are side by side. */
.sb-parallel-workflow--queue-detail {
  grid-template-columns: minmax(15rem, .8fr) minmax(18rem, 1.2fr);
}

/* Shared editor-and-detail composition. The writing lane gets the working
   width, while the supporting lane keeps enough room for readable queue
   metadata. At compact desktop and tablet widths the pair stacks before
   either lane becomes a squeezed half-page column. */
.sb-parallel-workflow--editor-detail {
  grid-template-columns: minmax(0, 1.4fr) minmax(380px, .6fr);
}

.sb-parallel-workflow--review {
  grid-template-columns: 1fr;
}

.sb-parallel-column {
  display: grid;
  gap: 16px;
  min-width: 0;
  align-content: start;
}

.timesheet-correction-detail {
  align-self: start;
}

.rules-categories {
  display: grid;
  align-content: start;
  min-width: 0;
  border-top: 1px solid var(--divider);
}

.rules-category-option {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  width: 100%;
  min-height: 52px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--divider);
  text-align: left;
}

.rules-category-option.is-on,
.month-day.is-week {
  box-shadow: inset 4px 0 0 var(--act);
}

.box,
.pay-edit {
  background: var(--card);
  border-radius: var(--radius);
  padding: 16px;
  border-top: 1px solid var(--line);
}

/* A native select can still serve as a text-list trigger on compact manager
   surfaces. */
.main select.sb-list-select {
  min-height: 44px;
  padding: 0 12px;
  border: 0;
  background: transparent;
  text-align: left;
}

/* box: a bordered content box (legacy shared shell). Ruling: unattributed */
.box h2 {
  margin: 0 0 12px;
  font-size: var(--type-sub);
  line-height: 1.15;
  font-weight: 400;
  color: var(--head);
  letter-spacing: -0.02em;
  text-transform: none;
}

/* stats: a row of stat tiles. Ruling: unattributed */
.stats {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

/* stat: a labelled stat tile (label + big value). Ruling: unattributed */
.stat {
  padding: 12px 16px 4px 0;
}

.stat + .stat {
  border-left: 1px solid var(--divider);
  padding-left: 16px;
}

.stat span {
  display: block;
  font-size: var(--type);
  color: var(--shadow);
}

.stat b {
  display: block;
  margin-top: 4px;
  font-size: var(--type-sub);
  font-variant-numeric: tabular-nums;
}

.dash-card {
  margin-bottom: 16px;
}

.dash-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 16px;
}

.dash-link {
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}

.dash-labor,
.dash-sales,
.roster,
.sched-compact {
  display: grid;
  gap: 16px;
}

.dash-labor {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 20px 24px;
}

.dash-sales {
  grid-template-columns: repeat(4, minmax(0, 1fr));
  margin: 20px 0 8px;
}

.dash-labor .stat,
.dash-sales .stat {
  padding: 0;
  min-width: 0;
  overflow: hidden;
}

.dash-labor .stat + .stat,
.dash-sales .stat + .stat,
.pay-strip .stat + .stat {
  border-left: 0;
  padding-left: 0;
}

.dash-split {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
  gap: 16px;
  margin-bottom: 16px;
}

.dash-decision {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.dash-decision .btn {
  min-width: 88px;
}

.dash-alert {
  margin: 0 0 16px;
  color: var(--rust);
}

/* num: a right-aligned numeric table cell. Ruling: unattributed */
/* V1 (defect 4). Money and hours are read by scanning down a column and
   comparing magnitudes, so the shared numeric cell is right aligned on
   tabular figures. Section 1's "all ordinary text is left aligned" still
   governs every other cell; `.num` marks the ones that are not text. */
.table .num,
.sb-data-table .num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.people,
.sched-compact-days {
  display: grid;
}

.people-layout {
  display: grid;
  grid-template-columns: minmax(280px, 0.8fr) minmax(0, 1.2fr);
  gap: 16px;
  align-items: start;
}

.person {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  min-height: 56px;
  padding: 10px 0 10px 12px;
  background: var(--card);
  width: 100%;
  text-align: left;
  color: inherit;
  box-shadow: inset var(--nest-rail) 0 0 var(--rail, transparent);
}

/* Ordinary and secondary anchor-rendered buttons take the ambient text
   color of their surrounding surface. The filled primary variant is
   excluded here so its own `.btn-primary` white-on-terracotta color (set
   below) is never overridden back to inherited ink text - an anchor tag
   must render identically to a <button class="btn btn-primary">. */
a.person,
a.map-row,
a.btn:not(.btn-primary) {
  color: inherit;
}

a.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-align: center;
}

.person-name {
  color: inherit;
  font-weight: 700;
  text-decoration: none;
}

.person-name:hover,
.person-name:focus-visible {
  text-decoration: underline;
}

/* map-flag: the shared map flag primitive. Ruling: unattributed */
.map-flag {
  color: var(--rust);
  white-space: nowrap;
}

.legend-live {
  background: var(--clay);
  box-shadow: inset 4px 0 0 var(--act);
}

.month-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  border-top: 1px solid var(--divider);
  border-left: 1px solid var(--divider);
}

.month-weekday {
  padding: 8px;
  border-right: 1px solid var(--divider);
  border-bottom: 1px solid var(--divider);
  background: var(--card);
  color: var(--muted);
  text-align: left;
}

.month-day {
  min-height: 88px;
  padding: 8px;
  border-right: 1px solid var(--divider);
  border-bottom: 1px solid var(--divider);
  display: grid;
  align-content: start;
  gap: 4px;
  background: var(--card);
  color: inherit;
  text-align: left;
}

.month-day.is-out {
  color: var(--muted);
  background: var(--paper);
}

.month-day small {
  color: var(--muted);
}

/* photo: a small square avatar/photo thumbnail. Ruling: date unknown Matt "the names need avatars" */
.photo {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--hair);
  font-size: var(--type);
  color: var(--ink);
  flex: none;
  position: relative;
  overflow: hidden;
}

/* Real photo avatar layers over the initials fallback beneath it; if the photo
   is missing or fails to load, the initials show through (Matt: "the names need
   avatars", one shared rule so it flows to every Creosote page). */
.photo > img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* chip: a small pill-shaped label/tag. Ruling: unattributed */
.chip {
  display: inline-flex;
  align-items: center;
  min-height: 28px;
  padding: 0 10px;
  border-radius: var(--radius);
  font-size: var(--type);
  background: var(--face);
  color: var(--shadow);
  white-space: nowrap;
  border: 0;
  text-align: left;
  cursor: pointer;
}

.chip:hover,
.chip:focus-visible {
  background: transparent;
  color: var(--act);
}

/* A `.chip` used as a single-select toggle inside a `.fields` group (e.g. a
   category picker) marks the chosen one with `aria-pressed="true"` -- the
   one shared "selected" look for that pattern, so a new chip-group toggle
   never invents its own selected style. */

.chip-held {
  background: var(--hair);
  color: var(--ink);
}

/* warn: a warning/attention icon or chip. Ruling: unattributed */
.warn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--ink);
  font-weight: 400;
  white-space: nowrap;
}

/* is-sentence: the shared is sentence primitive. Ruling: unattributed */
/* B10, 2026-09-03. `.warn` is a status CHIP -- "3 blocked", "built 12:42 pm" --
   and `nowrap` is what keeps its dot with its words. A few places hand it a
   full SENTENCE instead (the payroll authority note), and an unwrappable
   sentence sets the whole page-head's minimum width: at 390px it forced a
   1,027px head and clipped the period control off the screen. This modifier
   is the one shared way to say "this chip holds a sentence": the text wraps,
   the dot stays put on the first line. Never restate it as a page rule. */
.warn.is-sentence {
  align-items: flex-start;
  white-space: normal;
}

.warn.is-sentence i {
  margin-top: 7px;
  transform: none;
  /* a wrapped sentence's first line: centre the dot on its lowercase words */
  translate: 0 -0.25em;
}

.warn i {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex: none;
  background: var(--ink);
  /* Matt 2026-09-09: the dot centres on the lowercase words, not on the
     line box. Measured: the line box centre sits on the baseline, the
     x-height centre a quarter em above it. */
  transform: translateY(-0.25em);
}

/* status: a status dot/line indicator. Ruling: unattributed */
.warn.is-ok i,
.status.is-ok::before {
  background: var(--ok);
}

.warn.is-attn i,
.status.is-wait::before,
.status.is-attn::before {
  background: var(--attn);
}

.warn.is-stop i,
.status.is-off::before,
.status.is-stop::before {
  background: var(--stop);
}

/* is-quiet: the shared is quiet primitive. Ruling: unattributed */
/* TIMEOFF-PAGE, Matt 2026-09-10. The quiet end of the same dot ladder: a
   state that is true and needs nothing ("open" on a time-off day cell).
   Ink, ok, attn and stop all read as "look at me"; this one does not, and
   the word beside it still carries the meaning on its own. */
.warn.is-quiet i {
  background: var(--line);
}

.warn-key {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin: 0 0 16px;
  color: var(--ink);
}

.pay-layout {
  display: grid;
  grid-template-columns: 280px minmax(0, 1fr);
  gap: 16px;
  align-items: start;
}

.pay-edit .account-form {
  max-width: none;
}

/* queue: a left-hand list of clickable one-line rows for triage/review. Ruling: unattributed */
.queue {
  display: grid;
  gap: 0;
  background: var(--card);
  border-radius: var(--radius);
}

/* V1 (2026-09-03, visual audit defects 1 and 2). This is THE decision-queue
   row for the payroll workspace: step 1's punch requests and step 2's review
   days are one primitive, not two rows that happen to resemble each other.
   Its shape is avatar, one title line, one meta line, one status word -- all
   three text runs clamped, so a long sentence can never grow the row into
   its neighbour (Cole's second line ran into Jonathan's row) and the status
   column is a fixed track so every row is exactly as wide as every other.
   `.sb-queue-row` is the canonical name; `.queue-row` stays as the alias the
   existing markup already carries. */
.queue-row,
.sb-queue-row {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr) 104px;
  gap: 12px;
  align-items: center;
  min-height: 64px;
  height: 64px;
  padding: 12px 16px;
  width: 100%;
  text-align: left;
  background: transparent;
  color: inherit;
  box-shadow: inset var(--nest-rail) 0 0 var(--rail, transparent);
}

.queue-row > :nth-child(2) {
  min-width: 0;
  overflow-wrap: normal;
  word-break: normal;
}

/* One line each, ellipsised. The full sentence is the detail card's job. */
.sb-queue-title,
.sb-queue-meta,
.sb-person-name,
.sb-person-note {
  display: block;
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.sb-queue-meta,
.muted,
.sb-person-note {
  color: var(--muted);
  font-size: var(--type);
}

.queue-row > .warn,
.sb-queue-row > .warn {
  justify-self: start;
  min-width: 0;
}

/* filter-row: a row of filter controls above a list/table. Ruling: unattributed */
/* B15: canonical control-row rule. .sb-filter-row was a near-identical
   parallel definition (same box model, plus align-items: center); collapsed
   here as a single source of truth rather than two rules that drift.
   Markup migration off the sb-* name stays out of scope.

   B14 (R8: control rows are grids of equal-width cells): auto-fit with a
   96px floor keeps short chip/tab labels (all / attention / blocked, the
   dashboard's five view tabs, etc.) equal width per row and wraps onto a
   new row of equally-sized cells at phone widths instead of shrinking past
   readability or scrolling. */
/* Matt, 2026-09-14 (13-group ruling, group 3: filter rows, toolbars and the
   date strip take Creosote OS's look): the control row is a WRAPPING FLEX
   row, not a grid of equal cells. A filter's own control widths differ (a
   date input is not a search box), so equal tracks either stretched a short
   control or squeezed a long one; wrapping lets each control keep its own
   width and drop to the next line when the row runs out. One definition for
   both names, replacing the three copies with three different gaps that the
   Creosote OS sheet carried. This supersedes B14's auto-fit equal-cell rule
   for these rows. Controls align on their BOTTOM edge (`align-items: end`) so
   a labelled control and a bare one sit on one line. */
.filter-row,
.sb-filter-row {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  gap: .75rem 1rem;
  margin: 0 0 1rem;
}

.sb-filter-row label {
  display: grid;
  gap: .35rem;
  font-size: .8rem;
  opacity: .72;
  text-transform: lowercase;
}

.filter-row .choice.is-on {
  background: transparent;
  color: var(--act);
}

/* sb-tabs: the shared tabs primitive. Ruling: unattributed */
.tabs,
.sb-tabs {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
  margin-bottom: 12px;
}

.tabs button,
.sb-tabs button {
  min-height: 44px;
  width: 100%;
  padding: 0 8px;
  background: var(--card);
  border-radius: var(--radius);
  cursor: pointer;
  color: var(--ink);
}

/* sb-filter: the shared filter primitive. Ruling: unattributed */
/* Matt 2026-09-09: filters are filters, not a wizard. A filter row of text
   choices sits at the START of its row beside the list trigger; the tab
   grid is for steps. First needed by manager requests. */
.sb-filter-row--choices {
  grid-template-columns: auto auto;
  justify-content: start;
  align-items: baseline;
  gap: 24px;
}

.sb-filter-choices {
  display: flex;
  flex-wrap: wrap;
  gap: 0 20px;
  align-items: baseline;
}

.sb-filter-choices > button {
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--muted);
  font: inherit;
  cursor: pointer;
}

/* days: a row of day cells. Ruling: unattributed */
.days {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 8px;
  margin-bottom: 16px;
}

/* Shared operational date strip. Matt 2026-09-09: it reads like the step
   strip above it, the same segmented cells, dividers, dot track and one
   fixed cell height, so the two strips share one grammar. Seven dates fit
   as a week at wider widths; at narrow widths each date keeps its status on
   its own readable row instead of colliding inside seven squeezed columns. */
.days.sb-date-strip {
  gap: 0;
  margin: 0 0 var(--sb-workspace-rhythm);
  border-block: 1px solid var(--divider);
}

.sb-date-strip .schedule-day {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 14px;
  gap: 10px;
  align-items: center;
  min-width: 0;
  min-height: var(--sb-step-cell-height-compact);
  height: var(--sb-step-cell-height-compact);
  padding: 10px 12px;
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  background: transparent;
  color: var(--ink);
  text-align: left;
  white-space: normal;
}

.sb-date-strip .schedule-day.is-on,
.sb-date-strip .schedule-day.is-on:hover {
  color: var(--act);
  background: transparent;
  border-bottom-color: var(--act);
}

.sb-date-strip .sb-date-copy {
  display: block;
  min-width: 0;
}

/* The dot occupies its own fixed track whether or not it is drawn, the same
   rule the step strip's state dot follows. */

/* V1 (2026-09-03). The shared operational date strip carries both date forms
   and shows exactly one: the full "Sun, Dec 1" while seven columns can hold
   it, the compact "Sun 1" below that, where the full form wrapped onto two
   lines. The accessible name is always the full form. */

@media (max-width: 1200px) {
  .sb-date-strip .sb-date-full {
    display: none;
  }

  .sb-date-strip .sb-date-compact {
    display: inline;
  }
}

.sb-date-strip .schedule-day small {
  display: block;
  margin-top: 2px;
  font-size: var(--type);
  line-height: 1.25;
}

@media (max-width: 700px) {
  .days.sb-date-strip {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 4px 12px;
  }
}

/* day: a single calendar/schedule day cell. Ruling: unattributed */
.day {
  min-height: 64px;
  background: var(--card);
  border-radius: var(--radius);
  padding: 8px;
  text-align: left;
  cursor: pointer;
  color: var(--ink);
}

.day span {
  font-size: var(--type);
  color: var(--shadow);
}

.day.is-on span {
  color: var(--on-action);
}

/* table: the shared data table shell (borderless card, responsive reflow). Ruling: unattributed */
.table-wrap {
  overflow-x: hidden;
  max-width: 100%;
}

.table {
  width: 100%;
  border-collapse: collapse;
  background: var(--card);
  border-radius: var(--radius);
}

.sb-calendar-table {
  table-layout: fixed;
}

.table th,
.table td {
  padding: 12px 14px;
  text-align: left;
  vertical-align: top;
}

/* sb-grouped: the shared grouped primitive. Ruling: unattributed */
/* Shared grouped-table contract: keep role rows visually together and mark
   only the boundary between team-member groups. */
.table.sb-grouped-table tbody tr td {
  border-top: 0;
}

.table th {
  background: var(--face);
  color: var(--muted);
  font-size: var(--type);
  font-weight: 400;
  letter-spacing: 0;
  text-transform: lowercase;
}

/* sort: a clickable column-sort indicator. Ruling: unattributed */
.table th .sort {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  min-height: 44px;
  padding: 0;
  background: transparent;
  cursor: pointer;
  text-align: left;
}

.table th .sort::after {
  content: "";
  color: var(--shadow);
}

.table th[aria-sort="ascending"] .sort::after {
  content: "^";
}

.table th[aria-sort="descending"] .sort::after {
  content: "v";
}

/* fields: a form input row/grid wrapper. Ruling: unattributed */
.fields {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 12px;
}

.fields .chip {
  min-height: 44px;
  cursor: pointer;
}

/* pivot: the shared pivot primitive. Ruling: unattributed */
.pivot {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
  margin-bottom: 12px;
}

/* bin: a delete/trash target (drag-to-remove). Ruling: unattributed */
.bin {
  min-height: 88px;
  padding: 12px;
  border-radius: var(--radius);
  background: var(--face);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.bin.is-over {
  outline: 3px solid var(--act);
  outline-offset: 2px;
}

.bin > span,
.search::placeholder,
.lane > span {
  color: var(--shadow);
}

.bin .chip {
  min-height: 44px;
  cursor: pointer;
  align-self: flex-start;
}

.sb-date-picker.sb-overlay {
  position: fixed;
  z-index: 60;
  display: grid;
  grid-template-columns: 180px minmax(0, 1fr);
  width: min(680px, calc(100vw - 24px));
  max-width: calc(100vw - 24px);
  margin: 0;
  padding: 10px;
  gap: 10px;
}

.sb-date-picker.sb-overlay.sb-date-picker--single {
  grid-template-columns: minmax(0, 1fr);
}

.sb-date-presets {
  display: grid;
  align-content: start;
  gap: 1px;
  padding-right: 10px;
  border-right: 1px solid var(--divider);
}

.sb-date-presets button {
  min-height: 36px;
  width: 100%;
  background: transparent;
  border: 0;
  border-radius: var(--radius);
  color: var(--ink);
  text-align: left;
  padding: 0 10px;
  font: inherit;
  cursor: pointer;
}

.sb-date-months {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 10px;
}

.sb-date-months > section {
  min-width: 0;
  padding: 4px 6px 6px;
  border: 1px solid var(--divider);
}

.sb-date-month-row,
.sb-date-months > section > .row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 28px;
}

.sb-date-month-row strong,
.sb-date-months > section > .row strong {
  font-weight: 400;
}

.sb-date-weekdays,
.sb-date-dates {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 1px;
}

.sb-date-weekdays span {
  text-align: left;
  font-size: 13px;
  color: var(--shadow);
  padding: 4px 2px;
}

.sb-date-dates button {
  min-height: 30px;
  padding: 0 2px;
  background: transparent;
  border: 0;
  border-radius: var(--radius);
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.sb-date-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 6px;
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--divider);
}

.sb-date-actions .muted {
  flex: 1 1 auto;
  margin: 0;
  text-align: left;
}

.sb-date-field-trigger {
  display: flex;
  align-items: center;
  min-height: 44px;
  width: 100%;
  padding: 0 12px;
  background: var(--field);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  color: var(--act);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

@media (max-width: 900px) {
  .sb-date-picker.sb-overlay {
    grid-template-columns: minmax(0, 1fr);
  }

  .sb-date-presets {
    padding-right: 0;
    padding-bottom: 8px;
    border-right: 0;
    border-bottom: 1px solid var(--divider);
    grid-auto-flow: column;
    overflow-x: auto;
  }

  .sb-date-months {
    grid-template-columns: minmax(0, 1fr);
  }
}

.jobs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}

.job {
  min-height: 88px;
  border-radius: var(--radius);
  padding: 12px;
  background: var(--card);
  text-align: left;
  cursor: pointer;
  font: inherit;
  color: inherit;
  text-decoration: none;
  display: block;
}

.job b {
  display: block;
  margin-bottom: 4px;
}

/* search: a generic search input. Ruling: unattributed */
.search {
  min-height: 44px;
  width: 100%;
  max-width: 420px;
  padding: 0 12px;
  border-radius: var(--radius);
  background: var(--card);
  margin-bottom: 12px;
}

.employee {
  background: var(--card);
  border-radius: var(--radius);
  overflow: hidden;
}

.employee-head {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  min-height: 56px;
  padding: 12px 16px;
  background: var(--card);
  width: 100%;
  text-align: left;
}

.shifts {
  margin: 0 12px 12px;
  background: var(--face);
  border-radius: var(--radius);
  box-shadow: inset 6px 0 0 var(--act);
}

.shift {
  padding: 12px 14px;
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) 1fr 1fr;
  gap: 12px;
}

.shift small {
  display: block;
  color: var(--shadow);
}

/* bars: a small bar-chart glyph/indicator. Ruling: unattributed */
.bars {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 8px;
  align-items: end;
  height: 180px;
}

/* bar: a horizontal progress/value bar. Ruling: unattributed */
.bar {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: 44px;
  gap: 8px;
}

.bar i {
  display: block;
  width: 100%;
  border-radius: var(--radius);
  background: var(--act);
  height: var(--h, 40%);
}

.bar span {
  text-align: left;
  white-space: nowrap;
}

.sched {
  display: grid;
  grid-template-columns: 184px repeat(7, minmax(0, 1fr));
  /* Keep row and column divisions as light as the shared dashboard rule.
     The grid background makes the one-pixel gaps explicit, instead of
     letting the old eight-pixel card spacing read as heavy rails. */
  gap: var(--divider-width);
  background: var(--divider);
  align-items: stretch;
}

.sched-head,
.sched-name,
.sched-cell {
  background: var(--card);
  border-radius: var(--radius);
  min-height: 64px;
  min-width: 0;
  padding: 8px;
}

.schedule-mode-picker .sb-list-trigger {
  min-height: 44px;
  padding: 0 10px;
  border: 0;
  background: transparent;
  text-align: left;
  font: inherit;
  cursor: pointer;
}

.schedule-mode-menu {
  position: absolute;
  z-index: 30;
  top: calc(100% + 4px);
  left: 0;
  display: grid;
  min-width: 120px;
  gap: 4px;
  padding: 4px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 12px 28px rgb(35 31 32 / 0.14);
}

.schedule-mode-menu button {
  min-height: 40px;
  padding: 0 10px;
  border: 0;
  background: transparent;
  text-align: left;
  font: inherit;
  cursor: pointer;
}

.sched-cell {
  position: relative;
  padding-right: 12px;
  transition: padding-right 120ms ease;
}

.sched-cell:not(.is-empty):has(.cell-add-rail):hover,
.sched-cell:not(.is-empty):has(.cell-add-rail):focus-within {
  padding-right: 62px;
}

.sched-head {
  display: flex;
  flex-direction: column;
  justify-content: center;
  background: var(--face);
}

/* The people-column control belongs to the grid header, beside the first day
   header, so navigation labels and dates share one horizontal alignment line. */
.sched-view-by-head {
  justify-content: center;
  align-items: flex-start;
}

.sched-view-by-head .sb-label-trigger-row {
  width: 100%;
  align-items: center;
  gap: 4px;
}

.sched-view-by-head .schedule-view-by-trigger {
  min-width: 0;
  max-width: 100%;
  padding-left: 4px;
  padding-right: 4px;
}

.sched-name {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr);
  gap: 8px;
  align-items: center;
}

.sched-name strong,
.sched-name .muted {
  overflow: visible;
  text-overflow: clip;
  white-space: normal;
  overflow-wrap: break-word;
}

.sched-compact-card {
  background: var(--card);
  border-top: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
}

.sched-compact-person {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr);
  gap: 12px;
  align-items: center;
  padding: 12px 16px;
  box-shadow: inset var(--nest-rail) 0 0 var(--rail, transparent);
}

.sched-compact-person .employee-menu-trigger strong,
.sched-compact-person .muted {
  white-space: normal;
  overflow-wrap: break-word;
}

.sched-compact-day {
  display: grid;
  grid-template-columns: 104px minmax(0, 1fr);
  gap: 12px;
  align-items: start;
  padding: 10px 12px;
  border-top: 1px solid var(--divider);
}

.sched-compact-day-label {
  padding: 8px 0;
  color: var(--muted);
}

.sched-compact-day .sched-cell {
  padding: 0;
  min-height: 44px;
  background: transparent;
}

.sched-compact-summary {
  padding: 12px 16px;
}
.sched-compact-group {
  margin: var(--space-4) 0 0;
  padding: 0 var(--space-2);
  color: var(--muted);
  font-size: .8rem;
  letter-spacing: .04em;
  text-transform: uppercase;
}

.sched-cell {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.sched-forecast-summary {
  grid-column: 1 / -1;
  grid-template-columns: 184px repeat(7, minmax(0, 1fr));
}

.sched-forecast-total,
.sched-forecast-day,
.sb-summary-cell {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0;
  min-width: 0;
  min-height: 52px;
  padding: 4px 8px;
  background: var(--card);
  font-size: 13px;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
}

/* Compact numeric reading beside a label. First needed by the schedule
   publish drawer. */
.sb-summary-value {
  font-variant-numeric: tabular-nums;
}

.shift-pill {
  display: block;
  width: 100%;
  text-align: left;
  min-height: 44px;
  padding: 6px 8px;
  border-radius: var(--radius);
  background: var(--face);
  color: var(--ink);
  cursor: pointer;
  overflow-wrap: anywhere;
  font-weight: 400;
}

.shift-pill[style*="--role-fill"] {
  background-color: var(--role-fill, var(--face));
  color: var(--ink);
}

.shift-pill.is-on {
  outline: 3px solid var(--act);
  outline-offset: 1px;
}

.shift-pill small {
  display: block;
  font-weight: 400;
}

.shift-pill.is-draft {
  border: 2px dashed var(--role-edge, currentColor);
  box-shadow: none;
  opacity: 1;
  /* Matt 2026-09-08: a draft keeps its role tint, thinned toward paper, never
     a gray wash. The old canvas-gray overlay (EAE9E8 at 78%) hid the role
     colour and made every draft read the same. Half the published fill on
     the paper ground keeps drafts clearly lighter than published without
     the gray undertone. */
  background-color: color-mix(
    in srgb,
    var(--role-fill, var(--face)) 50%,
    var(--paper)
  );
  color: var(--ink);
  background-image: none;
}

.shift-pill.is-draft::after {
  content: "draft";
  display: block;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  opacity: inherit;
}

/* Month cards carry the state as an actual line so draft and published cards
   remain distinguishable even when pseudo-content is unavailable to a browser
   or assistive technology. */
.month-day .shift-pill.is-draft::after {
  content: none;
}

.shift-pill.is-live {
  border: 1px solid transparent;
  box-shadow: inset 5px 0 0 var(--role-edge, currentColor);
  opacity: 1;
  background-color: var(--role-fill, var(--face));
  color: var(--ink);
  background-image: none;
}

.shift-pill.is-template {
  border: 1px solid var(--line);
  box-shadow: inset 5px 0 0 var(--role-edge, currentColor);
  background: var(--card);
  background-image: none;
  color: var(--ink);
}

.template-shift-item > .shift-pill {
  padding-right: 38px;
}

.template-shift-item > .template-lock-cycle,
.template-shift-item > .template-lock-static {
  position: absolute;
  z-index: 2;
  top: 5px;
  right: 5px;
}

.template-lock-cycle,
.template-lock-static {
  display: grid;
  width: 28px;
  height: 28px;
  padding: 0;
  place-items: center;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: transparent;
  color: var(--muted);
}

.template-lock-cycle:hover,
.template-lock-cycle:focus-visible {
  border-color: var(--line);
  background: var(--card);
  color: var(--act);
}

.template-lock-cycle:disabled {
  opacity: .45;
  cursor: wait;
}

.template-dialog-lock-control {
  min-height: 32px;
}

.template-dialog-lock-control .template-lock-cycle,
.template-dialog-lock-control .template-lock-static {
  position: static;
  display: inline-flex;
  width: auto;
  min-width: 28px;
  gap: 8px;
  padding: 0 8px;
  background: transparent;
}

.template-lock-name {
  color: var(--ink);
  font: inherit;
}

.template-lock-icon {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.template-lock-icon .template-lock-body-solid {
  fill: currentColor;
  stroke: currentColor;
}

.template-lock-icon .template-lock-keyhole {
  fill: var(--field);
  stroke: none;
}

.template-lock-icon .template-unlock-shackle {
  stroke-width: 2.3;
}

.template-role-name {
  position: relative;
  grid-template-columns: minmax(0, 1fr);
  box-shadow: inset 5px 0 0 var(--rail, var(--line));
}

.template-role-cell {
  min-height: 64px;
}

.crew-rotation-board {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--divider-width);
  border-top: 1px solid var(--divider);
  border-bottom: 1px solid var(--divider);
  background: var(--divider);
}

#crew-rotation-dialog {
  width: min(920px, calc(100vw - 28px));
}

.crew-rotation-column {
  display: flex;
  min-width: 0;
  flex-direction: column;
  background: var(--card);
}

.crew-rotation-column-head {
  display: grid;
  gap: 3px;
  min-height: 64px;
  padding: 12px;
  border-bottom: 1px solid var(--divider);
}

.crew-rotation-list {
  display: grid;
  align-content: start;
  flex: 1;
}

.crew-member-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  padding: 12px;
  border-bottom: 1px solid var(--divider);
}

.crew-member-kind {
  display: flex;
  grid-column: 1 / -1;
  justify-content: space-between;
  gap: 8px;
}

.crew-member-person,
.crew-member-pattern,
.access-editor-grid .actions,
.access-editor-grid > .field:last-of-type,
.pin-card:has(.mix) {
  grid-column: 1 / -1;
}

.crew-member-row > .crew-sub {
  grid-column: 1;
}

.crew-member-row > .chip {
  grid-column: 2;
  justify-self: end;
}

.crew-sub {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  margin: 0;
}

.crew-enabled {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  margin: 0;
}

.crew-column-add {
  align-self: flex-start;
  min-height: 44px;
  margin: 4px 12px;
}

.crew-rotation-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px;
  align-items: center;
}

.template-mode-strip {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: var(--divider-width);
  margin-bottom: 12px;
  background: var(--divider);
}

.template-mode-strip > div {
  min-width: 0;
  padding: 8px;
  background: var(--card);
}

.template-mode-strip > div > span {
  display: block;
  font-weight: 700;
}

/* Shift Templates leaves its eight-column desktop calendar at the shared
   tablet breakpoint. Keep the companion scheduling-mode strip on the same
   breakpoint so its rotation disclosures never remain in seven narrow
   columns after the templates themselves have reflowed into cards. */
@media (max-width: 1200px) {
  .template-mode-strip {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.sb-config-choice .sb-list-trigger {
  width: 100%;
  padding-left: 0;
}

.sb-clickable-list {
  display: grid;
  gap: 2px;
  margin: 16px 0 0;
  padding: 8px 0 0;
  border-top: 1px solid var(--line);
  /* Matt 2026-09-09: rows, never list bullets beside the attention dot. */
  list-style: none;
}

/* sb-note: the shared note primitive. Ruling: unattributed */
/* Matt 2026-09-09: a note list is rows in two fixed slots, a 14px dot track
   and the words, so every row's text starts on the same line and a dot sits
   centred on its row whether or not the row carries one. */
.sb-note-list > li {
  display: grid;
  grid-template-columns: 14px minmax(0, 1fr);
  gap: 8px;
  align-items: center;
  padding: 2px 0;
}

.sb-note-list > li > .warn {
  display: contents;
}

.sb-note-list > li > .warn > i {
  justify-self: center;
  margin-top: 0;
}

.sb-note-list > li:not(:has(> .warn))::before {
  content: "";
  width: 14px;
}

/* board: the shared board primitive. Ruling: unattributed */
.board {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}

/* lane: the shared lane primitive. Ruling: unattributed */
.lane {
  background: var(--face);
  border-radius: var(--radius);
  padding: 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-height: 200px;
}

.mail-list {
  display: grid;
  background: var(--card);
  border-radius: var(--radius);
  overflow: hidden;
}

.mail-row {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr) auto;
  gap: 12px;
  align-items: center;
  min-height: 64px;
  padding: 12px 16px;
  width: 100%;
  text-align: left;
  background: var(--card);
  color: inherit;
}

.mail-row.is-on,
.icon-btn:hover,
.sched-name.is-band,
.sched-cell.is-band {
  background: var(--face);
}

.gate {
  min-height: 100dvh;
  display: grid;
  place-items: center;
  background: var(--face);
  color: var(--ink);
  padding: 24px;
}

.gate-card .logo {
  height: 64px;
  margin: 0 0 20px;
}

.gate-card .btn {
  width: 100%;
}

/* sr-only: screen-reader-only accessible text. Ruling: unattributed */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

@media (max-width: 900px) {
  body:has(#sagebrush-shell[data-state~="nav-open"]) {
    overflow: hidden;
  }

  .shell {
    grid-template-columns: 1fr;
    grid-template-rows: 56px minmax(0, 1fr);
    background: var(--face);
  }

  .topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    padding: 6px 8px;
    background: var(--paper);
    border-bottom: 1px solid var(--divider);
    position: sticky;
    top: 0;
    z-index: 15;
  }

  .topbar .logo {
    height: 44px;
    margin: 0;
    max-width: 180px;
  }

  .topbar .menu-btn {
    min-height: 44px;
    min-width: 44px;
    padding: 0 12px;
    background: var(--card);
    border-radius: var(--radius);
    color: var(--ink);
  }

  .nav-backdrop.is-open {
    display: block;
    position: fixed;
    inset: 0;
    background: rgb(35 31 32 / 0.4);
    z-index: 25;
  }

  .nav {
    display: none;
    position: fixed;
    z-index: 30;
    top: 0;
    left: 0;
    bottom: 0;
    width: min(280px, 92vw);
    min-height: 0;
    overflow: auto;
  }

  .nav.is-open {
    display: flex;
  }

  .stats,
  .sb-date-picker.sb-overlay,
  .jobs,
  .shift,
  .pivot,
  .board {
    grid-template-columns: 1fr;
  }

  .sb-date-months {
    grid-template-columns: 1fr;
  }

  .stats,
.tabs,
.sb-tabs,
.dash-labor,
.dash-sales,
.dash-split {
    grid-template-columns: 1fr 1fr;
  }

  .days:not(.sb-date-strip),
.bars,
.cal-grid {
    grid-template-columns: repeat(7, minmax(0, 1fr));
  }

  
  
  .stat + .stat {
    border-left: 1px solid var(--divider);
    padding-left: 16px;
    border-top: 0;
  }

  .stat:nth-child(odd) {
    border-left: 0;
    padding-left: 0;
  }

  .stat:nth-child(n + 3) {
    border-top: 1px solid var(--divider);
    padding-top: 12px;
  }

  .main,
  .sb-page {
    padding: 12px;
  }

  .page-head,
  .sb-page-head {
    flex-direction: column;
    align-items: stretch;
  }

  .pay-layout {
    grid-template-columns: 1fr;
  }

  
  .page-head .row .btn,
  .page-head .row .choice {
    flex: 1;
    min-width: 0;
  }

  .person,
  .employee-head,
  .mail-row {
    grid-template-columns: 40px minmax(0, 1fr);
  }

  .person .chip,
  .person .warn,
  .employee-head .chip,
  .mail-row .chip {
    grid-column: 2;
    justify-self: start;
  }
}

@media (max-width: 560px) {
  .crew-member-row {
    grid-template-columns: 1fr;
    align-items: stretch;
  }

  .crew-member-kind,
  .crew-member-person,
  .crew-member-pattern,
  .crew-member-row > .crew-sub,
  .crew-member-row > .chip {
    grid-column: 1;
    grid-row: auto;
  }

  /* SW2 (2026-09-04). The third column is a status phrase -- "waiting for
     your answer", "waiting on Kaytlynn Kellch" -- and `auto` gave it half a
     phone screen, leaving the row's actual reading ("Sarah Brandon's shift:
     Sep 5, 2026, 10:00a to 5:30p") in a two-word column beside it. On a phone
     the row stacks instead: the status rail keeps its column and everything
     else reads down the second one, the same treatment `.mail-row .chip`
     already gets right above. Shared primitive, so every decision queue built
     on it reads at 390 -- employee requests, manager time off, tips checks. */
  .sb-decision-row {
    grid-template-columns: 40px minmax(0, 1fr);
    align-items: start;
  }

  .sb-decision-row > * {
    grid-column: 2;
  }

  .sb-decision-row > :first-child {
    grid-column: 1;
    grid-row: 1 / -1;
  }

}

@media (max-width: 1120px) {
  .sb-parallel-workflow--editor-detail {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 700px) {
  /* Below tablet width the dashboard pairs stack; a five-column table in a
     half-width phone column is unreadable (standard section 3: reflow, wrap,
     stack instead of an inner scrollbar). */
  
  
  .sb-parallel-workflow,
  .sb-parallel-workflow--queue-detail,
  .sb-parallel-workflow--review {
    grid-template-columns: 1fr;
  }

  /* V1 (defect 9). A two-across grid of five cells left a ragged last row
     and stacked three-line labels on a phone. The strip becomes one row of
     numbered dots; only the step the manager is standing on names itself,
     which is the one label that answers "where am I". */
  .sb-review-wizard {
    display: flex;
    grid-template-columns: none;
  }

  /* Measured at 390px: the row has 366px to give. Four dot-only cells at
     their own minimum leave 182px for the current cell, and 128px of that is
     the label -- enough for "punch requests" to wrap onto two whole words
     rather than being cut at "punch reques". The badge and the dot shrink to
     buy that width; nothing is hidden. */
  .sb-review-wizard > a {
    flex: 0 0 auto;
    height: var(--sb-step-cell-height-compact);
    min-height: var(--sb-step-cell-height-compact);
    grid-template-columns: 20px 10px;
    gap: 4px;
    padding: 10px 6px;
  }

  .sb-review-wizard > a > span:first-child {
    width: 20px;
    height: 20px;
  }

  .sb-review-wizard > a > [data-payroll-state] {
    width: 10px;
    height: 10px;
  }

  .sb-review-wizard > a[aria-current="step"] {
    flex: 1 1 auto;
    grid-template-columns: 20px minmax(0, 1fr) 10px;
    gap: 6px;
    padding: 10px 6px;
  }

  .sb-review-wizard > a > span:nth-child(2),
.sb-review-wizard > a[aria-current="step"] small {
    display: none;
  }

  .sb-review-wizard > a[aria-current="step"] > span:nth-child(2) {
    display: block;
  }

  /* V1 second pass: a title never clips, here either. Five cells and a full
     label cannot share 390px on one line -- "punch requests" alone needs 135px
     against the 58px the row can spare -- so the current step's label wraps to
     two, and the compact cell is sized for two so every page's strip is still
     the same height whether its own label takes one line or two. */
  .sb-review-wizard > a[aria-current="step"] strong {
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: visible;
  }

  }

@media (min-width: 1501px) {
  .sb-parallel-workflow--review {
    grid-template-columns: minmax(0, 1.7fr) minmax(320px, .8fr);
  }
}

@media (max-width: 520px) {
  .template-mode-strip {
    grid-template-columns: 1fr;
  }

  .sched-compact-day {
    grid-template-columns: 1fr;
    gap: 2px;
  }

  .sched-compact-day-label {
    padding: 4px 0 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  * {
    transition: none !important;
    animation: none !important;
  }
}

a.who {
  text-decoration: none;
  color: inherit;
  min-height: 44px;
  border-radius: var(--radius);
  width: 100%;
}

.who strong {
  font-weight: 400;
  font-size: var(--type);
}

.who .muted {
  font-size: var(--type);
}

.status {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  color: var(--ink);
  font-weight: 400;
}

.status::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ink);
  flex: none;
}

.pay-table {
  width: 100%;
  max-width: 100%;
  table-layout: auto;
  font-variant-numeric: tabular-nums;
}

.pay-table th,
.pay-table td {
  padding: 10px 6px;
}

.pay-table th,
.pay-table .nest-label {
  white-space: normal;
}

/* The 36px first column is the expand toggle's, and only the toggle's.
   Matt, 2026-09-09 ("LAME"): the Gusto verify tables and the tips week
   table put the PERSON in the first column, and under fixed layout this
   rule squeezed every name to one letter per line. A first cell earns the
   toggle width by holding the toggle; a header cell earns it by being the
   empty cell above one. Nothing else in a pay table is 36px. */
.pay-table tbody td:first-child:has(> .nest-toggle),
.pay-table thead th:first-child:empty {
  width: 36px;
  padding-right: 0;
}

.pay-table td:nth-child(2),
.pay-table th:nth-child(2) {
  max-width: 320px;
}

/* V1 (defect 12, found in the audit). `nowrap` on the role label made a
   ten-column tips table 75px wider than the page, and `.table-wrap` hid the
   overflow rather than reflowing it -- clipped data, which section 3.5 bans
   outright. The label wraps instead. */

/* V1 (defect 4). The person cell used to be one nowrap flex line -- avatar,
   name and the "multiple jobs" note all on one row -- inside a 240px cell,
   so it overflowed into the pay column beside it. It is now a fixed avatar
   track and a two-line copy block: the name on its own line, the note under
   it, both clamped, nothing leaving the cell. */
.pay-table .person-cell {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  white-space: normal;
}

.sb-person-copy {
  display: grid;
  min-width: 0;
}

.pay-table .diff-up,
.nest-toggle:hover {
  color: var(--ink);
}

.pay-table .diff-down,
.hero-stat span.delta.is-down,
.perf-metric span.delta.is-down,
.stat b.delta.is-down,
.stat span.delta.is-down,
td.delta.is-down,
.delta.is-down {
  color: var(--stop);
}

/* Responsive grouped tables reflow each record by meaning.  Grouped tbody
   boundaries remain the only team-member separators, so role rows stay
   together without an inner scrollbar or clipped 11-column grid.

   B13: this family's widest measured members are the timesheets daily table
   (11 columns, natural min-content width ~937px) and the summary table (10
   columns, ~828px). At the old 760px activation both still clipped between
   761-899px, and the daily table clipped as far up as ~900-992px (only
   31px of headroom remained even at a 1024px viewport). Raised to 1024px so
   every tablet width in the accepted matrix reflows instead of clipping;
   1365px (desktop) still renders the full table because it comfortably
   exceeds both natural widths. */
@media (max-width: 1024px) {
  .sb-data-table--responsive.pay-table {
    display: block;
    width: 100%;
    table-layout: auto;
  }

  .sb-data-table--responsive.pay-table thead,
.sb-data-table--responsive.pay-table tbody tr:not(.nest-row)
    td[data-blank],
.sb-data-table--responsive.pay-table tbody tr:not(.nest-row)
    td:first-child:has(> .nest-toggle) {
    display: none;
  }

  .sb-data-table--responsive.pay-table tbody {
    display: grid;
    width: 100%;
  }

  .sb-data-table--responsive.pay-table > tbody + tbody {
    border-top: var(--divider-width) solid var(--divider);
  }

  .sb-data-table--responsive.pay-table tbody tr:not(.nest-row) {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 4px;
    width: 100%;
    padding: 10px 0;
  }

  .sb-data-table--responsive.pay-table tbody tr.sb-pay-parent {
    position: relative;
    padding-right: 48px;
  }

  .sb-data-table--responsive.pay-table tbody tr:not(.nest-row) td {
    display: grid;
    grid-template-columns: minmax(96px, 32%) minmax(0, 1fr);
    gap: 8px;
    width: 100%;
    min-width: 0;
    padding: 3px 0;
    border: 0;
/* V1 second pass (coordinator items 4 and 5, 2026-09-03). `overflow-wrap:
   anywhere` breaks a word at ANY character, which is what rendered the
   pay-period table as one letter per line. `break-word` breaks only a word
   that genuinely cannot fit, and unlike `anywhere` it does not shrink the
   cell's min-content width, so ordinary words stay whole. */
    white-space: normal;
    overflow-wrap: break-word;
    word-break: normal;
  }

  .sb-data-table--responsive.pay-table tbody tr:not(.nest-row)
    td[data-label]::before {
    content: attr(data-label);
    /* V1 second pass (coordinator item 5, 2026-09-03). The label was being
       GENERATED and then hidden: the base rule above sets
       `.sb-data-table--responsive td[data-label]::before { display: none }`,
       and this rule set only `content`, so a reflowed pay table printed a bare
       column of values with nothing saying what they were. A later lane had
       restored `display: block` for flat pay tables only (`.sb-flat-records`),
       which left the NESTED ones -- the summary and the pay-period review --
       unlabelled. Every reflowed pay table shows its labels. */
    display: block;
    color: var(--muted);
    font-size: var(--type);
  }

  /* B12x (2026-09-03). The stacked form printed every column per person,
     including the ones that carry nothing for most people -- "net pay -",
     "sick -", "unpaid -", "bonus -" and so on -- turning one employee into a
     15-line card. The page names a blank cell (empty, a dash, or a
     zero-hours value) with `data-blank` on the parent row, EXCEPT the fixed
     always-visible set (name, job, pay, net pay, regular hrs, paycheck
     tips) it never marks; this one rule drops every other marked cell from
     the stacked list. Scoped to the same media block and `tr:not(.nest-row)`
     selector as the reflow itself, so both `#summary-content` and
     `#period-content` -- the two `pay-table` instances this rule already
     governs -- get it for free, with no page-specific CSS. */
  
  /* Same scoping as the 36px rule above: only a toggle cell disappears
     in the labeled-record reflow. A person cell in the first column stays. */
  
  .sb-data-table--responsive.pay-table tbody tr.sb-pay-parent
    td:first-child:has(.nest-toggle) {
    display: block;
    position: absolute;
    top: 10px;
    right: 0;
    width: 44px;
    padding: 0;
    border: 0;
  }

  .sb-data-table--responsive.pay-table .person-cell,
  .sb-data-table--responsive.pay-table .nest-label {
    white-space: normal;
    overflow-wrap: break-word;
    word-break: normal;
  }

  .sb-data-table--responsive.pay-table tbody tr.nest-row {
    display: block;
    width: 100%;
    padding: 8px 0;
  }

  .sb-data-table--responsive.pay-table tbody tr.nest-row td {
    display: block;
    width: 100%;
    padding: 8px 0;
    border: 0;
    white-space: normal;
    overflow-wrap: break-word;
    word-break: normal;
  }
}

/* nest-row: the shared nest row primitive. Ruling: unattributed */
/* Nest: caret = expands. Indent = child. Rail = identity on every row. */
.table tbody tr:not(.nest-row) td:first-child {
  box-shadow: inset var(--nest-rail) 0 0 var(--rail, transparent);
}

/* nest-child: the shared nest child primitive. Ruling: unattributed */
.table tbody tr.nest-child td:first-child {
  box-shadow: inset var(--nest-rail-child) 0 0 var(--rail, transparent);
}

.pay-child .nest-label,
.nest-child .nest-label,
.table.rpt-table .nest-child .nest-label {
  padding-left: var(--nest-indent);
}

/* icon-btn: a small icon-only button (prev/next, utility actions). Ruling: unattributed */
.icon-btn {
  min-height: 44px;
  min-width: 44px;
  padding: 0;
  background: var(--hair);
  border: 1px solid var(--line);
  color: var(--ink);
  display: inline-grid;
  place-items: center;
  border-radius: var(--radius);
  cursor: pointer;
}

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

/* account-form: the shared account/settings form layout. Ruling: unattributed */
.account-form {
  display: grid;
  gap: 16px;
  max-width: 520px;
  min-width: 0;
}

/* sb-form: the shared form primitive. Ruling: unattributed */
/* Full-lane writing forms use all of the shared editor pane rather than the
   compact account-form measure. Pair with `.sb-editor-form` when the same
   editor also lives in a column, not only in an overlay. */
.sb-form--wide {
  max-width: none;
  min-width: 0;
}

/* Hint copy under a form heading or a check-group label. Same face as the
   legacy `.muted` utility, given its own sb-* name so account-form/
   sb-editor-form markup (PEOPLE-EDIT, 2026-09-08) never has to reach for a
   pre-Creosote class to read a field or a group. */

.account-form label {
  display: grid;
  gap: 6px;
  font-size: var(--type);
  font-weight: 400;
  min-width: 0;
}

/* Nested fieldset children of this form family shrink with the track.
   Visual grouped-field chrome lives on `.sb-fieldset`; this is not a
   global fieldset reset. */

/* Checkbox and radio copy always begins beside its control. The label may
   wrap inside the text column at narrow widths, but the control never takes
   an entire grid row and strands its description below it. */
.account-form label:has(> input[type="checkbox"]),
.account-form label:has(> input[type="radio"]) {
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  column-gap: 8px;
}

.account-form label > input[type="checkbox"],
.account-form label > input[type="radio"] {
  width: auto;
  height: auto;
  min-height: 0;
  padding: 0;
  flex: 0 0 auto;
}

/* Native checkbox uses the action token, not browser blue. `.sb-check` is
   the same treatment outside this form family. Dimensions stay native. */
.account-form input[type="checkbox"],
.sb-check {
  accent-color: var(--act);
}

/* sb-input: the shared input primitive. Ruling: unattributed */
/* Prefix or suffix field. First needed by the schedule rate form's money
   input (`data-format="money"`). */
.sb-input {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.account-form input,
.account-form select,
.account-form textarea {
  min-height: 44px;
  min-width: 0;
  padding: 0 12px;
  border-radius: var(--radius);
  background: var(--field);
  border: 1px solid var(--line);
}

.account-form input:not([type="checkbox"]):not([type="radio"]),
.account-form select,
.account-form textarea {
  max-width: 100%;
}

.account-form select {
  width: 100%;
}

.account-form input:not([type="checkbox"]):not([type="radio"]),
.account-form textarea {
  background: var(--field);
}

.account-form textarea {
  min-height: 88px;
  padding: 8px 12px;
  resize: vertical;
}

/* sb-fieldset: a grouped fieldset of related inputs. Ruling: unattributed */
/* Canonical grouped-field primitive. Originating Staff account-form
   fieldsets consume this class; there is no global fieldset/legend reset. */
.sb-fieldset {
  border: 0;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 8px;
  min-width: 0;
}

.sb-fieldset > legend {
  padding: 0;
  margin: 0 0 6px;
  color: var(--ink);
}

/* sb-inline: the shared inline primitive. Ruling: unattributed */
/* Inline machine identifiers rendered as `code`. Inherit the surrounding
   face; do not switch to monospace or a block treatment. Originating PIM
   exact IDs consume this; there is no global `code` reset. */
.sb-inline-code {
  font-family: inherit;
  font-size: inherit;
  font-weight: 400;
  color: inherit;
  text-transform: none;
  overflow-wrap: anywhere;
}

.sched-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  margin: 0 0 16px;
  font-size: var(--type);
}

.sched-legend i {
  display: inline-block;
  width: 14px;
  height: 14px;
  border-radius: 2px;
  margin-right: 6px;
  vertical-align: -2px;
}

.stat span {
  display: block;
  font-size: var(--type);
  color: var(--muted);
  font-weight: 400;
  text-transform: lowercase;
}

.stat b {
  display: block;
  margin-top: 4px;
  font-size: var(--type-sub);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  color: var(--head);
}

.box h2 {
  font-size: var(--type-sub);
  font-weight: 400;
  letter-spacing: -0.02em;
  text-transform: none;
  color: var(--head);
}

.mail-row.is-unread,
.mail-row.is-unread strong,
.mail-row.is-unread .muted {
  font-weight: 700;
}

/* Type: 16 / 26 (16 × φ) / 32. Weight 400 except unread mail.
   Pills selected: clay. Buttons and nav selected: terracotta. */
html,
body,
#app,
button,
input,
select,
textarea,
a,
th,
td,
strong,
b,
h1,
h2,
h3,
summary,
label {
  font-family: "Figtree", sans-serif;
  font-weight: 400;
}

.person-block.is-open .person,
.person.is-on {
  background: transparent;
  color: var(--act);
  box-shadow: inset var(--nest-rail) 0 0 var(--act);
}

.person-detail {
  padding: 12px 8px 16px 16px;
}

@media (max-width: 900px) {
  .people-layout {
    grid-template-columns: 1fr;
  }
}

.person-detail h2,
.fan-body p {
  margin: 0 0 8px;
}

/* nest-toggle: the shared nest toggle primitive. Ruling: unattributed */
.nest-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 44px;
  min-width: 24px;
  padding: 0;
  background: transparent;
  color: var(--muted);
}

.nest-row td {
  background: var(--face);
  padding: 16px 14px 20px;
  box-shadow: inset 6px 0 0 var(--act);
}

/* nest-form: the shared nest form primitive. Ruling: unattributed */
.nest-form {
  max-width: 520px;
}

.nest-form input,
.nest-form select {
  background: var(--card);
}

/* stack-toggle: the shared stack toggle primitive. Ruling: unattributed */
.stack-toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  width: 100%;
  padding: 0;
  background: transparent;
  color: var(--head);
  font-size: var(--type-sub);
  line-height: 1.15;
  letter-spacing: -0.02em;
  text-align: left;
}

.sched-cell.is-drop {
  outline: 2px solid var(--act);
  outline-offset: -2px;
  background: var(--clay);
}

.shift-pill[draggable="true"] {
  cursor: grab;
}

.shift-pill[draggable="true"]:active {
  cursor: grabbing;
}

.block-chip {
  min-height: 44px;
  padding: 6px 8px;
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--muted);
}

/* Calendar-owned blocks share one compact treatment across Schedule and Time
   off.  The grid carries only the label and time; notes, source identifiers,
   and audit detail stay behind the block's detail/editor surface. */
.sb-calendar-block,
.block-chip {
  display: flex;
  min-width: 0;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  gap: 0;
  border: 0;
  border-left: 4px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--ink);
  text-align: left;
}

.sb-calendar-block.is-pto,
.sb-calendar-block.is-unavail,
.sb-calendar-block.is-open,
.block-chip.is-pto,
.block-chip.is-unavail,
.day-notice.sb-calendar-block {
  min-height: 44px;
  padding: 6px 8px;
}

button.sb-calendar-block,
a.sb-calendar-block,
button.block-chip,
a.day-notice {
  width: 100%;
  font: inherit;
  text-decoration: none;
  cursor: pointer;
}

button.sb-calendar-block:hover,
button.sb-calendar-block:focus-visible,
a.sb-calendar-block:hover,
a.sb-calendar-block:focus-visible,
button.block-chip:hover,
button.block-chip:focus-visible,
a.day-notice:hover,
a.day-notice:focus-visible {
  border-left-color: var(--act);
  color: var(--ink);
}

.sb-calendar-block .chip-title,
.sb-calendar-block .chip-note,
.block-chip .chip-title,
.block-chip .chip-note {
  color: inherit;
  opacity: 1;
}

.block-chip.is-pto,
.block-chip.is-unavail {
  background: var(--paper);
}

/* The Time Off calendar uses the same block primitive as Schedule. State
   treatment belongs to the primitive so pages do not repaint PTO or
   unavailability independently. */
.sb-calendar-block.is-pto {
  background: var(--paper);
  border-left-color: var(--act);
}

.sb-calendar-block.is-pto.is-pending {
  background: var(--card);
  border-left: 4px dashed var(--attn);
}

.sb-calendar-block.is-unavail {
  background: var(--face);
  border-left-color: var(--muted);
}

/* TIMEOFF-PAGE, Matt 2026-09-10. A day with nothing on it is a state, not a
   blank: same block, same two lines, quietest possible treatment, and never
   a control (nothing to open). */
.sb-calendar-block.is-open {
  background: transparent;
  border-left-color: var(--divider);
  color: var(--muted);
}

.sb-calendar-block-time {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

/* PTO and unavailable blocks are neutral records, not action buttons. Keep
   their identity rail unchanged on hover and keyboard focus. */
button.block-chip.is-pto:hover,
button.block-chip.is-pto:focus-visible,
button.block-chip.is-unavail:hover,
button.block-chip.is-unavail:focus-visible {
  border-left-color: var(--line);
  background: var(--paper);
  color: var(--ink);
}

.chip-note {
  display: block;
  opacity: 0.8;
}

/* day-notice: the shared day notice primitive. Ruling: unattributed */
.day-notice {
  min-height: 44px;
  padding: 6px 8px;
  background: var(--card);
  border-radius: var(--radius);
}

.day-notice small {
  display: block;
  color: var(--act);
}

.events-add {
  color: var(--act);
  text-decoration: none;
}

.cell-add {
  flex: 0 0 30px;
  min-height: 30px;
  width: 48px;
  padding: 0;
  background: var(--hair);
  border: 1px solid var(--line);
  color: var(--muted);
  border-radius: var(--radius);
  overflow: hidden;
  white-space: nowrap;
  font-size: var(--type);
  opacity: 0;
  pointer-events: none;
  transition: opacity 120ms ease;
}

.cell-add-rail {
  position: absolute;
  z-index: 2;
  top: 0;
  right: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  width: 8px;
  /* The add affordance is revealed on interaction; its resting hit area is
     transparent so the grid has no always-visible vertical add bar. */
  background: transparent;
  transition: width 120ms ease;
}

.sched-cell:hover > .cell-add-rail,
.sched-cell:focus-within > .cell-add-rail {
  width: 56px;
  background: transparent;
}

.sched-cell:hover > .cell-add-rail .cell-add,
.sched-cell:focus-within > .cell-add-rail .cell-add {
  opacity: 1;
  pointer-events: auto;
}

/* An empty schedule slot is one large reveal target. Occupied schedule cells
   keep the same add control on the right, while template cells never render
   this rail once their single slot is filled. */
.sched-cell.is-empty > .cell-add-rail,
.sched-cell.is-empty:hover > .cell-add-rail,
.sched-cell.is-empty:focus-within > .cell-add-rail {
  inset: 0;
  width: auto;
}

.cell-menu {
  position: fixed;
  z-index: 40;
  min-width: 180px;
  padding: 4px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: 0 12px 32px rgb(35 31 32 / 0.18);
}

.cell-menu button {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 44px;
  padding: 0 12px;
  background: transparent;
  border: 0;
  text-align: left;
}

.cell-menu button:hover,
.cell-menu button:focus-visible {
  background: transparent;
  color: var(--act);
}

.cell-menu a[role="menuitem"] {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  min-height: 44px;
  margin-top: 4px;
  padding: 0 12px;
  background: transparent;
  border: 0;
  color: var(--ink);
  text-align: left;
  text-decoration: none;
}

.cell-menu a[role="menuitem"]:hover,
.cell-menu a[role="menuitem"]:focus-visible {
  background: transparent;
  color: var(--act);
}

.shift-dialog {
  width: min(560px, calc(100vw - 28px));
  max-height: calc(100vh - 28px);
  overflow: hidden;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0;
  background: var(--card);
  color: var(--ink);
  box-shadow: 0 24px 70px rgb(35 31 32 / 0.28);
}

.shift-dialog-body {
  max-width: none;
  padding: 20px;
  max-height: calc(100vh - 28px);
  overflow: auto;
}

.sb-dialog-fields {
  display: grid;
  gap: 12px;
}

/* sb-field: the shared field primitive. Ruling: unattributed */
/* Shared dialog hierarchy: each field name is a compact subheading and the
   value or control below it remains body text. Pages use these primitives
   instead of inventing modal-specific type rules. */
.sb-field-stack {
  display: grid;
  gap: 6px;
}

.sb-field-heading,
.sb-dialog-subheading {
  margin: 0;
  color: var(--head);
  font-size: var(--type);
  line-height: 1.25;
}

/* sb-shift: the shared shift primitive. Ruling: unattributed */
.sb-shift-note-copy {
  margin: 0;
  white-space: pre-wrap;
}

.template-shift-detail-pair {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  align-items: start;
}

.shift-dialog-body input:not([type="checkbox"]):not([type="radio"]),
.shift-dialog-body select {
  height: 44px;
  max-height: 44px;
  appearance: auto;
}

/* dialog-head: the shared dialog head primitive. Ruling: unattributed */
.dialog-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}

/* dialog-close: the shared dialog close primitive. Ruling: unattributed */
.dialog-close {
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  background: var(--face);
  border-radius: var(--radius);
  color: var(--ink);
  font-size: 24px;
  line-height: 1;
}

.frequent-shifts {
  display: grid;
  gap: 8px;
}

.frequent-shifts p {
  margin: 0;
  color: var(--muted);
}

.frequent-suggestion {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  width: 100%;
  min-height: 56px;
  padding: 8px 12px;
  background: var(--face);
  color: var(--ink);
  text-align: left;
  border-radius: var(--radius);
  box-shadow: inset 5px 0 0 var(--act);
}

.frequent-suggestion small,
.hero-stat span,
.signal span {
  display: block;
  color: var(--muted);
}

.time-pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

/* A time pair that carries its own trailing control keeps the two times and
   that control on one line instead of dropping it into a half-width cell.
   First needed by the schedule unavailability editor's removable windows. */
.time-pair:has(> button) {
  grid-template-columns: 1fr 1fr auto;
}

.role-field {
  display: grid;
  grid-template-columns: 20px 1fr;
  gap: 8px;
  align-items: center;
}

.role-swatch {
  width: 20px;
  height: 20px;
  border-radius: 2px;
}

.weekday-fieldset {
  border: 0;
  padding: 0;
  margin: 0;
  display: grid;
  gap: 8px;
}

.weekday-fieldset legend,
.pay-strip .stat {
  padding: 0;
}

.weekday-pills {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 6px;
}

.weekday-pills .choice {
  min-width: 0;
  padding: 0;
}

/* toggle-row: a labelled row with a trailing toggle control. Ruling: unattributed */
.toggle-row {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
}

.toggle-row input {
  width: auto;
  min-height: 0;
}

/* sb-option: the shared option primitive. Ruling: unattributed */
/* Canonical checkbox/radio option grid. Related choices scan in aligned
   columns at wide widths and naturally become one column without an inner
   scroll region. */
.sb-option-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
  column-gap: 24px;
  border-top: 1px solid var(--divider);
}

.sb-option-grid .toggle-row {
  min-width: 0;
  border-bottom: 1px solid var(--divider);
}

.sb-option-grid .toggle-row > span {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* form-error: inline field validation error text. Ruling: unattributed */
.form-error {
  margin: 0;
  color: var(--stop);
}

.dialog-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px;
  background: var(--card);
  padding: 12px 0 4px;
  border-top: 1px solid var(--divider);
}

/* A short viewport turns the dialog into the viewport itself. There is one
   scroll surface, not a scrollable card nested inside the page. */
@media (max-height: 720px) {
  .shift-dialog {
    width: 100vw;
    height: 100dvh;
    max-height: none;
    border: 0;
    border-radius: 0;
    overflow-y: auto;
  }

  .shift-dialog-body {
    min-height: 100%;
    max-height: none;
    overflow: visible;
  }
}

@media (max-width: 430px) {
  .template-shift-detail-pair {
    grid-template-columns: 1fr;
  }
}

.dialog-actions .btn:first-child {
  margin-right: auto;
}

.pay-strip {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 16px;
  margin: 8px 0 12px;
}

/* V1 second pass (coordinator item 6, 2026-09-03). These readings are short
   SENTENCES now ("0 of 7 days approved", "checks passed"), not figures, and
   `nowrap` made each one run straight through the stat beside it at 1024 and
   390. They wrap; the four-up band drops to two-up while the sidebar leaves
   the content column under 800px. */
.pay-strip .stat b {
  white-space: normal;
  overflow-wrap: break-word;
  word-break: normal;
}

.pay-strip .stat small {
  display: block;
  margin-top: 2px;
  font-size: 13px;
}

.audit-head,
.audit-item {
  display: grid;
  grid-template-columns: minmax(110px, 1.1fr) minmax(72px, 0.7fr) minmax(72px, 0.7fr) minmax(96px, 0.9fr) minmax(88px, 0.8fr) auto;
  gap: 12px;
  align-items: center;
}

.audit-head {
  color: var(--muted);
  padding: 0 0 8px;
}

.audit-head .num,
.audit-item .num {
  text-align: left;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.audit-item {
  min-height: 72px;
  padding: 8px 0;
}

.audit-block.is-open .audit-item {
  background: var(--clay);
  margin: 0 -16px;
  padding: 8px 16px;
}

.audit-actions {
  justify-content: flex-end;
  flex-wrap: nowrap;
}

.audit-body {
  padding: 12px 0 16px;
  box-shadow: inset 4px 0 0 var(--act);
  padding-left: 16px;
}

@media (max-width: 1200px) {
  .pay-strip {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 900px) {

  
  .audit-item {
    grid-template-columns: 1fr 1fr;
    gap: 8px 16px;
  }

  .audit-actions {
    grid-column: 1 / -1;
    justify-content: flex-start;
    flex-wrap: wrap;
  }
}

.job-tick {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}

.job-tick i {
  width: 8px;
  height: 8px;
  background: var(--act);
  flex: 0 0 8px;
}

.fan-list {
  margin-top: 8px;
  border-top: 1px solid var(--divider);
}

.fan-toggle {
  display: grid;
  grid-template-columns: 16px minmax(0, 1fr) auto;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: 44px;
  padding: 8px 0;
  background: transparent;
  color: var(--ink);
  text-align: left;
}

.fan-toggle-copy,
.sb-employee-details {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.fan-summary {
  color: var(--muted);
  font-size: 13px;
  font-weight: 400;
  line-height: 1.2;
}

.fan-block.is-open .fan-toggle {
  color: var(--head);
}

.fan-body {
  padding: 0 0 16px 24px;
}

.person-fan-toolbar {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  flex-wrap: wrap;
  min-height: 44px;
  margin-bottom: 8px;
}

.person-profile-workspace {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(220px, .6fr);
  gap: 24px;
  align-items: start;
}

.person-core-profile {
  margin-top: 8px;
  padding-top: 4px;
  border-top: 1px solid var(--divider);
}

.access-editor-grid {
  display: grid;
  grid-template-columns: minmax(180px, .8fr) minmax(260px, 1.2fr);
  gap: 16px 24px;
  align-items: start;
}

.access-editor-grid .check-group label {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: 8px;
}

.role-checks {
  display: grid;
  gap: 4px;
  margin-top: 8px;
}

.role-check-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  min-height: 36px;
  padding: 4px 0;
  border-bottom: 1px solid var(--divider);
}

.role-eligibility,
.primary-role-choice {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  white-space: nowrap;
}

.person-profile-group,
.person-profile-records .secondary-details {
  min-width: 0;
  padding-top: 12px;
  border-top: 1px solid var(--divider);
}

.person-profile-group .sb-reading-grid {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin-bottom: 0;
}

/* LAYOUT-4 (Matt, 2026-09-23). Same defect shape as LAYOUT-3 above: this
   two-column reading-grid rule and the outer two-column workspace rule
   below are equal-specificity class selectors to the SAME two selectors'
   mention inside the phone `@media (max-width: 700px) { ... 1fr }` collapse
   higher in this file (the block that also carries `.person-profile-row`,
   `.dash-labor`, etc.). Equal specificity means only whichever occurrence is
   LATER in the file ever wins, at every width -- these two unconditional
   rules are later, so the phone collapse never reached the People record at
   all. "contact and employment" kept its 2-column reading grid INSIDE an
   outer workspace column that was itself still fighting a 220px-floor
   second track for the photo panel, so "work email" and "phone" each had
   roughly a letter's width to draw in ("w/or/k/e/m/ail", "ph/on/e").
   Repeating both selectors here, after their unconditional rules, restores
   the single-column phone stack the same way LAYOUT-3 does for
   `.sb-record-row`. 1440 is untouched. */
@media (max-width: 700px) {
  .person-profile-workspace,
  .person-profile-group .sb-reading-grid {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 700px) {
  
  .access-editor-grid .actions,
  .access-editor-grid > .field:last-of-type {
    grid-column: auto;
  }

}

.history-list {
  display: grid;
  gap: 12px;
  margin-top: 16px;
}

.hero-stat b {
  display: block;
  margin: 4px 0;
  color: var(--head);
  font-size: var(--type-head);
  line-height: 1.1;
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}

.mix {
  display: grid;
  gap: 10px;
  margin: 12px 0 16px;
}

.mix.is-compact {
  margin: 8px 0 0;
}

.mix.is-compact .mix-row {
  min-height: 24px;
}

.slice-controls .eyebrow {
  margin-bottom: 8px;
}

/* signal: a small colored status signal dot. Ruling: unattributed */
.signal b {
  display: block;
  margin: 4px 0;
  color: var(--head);
  font-size: var(--type-sub);
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.signal.is-alert {
  box-shadow: inset 4px 0 0 var(--stop);
}

/* empty-artifact: an emptied @media block left over from earlier de-paging work (zero declarations, zero visual effect). Ruling: unattributed */
@media (max-width: 900px) {
  

  

  
}

.rpt-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--rpt-gap);
  margin-bottom: 16px;
  overflow: visible;
}

/* rpt-chip: the shared rpt chip primitive. Ruling: unattributed */
.rpt-chip {
  appearance: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: var(--rpt-chip-w);
  min-width: var(--rpt-chip-w);
  max-width: var(--rpt-chip-w);
  height: var(--rpt-chip-h);
  min-height: var(--rpt-chip-h);
  padding: 0 12px;
  background: var(--hair);
  color: var(--ink);
  border: 0;
  border-radius: var(--radius);
  box-shadow: none;
  flex: 0 0 var(--rpt-chip-w);
}

.rpt-chip-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Pay-period selectors retain their legacy geometry while inheriting the
   canonical clickable-text treatment. */
.rpt-chip.sb-list-trigger {
  justify-content: flex-start;
  background: transparent;
  border: 0;
  text-align: left;
}

.rpt-panel.sb-action-list {
  gap: 4px;
  padding: 4px;
  border-color: var(--line);
  background: var(--card);
}

.rpt-panel.sb-action-list .rpt-option {
  justify-content: flex-start;
  min-height: 44px;
  background: transparent;
  text-align: left;
}

/* Filled terracotta controls keep their contrast in every interaction state.
   Borderless list triggers are intentionally excluded: they stay ink at rest
   and use terracotta as their text-only open/hover cue. */
.nav a.navlink:hover,
.nav a.navlink:focus-visible,
.nav a.navlink:active,
.nav button.navlink:hover,
.nav button.navlink:focus-visible,
.nav button.navlink:active,
.nav a.navlink.is-on:hover,
.nav a.navlink.is-on:focus-visible,
.nav a.navlink.is-on:active,
.nav button.navlink.is-on:hover,
.nav button.navlink.is-on:focus-visible,
.nav button.navlink.is-on:active,
.btn-primary:hover,
.btn-primary:focus-visible,
.btn-primary:active,
.tabs button.is-on:hover,
.tabs button.is-on:focus-visible,
.tabs button.is-on:active,
.sb-tabs button.is-on:hover,
.sb-tabs button.is-on:focus-visible,
.sb-tabs button.is-on:active,
.day.is-on:hover,
.day.is-on:focus-visible,
.day.is-on:active,
.sb-date-dates button.on:hover,
.sb-date-dates button.on:focus-visible,
.sb-date-dates button.on:active,
a.who.is-on:hover,
a.who.is-on:focus-visible,
a.who.is-on:active,
.chip-wait:hover,
.chip-wait:focus-visible,
.chip-wait:active,
.frequent-suggestion.is-on:hover,
.frequent-suggestion.is-on:focus-visible,
.frequent-suggestion.is-on:active,
.rpt-chip.is-on:not(.sb-list-trigger):hover,
.rpt-chip.is-on:not(.sb-list-trigger):focus-visible,
.rpt-chip.is-on:not(.sb-list-trigger):active,
.rpt-option.is-on:hover,
.rpt-option.is-on:focus-visible,
.rpt-option.is-on:active {
  background: var(--act);
  color: var(--on-action);
}

.rpt-well {
  width: var(--rpt-chart-w);
  max-width: 100%;
  margin-bottom: 16px;
}

.rpt-chart {
  width: 100%;
  height: var(--rpt-chart-h);
}

.rpt-chart .sales-chart,
.rpt-chart .grain-bars {
  display: block;
  width: 100%;
  height: var(--rpt-chart-h);
}

.chart-grid {
  stroke: var(--divider);
  stroke-width: 1;
}

.chart-label {
  fill: var(--muted);
  font-size: 12px;
  font-family: inherit;
}

.table.rpt-table {
  width: 100%;
  max-width: 100%;
  table-layout: auto;
}

.table.rpt-table th,
.table.rpt-table td {
  background: var(--card);
  vertical-align: middle;
  box-shadow: none;
  border-left: 0;
  border-right: 0;
  padding: 10px 8px;
  white-space: nowrap;
}

.table.rpt-table th:first-child,
.table.rpt-table td:first-child {
  width: 36px;
  padding-left: 8px;
  padding-right: 0;
}

.table.rpt-table .nest-label {
  white-space: normal;
  overflow-wrap: break-word;
  max-width: 260px;
}

.compare-pair .is-today,
.swatch.is-today {
  background: var(--act);
}

/* swatch: a small color swatch. Ruling: unattributed */
.swatch {
  display: inline-block;
  width: 12px;
  height: 12px;
  border-radius: 2px;
  margin-right: 6px;
}

.hero-stat span.delta.is-up,
.perf-metric span.delta.is-up,
.stat b.delta.is-up,
.stat span.delta.is-up,
td.delta.is-up,
.delta.is-up {
  color: var(--ok);
}

@media (max-width: 900px) {
  

  
}

/* Per-person analytics detail. Body-size label/value pairs, NOT .stat tiles:
   .stat's value is the 26px mid size and repeating it once per employee put ~55
   elements on that size, flattening heading, name and number into one scale. */

/* Time Off uses the same compact operating surface as Schedule.  The page
   has several adjacent sections, so keep their chrome quiet and let the
   one-pixel rails provide the hierarchy instead of adding more cards or
   oversized gaps. */
.sb-employee-field {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr);
  gap: 8px;
  align-items: center;
  min-width: 0;
  text-align: left;
}

.sb-employee-field--with-meta {
  grid-template-columns: 40px minmax(0, 1fr) auto;
  gap: 12px;
}

.sb-employee-link,
.employee-link {
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--ink);
  text-decoration: none;
}

.sb-employee-link:is(button),
.employee-link:is(button) {
  display: block;
  margin: 0;
  padding: 0;
  line-height: 1.25;
  text-align: left;
}

.sb-employee-role,
.employee-role {
  overflow-wrap: anywhere;
}

/* TIME OFF PAGE, creo.css lane phase 4 (2026-09-14): the 75 page-scoped
   rules that used to read `body[data-sb-page="manager-time-off"] ...` are
   replaced by named modifier classes and, where an element already carries
   a page-unique id, by that id directly. See the phase 4 report for the
   full redundant/modifier/held mapping. Markup: manager_time_off_page.py. */

/* .page-head--controls-inline: the header row sits center-aligned with a
   tighter bottom margin instead of the base page-head's bottom-aligned,
   40px-margin shape, because this page's header carries no eyebrow line.
   .sb-page-actions is a DOM child of this header, reached by descent. */
.page-head.page-head--controls-inline {
  align-items: center;
  margin-bottom: 16px;
}

.page-head.page-head--controls-inline p {
  margin-top: 0;
}

.page-head--controls-inline .sb-page-actions {
  justify-content: flex-end;
}

/* .schedule-control-area--compact: the week-navigation bar (today / prior /
   range label / next) runs tighter than the base schedule pattern's 12px
   gap and adds its own bottom margin; schedule-primary-controls and
   schedule-date-trigger are DOM descendants, reached by descent. */
.schedule-control-area.schedule-control-area--compact {
  gap: 4px;
  margin-bottom: 16px;
}

.schedule-control-area--compact .schedule-primary-controls {
  gap: 4px;
}

.schedule-control-area--compact .schedule-date-trigger {
  min-width: 0;
  padding: 8px 10px;
  color: var(--ink);
}

.schedule-control-area--compact .schedule-date-trigger:hover {
  color: var(--act);
}

/* #calendar-card / #pending-card / #all-approvals-card: page-unique ids
   already scope these rules on their own; the body[data-sb-page] prefix
   contributed nothing beyond what the id already guarantees, so it is
   dropped with no markup change. #balances-card matched nothing (the
   balances section moved to the People record, PEOPLE-PTO 2026-09-10) and
   is dropped from the selector list as dead weight. */
#calendar-card,
#pending-card,
#all-approvals-card {
  margin-top: 16px;
  padding: 12px;
  border-top: var(--divider-width) solid var(--divider);
}

#calendar-card > .sb-filter-row {
  margin: -4px 0 8px;
}

#pending-card h2,
#all-approvals-card h2 {
  margin-bottom: 8px;
}

/* .queue--decision-compact: the three queue wrappers on this page (pending
   requests, approvals, and the availability-saved list) run a tighter row
   height and padding than the base queue pattern, and use fixed identity
   columns instead of the shared scan-row grid. Applied to all three .queue
   elements so every .queue-row on the page keeps the same treatment the
   old page-scoped rule gave it. */
.queue.queue--decision-compact .queue-row {
  min-height: 56px;
  padding: 8px 12px;
}

/* TIMEOFF-PAGE, Matt 2026-09-10: the identity slot is a FIXED width now that
   the queue runs the full page instead of half of it. `.8fr` of a 1,500px row
   made a 650px name column and left the request fact floating in the middle
   of the row. */
.queue.queue--decision-compact
  .queue-row:has(> .sb-employee-field):not(.sb-decision-row--approval) {
  grid-template-columns: minmax(170px, 260px) minmax(0, 1fr) auto;
}

.queue.queue--decision-compact .queue-row + .queue-row {
  border-top-color: var(--divider);
}

/* .sb-calendar-table--week-grid: the week-at-a-glance absence calendar.
   Time Off keeps only its two-pixel label gap; block appearance belongs to
   the shared calendar primitive above. */
.sb-calendar-table.sb-calendar-table--week-grid .sb-calendar-block {
  gap: 2px;
}

/* TIMEOFF-PAGE, Matt 2026-09-10: "the formatting of the calendar is pretty
   wonky." One height for every day row and one height for the head row, so
   the week reads as a grid instead of a ragged stack; a table cell's
   `height` is a MINIMUM, so a rare long window wraps to a taller row rather
   than being cut off. Both of a cell's lines wrap for the same reason: the
   defect being fixed here is the word "unavailable" clipped at the column
   edge, and nothing on this calendar may clip. */
.sb-calendar-table.sb-calendar-table--week-grid thead th {
  height: 56px;
}

.sb-calendar-table.sb-calendar-table--week-grid tbody th[scope="row"],
.sb-calendar-table.sb-calendar-table--week-grid tbody td {
  height: 84px;
}

.sb-calendar-table.sb-calendar-table--week-grid td > .sb-calendar-block {
  height: 100%;
}

.sb-calendar-table.sb-calendar-table--week-grid .sb-calendar-block > .warn {
  min-width: 0;
  white-space: normal;
}

.sb-calendar-table.sb-calendar-table--week-grid .sb-calendar-block-time {
  min-width: 0;
  white-space: normal;
  overflow-wrap: anywhere;
}

/* .request-facts is not shared with any other page (grep-verified), so the
   body[data-sb-page] prefix on it and its :hover/:focus-visible variants
   contributed nothing beyond what the class name already provides; dropped
   with no markup change. */
.request-facts {
  min-height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.request-facts:hover,
.request-facts:focus-visible {
  color: var(--act);
}

/* #week-tables and #compare-wrap are page-unique ids; the table-wraps this
   page opens (visible overflow, unlike the shared clipping default) are
   always inside one of the two, so the prefix drops with no markup
   change. max-width: 100% matched the base .table-wrap rule already and is
   dropped as redundant. */
#week-tables .table-wrap,
#compare-wrap {
  overflow: visible;
}

/* .table--ruled: a bordered-cell table treatment (separate per-cell rules,
   divider-colored borders, a face-toned header) instead of the borderless
   `.table` card. Applied to every `.table` element this page renders: the
   week-grid calendar, the balance-comparison data table, and the paid-
   hours-per-day strip in the decision dialog. */
.table.table--ruled {
  border-collapse: separate;
  border-spacing: 0;
  table-layout: fixed;
}

/* TIMEOFF-PAGE, Matt 2026-09-10: 220px left the two-word roles ("general
   manager", "kitchen lead") wrapping onto a third line and made that one row
   taller than the rest. */
.sb-calendar-table.sb-calendar-table--week-grid th:first-child,
.sb-calendar-table.sb-calendar-table--week-grid td:first-child {
  width: 220px;
}

.sb-calendar-table.sb-calendar-table--week-grid thead th > span,
.sb-calendar-table.sb-calendar-table--week-grid thead th > small {
  display: block;
}

.sb-calendar-table.sb-calendar-table--week-grid thead th > small {
  margin-top: 2px;
}

.table.table--ruled th,
.table.table--ruled td {
  border-right: var(--divider-width) solid var(--divider);
  border-bottom: var(--divider-width) solid var(--divider);
  padding: 8px;
  vertical-align: top;
}

.table.table--ruled th:first-child,
.table.table--ruled td:first-child {
  border-left: var(--divider-width) solid var(--divider);
}

.table.table--ruled thead tr:first-child th {
  border-top: var(--divider-width) solid var(--divider);
}

.table.table--ruled th {
  background: var(--card);
  color: var(--ink);
}

.account-form.account-form--tight {
  gap: 12px;
}

@media (max-width: 900px) {
  .page-head.page-head--controls-inline {
    align-items: flex-start;
  }

  .page-head--controls-inline .sb-page-actions {
    justify-content: flex-start;
  }
}

@media (max-width: 520px) {
  .main.main--snug {
    padding: 16px 14px 32px;
  }

  .sb-calendar-table.sb-calendar-table--week-grid th,
  .sb-calendar-table.sb-calendar-table--week-grid td {
    padding: 6px 4px;
    overflow-wrap: anywhere;
  }
}

/* Narrow Time Off views reflow by meaning, not by squeezing the weekly
   matrix.  The page lane supplies data-day-label/data-label attributes to
   make each stacked item self-describing when its column header is hidden. */
@media screen and (max-width: 760px) {
  /* TIMEOFF-PAGE, Matt 2026-09-10: the identity column above is a wide-width
     rule. At phone width a 170px identity slot plus the request fact beside
     it does not fit 390, and the fact ran outside the card. The row stacks
     instead: identity on its own line, the fact and its status under it. */
  .queue.queue--decision-compact
    .queue-row:has(> .sb-employee-field):not(.sb-decision-row--approval) {
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
    height: auto;
    row-gap: 4px;
  }

  .queue.queue--decision-compact
    .queue-row:has(> .sb-employee-field):not(.sb-decision-row--approval) > * {
    grid-column: 1;
    grid-row: auto;
  }

  .page-head.page-head--controls-inline {
    gap: 8px;
  }

  .page-head--controls-inline .sb-page-actions {
    flex: 0 1 auto;
    gap: 4px;
  }

  .page-head--controls-inline .sb-page-actions .btn {
    flex: 0 1 auto;
    min-width: 0;
    width: auto;
    padding-inline: 10px;
    white-space: nowrap;
  }

  .schedule-control-area--compact .schedule-date-row,
  .schedule-control-area--compact .schedule-primary-controls {
    flex-direction: row;
    align-items: center;
    flex-wrap: nowrap;
    width: 100%;
  }

  .schedule-control-area--compact .schedule-primary-controls
    .schedule-date-trigger {
    min-width: 0;
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* #week-tables is a page-unique id; the day-label reflow below applies
     only to the calendar tables it holds, so the prefix drops with no
     markup change. */
  #week-tables .sb-calendar-table:has(td[data-day-label]) {
    display: block;
    width: 100%;
  }

  #week-tables .sb-calendar-table:has(td[data-day-label]) thead {
    display: none;
  }

  #week-tables .sb-calendar-table:has(td[data-day-label]) tbody,
  #week-tables .sb-calendar-table:has(td[data-day-label]) tr {
    display: grid;
    gap: 8px;
  }

  #week-tables .sb-calendar-table:has(td[data-day-label]) tr {
    padding: 10px;
    border: var(--divider-width) solid var(--divider);
  }

  #week-tables .sb-calendar-table:has(td[data-day-label]) th[scope="row"] {
    display: block;
    width: 100%;
    min-width: 0;
    padding: 0 0 8px;
    border: 0;
  }

  #week-tables .sb-calendar-table:has(td[data-day-label]) td {
    display: grid;
    gap: 6px;
    width: 100%;
    min-width: 0;
    padding: 10px;
    border: var(--divider-width) solid var(--divider);
  }

  #week-tables .sb-calendar-table:has(td[data-day-label]) td:has(.sb-calendar-block),
  #week-tables .sb-calendar-table:has(td[data-day-label]) td:has(> .muted) {
    grid-template-columns: 104px minmax(0, 1fr);
    align-items: center;
  }

  #week-tables .sb-calendar-table:has(td[data-day-label]) td::before {
    content: attr(data-day-label);
    color: var(--ink);
    font-size: var(--type-sub);
    line-height: 1.15;
  }

  /* .sb-data-table--tight-reflow: the balance-comparison table reflows at
     760px with a tighter gap/padding than the shared responsive-table
     breakpoint (900px), so it keeps its own class rather than reusing the
     shared reflow's spacing. */
  .table.sb-data-table--responsive.sb-data-table--tight-reflow {
    display: block;
    width: 100%;
  }

  .table.sb-data-table--responsive.sb-data-table--tight-reflow thead {
    display: none;
  }

  .table.sb-data-table--responsive.sb-data-table--tight-reflow tbody,
  .table.sb-data-table--responsive.sb-data-table--tight-reflow tr {
    display: grid;
    gap: 4px;
  }

  .table.sb-data-table--responsive.sb-data-table--tight-reflow tr {
    padding: 8px 0;
    border-bottom: var(--divider-width) solid var(--divider);
  }

  .table.sb-data-table--responsive.sb-data-table--tight-reflow td {
    display: grid;
    grid-template-columns: minmax(110px, 38%) minmax(0, 1fr);
    gap: 8px;
    padding: 2px 0;
    border: 0;
  }

  .table.sb-data-table--responsive.sb-data-table--tight-reflow td::before {
    content: attr(data-label);
    color: var(--ink);
  }

  /* #pending-card / #all-approvals-card / #compare-card are page-unique
     ids; every .sb-section-head this page renders is inside one of the
     three, so the prefix drops with no markup change. */
  #pending-card .sb-section-head,
  #all-approvals-card .sb-section-head,
  #compare-card .sb-section-head {
    align-items: flex-start;
    flex-wrap: wrap;
  }
}

/* ============================================================================
   Shared additions from the Palm lane - Matt-approved individually,
   2026-08-29 (DECISIONS-LEDGER). Five components with no prior Creosote
   analog, plus two data-scoped accent blocks that are inert unless an app
   sets the attributes.
   ==========================================================================*/

/* Trailing count after an option name in a clickable-text list
   ("needs me 104"). */


/* Sanitized foreign-HTML document viewer (email bodies read the way the
   sender built them, isolated from the page). .message-body-frame is the
   shadow-DOM mount host; .message-body is the typography contract inside. */




/* Attachment list on a compose/reply/forward form. */


/* Proportional per-day load bar inside a month-grid day cell. */




/* GBC mail-area accent flip (Matt-ruled, 2026-08-27 Palm shell rulings):
   scoped by attribute, inert for apps that never set data-accent. */
body[data-accent="gbc"] #area-mail,
body[data-accent="gbc"] dialog {
  --act: #266e96;
  --rust: #266e96;
}

/* sb-table: the shared table primitive. Ruling: unattributed */
/* Shared wide-table scroller (note 33, 2026-09-03). `.table-wrap` hides its
   overflow, which is right for a table that reflows but wrong for one that is
   genuinely wider than any desktop viewport: the tips workbook is seventeen
   columns and about 2,570px, so its right-hand columns (tips, processing
   fees, both discount columns) simply vanished at every width. This scroller
   is the one place a horizontal scrollbar is allowed, and it is always inside
   the surface rather than on the page: the page body never scrolls sideways.
   `.sb-sticky-first` pins the identity column so a row stays readable while
   the money columns scroll under it. Both are inert once the shared
   responsive reflow has turned the rows into labeled records, because a
   reflowed table is never wider than its container. */
.sb-table-scroll {
  overflow-x: auto;
  max-width: 100%;
}

.sb-table-scroll > table {
  min-width: 100%;
}

/* sb-sticky: the shared sticky primitive. Ruling: unattributed */
.sb-sticky-first thead th:first-child,
.sb-sticky-first tbody td:first-child {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--card);
}

.sb-sticky-first thead th:first-child {
  background: var(--face);
}

@media (max-width: 1024px) {
  .sb-data-table--responsive.pay-table.sb-sticky-first thead th:first-child,
  .sb-data-table--responsive.pay-table.sb-sticky-first tbody td:first-child {
    position: static;
  }
}

/* The reason field on a balance-check acknowledgement (note 31). Same shape
   as every other labelled field on the step; the input is the shared
   `.search` control, so nothing here invents a field treatment. */
.ack-reason {
  display: grid;
  gap: 4px;
}

/* A door has to look like a door. `.sb-list-trigger` sets `display:
   inline-flex`, which removes a <summary>'s built-in disclosure marker, so
   every disclosure built from the shared trigger rendered as a plain line of
   text with no affordance at all. The shared marker is the same caret pair the
   audit and stack toggles already use, so nothing new is invented and every
   page that uses the primitive gets the affordance back. */
summary.sb-list-trigger {
  list-style: none;
  gap: 6px;
}

summary.sb-list-trigger::-webkit-details-marker {
  display: none;
}

summary.sb-list-trigger::before {
  content: "\25B8";
  display: inline-block;
  width: 1em;
}

details[open] > summary.sb-list-trigger::before {
  content: "\25BE";
}

/* The shared pay-table reflow was written for a GROUPED table whose first cell
   is the expand toggle, so at tablet widths it hides `td:first-child` and
   suppresses the labels. A FLAT one-row-per-person table has its identity in
   that first cell and no toggle at all, so the same reflow dropped the
   employee's name and left a bare column of unlabeled numbers. This modifier
   keeps the shared reflow and restores the two things a flat table needs:
   the identity cell, and the label on every value. */
@media (max-width: 1024px) {
  .sb-data-table--responsive.pay-table.sb-flat-records
    tbody tr:not(.nest-row) td:first-child {
    display: grid;
  }

  .sb-data-table--responsive.pay-table.sb-flat-records
    tbody tr:not(.nest-row) td[data-label]::before {
    display: block;
  }

  /* A record is one person: the divider belongs between people, not between
     the seventeen facts inside one of them. */
  .sb-data-table--responsive.pay-table.sb-flat-records tbody tr {
    border-bottom: var(--divider-width) solid var(--divider);
  }
}


/* --------------------------------------------------------------------
   Creosote OS reconciliation additions (2026-09-14, css lane phase 1).
   Rules below exist only in Creosote OS's prior creo.css and are carried
   here so the two apps load one identical file. Team does not render any
   of these classes today, so nothing here changes a Team page. Placed
   BEFORE the run of pinned, contract-tested trailing sections (source-
   readback, catalog, workflow, quantity/document-preview, control-group,
   fitted-preview, and the 2026-09-04 promotions packets) so none of
   those section-boundary tests pick up an unexpected extra class.
   Two OS rules were NOT carried: a bare .sb-summary-band grid-template-
   columns rule and a bare .sb-summary-cell rule, both of which collide
   with an existing Team primitive of the same name (.sched-forecast-
   summary and the .sched-forecast-total/.sched-forecast-day/.sb-summary-
   cell group). A fifth family (.picker/.presets/.weekdays/.dates/
   .date-picker-actions) was dropped entirely: Team's test_date_picker.py
   asserts .picker no longer exists in creo.css (Team already migrated to
   the shared sb-date-picker/sb-date-months primitive). Held items and
   the dropped family are listed in the lane report.
   -------------------------------------------------------------------- */

.sb-panel--active.is-on {
  box-shadow: inset 4px 0 0 var(--act);
}

/* sb-context: the shared context primitive. Ruling: unattributed */
.sb-parallel-workflow:not(.sb-parallel-workflow--queue-detail):not(:has(.sb-context-surface)) {
  grid-template-columns: minmax(0, 1fr);
}

.rpt-store-swatch {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--hair);
  flex: none;
}

/* Chart series and legend swatch take the same store color (unified css
   rule, 2026-09-14). */
.sb-chart-series.rpt-store-hbt,
.sb-chart-series.rpt-store-sbc,
.sb-chart-series.rpt-store-sbu { stroke: var(--store-color); fill: var(--store-color); }
.sb-chart-key .rpt-store-swatch { margin-right: 4px; vertical-align: middle; }
.rpt-store-hbt { --store-color: var(--store-hbt); }

.rpt-store-sbc { --store-color: var(--store-sbc); }

.rpt-store-sbu { --store-color: var(--store-sbu); }

.photo.rpt-store-hbt,
.photo.rpt-store-sbc,
.photo.rpt-store-sbu,
.rpt-store-swatch.rpt-store-hbt,
.rpt-store-swatch.rpt-store-sbc,
.rpt-store-swatch.rpt-store-sbu {
  background: var(--store-color);
}

.pay-table td:first-child,
.pay-table th:first-child {
  width: 36px;
  padding-right: 0;
}

/* The approval-decision variant of .queue--decision-compact's row (a row
   carrying .sb-decision-row--approval, excluded by the :not() rule above)
   keeps its own wider fraction for the identity column. */
.queue.queue--decision-compact .queue-row:has(> .sb-employee-field) {
  grid-template-columns: minmax(170px, .8fr) minmax(0, 1fr) auto;
}

/* sb-drag: the shared drag primitive. Ruling: unattributed */
.sb-drag-item[data-dragging="true"] {
  cursor: grabbing;
  border-color: var(--act);
}

/* sb-nav: the shared nav primitive. Ruling: unattributed */
@media (max-width: 900px) {
body.sb-nav-open {
    overflow: hidden;
  }

}

@media (max-width: 1024px) {
.sb-data-table--responsive.pay-table tbody tr.pay-parent {
    position: relative;
    padding-right: 48px;
  }

.sb-data-table--responsive.pay-table tbody tr:not(.nest-row)
    td:first-child {
    display: none;
  }

.sb-data-table--responsive.pay-table tbody tr.pay-parent
    td:first-child:has(.nest-toggle) {
    display: block;
    position: absolute;
    top: 10px;
    right: 0;
    width: 44px;
    padding: 0;
    border: 0;
  }

}

/* ===== Creosote OS shared look, 2026-09-14 (Matt's 13-group rulings) =====

   Matt compared Team's look and Creosote OS's look on the 13-group comparison
   page and ruled which side becomes the shared look. The rules below are the
   Creosote OS declarations that won, folded out of the OS-only
   apps/web/src/styles.css and into this one sheet, plus the shared primitives
   those rules need. Rules that redeclared something already in this file were
   merged into that rule in place, higher up, not repeated here.

   The rulings, in Matt's grouping: page tokens, filter rows (wrapping flex),
   decision rows (button rows with the bottom border), five-column record rows
   with business facts, studio tiles, the status line with its orange left
   rail, the 43/57 queue-and-detail split, headings and small chrome all take
   Creosote OS's look. Text inputs are white on the EAE9E8 ground with OS's
   border, 44px height and padding. The review wizard keeps Team's five steps
   with OS's layout for the rest. Data tables take OS's look with numbers
   right-justified. Store colors are the brand accents. Reading grids take
   OS's hairline grid and lowercase labels with no fixed column cap.

   Team renders none of the OS-only class names below today, so nothing here
   changes a Team page by itself; what changes Team is the in-place merges
   named above, which Matt ruled deliberately ("Team adopts all of it when the
   held Team pull requests deploy"). Placed BEFORE the run of pinned,
   contract-tested trailing sections so no section-boundary test picks up an
   unexpected class.
   ========================================================================= */

/* Group 12. The named workspace container. An unnamed `@container
   (max-width: ...)` rule resolves against the NEAREST inline-size container,
   and several sub-surfaces are containers themselves, so a bare `@container`
   rule can fire off a sub-surface's width instead of the page's. A named
   `@container workspace (...)` rule always walks past a nearer unnamed
   container to this one, so "phone-only" collapses mean phone width. Team
   pages that have no `.sb-workspace` keep the 700px media fallback that sits
   higher in this file. */
.sb-workspace {
  container: workspace / inline-size;
  overflow-x: clip;
  padding: clamp(1rem, 4vw, 3rem);
}

/* Group 13. Headings. Creosote OS set `h1 { font-size: 2rem; margin: 0 }` and
   `h2 { font-size: 1.625rem; margin: 0 0 1rem }` unlayered, which outranked
   this file's layered base rules; 2rem and 1.625rem are the same 32px and 26px
   the base layer already reads from --type-head and --type-sub, so the sizes
   are written as those tokens rather than hand-set a second time. What
   actually moves is the margin: a heading owns the space under it, never the
   browser's default space above it. `.page-head h1` and `.sb-section-head h2`
   already zero their own and still win. */
h1 {
  font-size: var(--type-head);
  margin: 0;
}

h2 {
  font-size: var(--type-sub);
  margin: 0 0 1rem;
}

/* Group 13. The sidebar footer: the account block (when signed in) and the
   runtime status line share one footer at the bottom of the sidebar, and the
   footer owns the push-to-bottom margin so the two lines sit flush together
   instead of each fighting for the flex column's free space. */
.nav-footer {
  display: flex;
  flex-direction: column;
  gap: .5rem;
  margin-top: auto;
}

.nav-footer .who-block {
  margin-top: 0;
}

@media (max-width: 680px) {
  .nav-footer {
    margin-top: 0;
  }
}

/* Group 3. The toolbar row's own wrapping rule, shared with the filter row
   merged higher in this file. `.sb-row-verbs` is the verb run inside a
   record or queue row. */
.sb-row-verbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem 1rem;
}

/* Group 3. The operational date strip, seven dates across, each date keeping
   its own status under its label. Team's `.days.sb-date-strip` (two classes,
   higher in this file) still wins for a Team strip; this is the base rule for
   a strip that carries no `.days`. */
.sb-date-strip {
  display: grid;
  gap: .35rem;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  margin-bottom: 1rem;
}

.sb-date-strip .sb-choice {
  align-items: flex-start;
  display: flex;
  flex-direction: column;
  min-width: 0;
  white-space: normal;
}

/* Group 2. One field definition for every field surface. Matt, 2026-09-14:
   text inputs are white (--field, now #ffffff) on the EAE9E8 ground, with
   OS's 1px divider border, 44px minimum height and padding. This replaces the
   six copies the OS sheet carried (the bare field, the editor form's inputs,
   the kiosk search input, the filter row's controls, the review and device
   inputs, and the field's own inputs). Width is 100%; a per-field cap belongs
   to the record editor that wants one (`.sb-review-input`), not to every
   field on every page. */
.sb-field {
  display: grid;
  gap: .35rem;
  margin: .75rem 0;
  min-width: 0;
  width: 100%;
  min-height: 44px;
  padding: .35rem .75rem;
  border: 1px solid var(--divider);
  background: var(--field);
  color: var(--ink);
}

.sb-field input,
.sb-field select,
.sb-editor-form input,
.sb-editor-form textarea,
.sb-editor-form select,
.sb-review-input input,
.sb-device-field input,
.sb-kiosk-search input,
.sb-filter-row input,
.sb-filter-row select {
  width: 100%;
  min-width: 0;
  min-height: 44px;
  padding: .35rem .75rem;
  border: 1px solid var(--divider);
  background: var(--field);
  color: var(--ink);
}

/* A filter's own control is not a full-width field: it sits in a wrapping row
   beside other controls, so it takes its own readable width instead of the
   row's. 12rem is the width Creosote OS's filter controls already carry. */
.sb-filter-row input,
.sb-filter-row select {
  width: auto;
  min-width: 12rem;
}

.sb-editor-form label {
  display: grid;
  gap: .25rem;
  min-width: 0;
}

/* Group 2. A plain page-width comparison grid for two fields or two readings
   side by side, and the labelled single field with its own reading width. */
.sb-review-grid {
  display: grid;
  gap: clamp(1rem, 3vw, 2rem);
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.sb-review-input,
.sb-device-field {
  display: grid;
  gap: .3rem;
  font-size: .8rem;
}

.sb-review-input {
  max-width: 14rem;
}

@container workspace (max-width: 900px) {
  .sb-review-grid {
    grid-template-columns: 1fr;
  }
}

/* Group 4. The review wizard keeps Team's five steps on one row (the column
   count is declared on `.sb-review-wizard` higher in this file, unchanged);
   these are OS's declarations for a wizard whose steps are buttons rather
   than anchors, folded here so the two markups read as one component. */
.sb-review-wizard button {
  align-items: center;
  background: transparent;
  border: 0;
  border-bottom: 2px solid transparent;
  color: var(--ink);
  cursor: pointer;
  display: grid;
  gap: .65rem;
  grid-template-columns: 1.75rem minmax(0, 1fr);
  min-height: 4.5rem;
  padding: .65rem .75rem;
  text-align: left;
}

.sb-review-wizard button + button {
  border-left: 1px solid var(--divider);
}

.sb-review-wizard button > span:first-child {
  align-items: center;
  border: 1px solid var(--divider);
  display: grid;
  height: 1.75rem;
  justify-items: center;
  width: 1.75rem;
}

.sb-review-wizard button:hover,
.sb-review-wizard button:focus-visible,
.sb-review-wizard button[aria-current="step"] {
  color: var(--act);
  border-bottom-color: var(--act);
}

.sb-review-wizard button[aria-current="step"] > span:first-child {
  background: var(--act);
  border-color: var(--act);
  color: var(--on-action);
}

/* Group 5. Numbers read right-justified on one decimal rail, on the header
   as well as the cell, so a column of figures scans as a column. */
.sb-data-table .num,
.sb-data-table th.num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Group 5. A table that must fit its container rather than its content, and
   the grouped table's own tbody rules. */
.sb-table-fit {
  table-layout: fixed;
}

.sb-table-fit td,
.sb-table-fit th {
  overflow-wrap: anywhere;
}

.sb-table-fit input,
.sb-table-fit select {
  max-width: 100%;
  min-width: 0;
  width: 100%;
}

.sb-grouped-table tbody + tbody {
  border-top: 1px solid var(--divider);
}

.sb-grouped-table tbody tr,
.sb-grouped-table td {
  border-bottom: 0;
}

/* Group 6. The decision row's states. The row itself (button reset, bottom
   border, .25rem gaps, .85rem .5rem padding) is merged into `.sb-decision-row`
   higher in this file. */
.sb-decision-row:hover,
.sb-decision-row:focus-visible,
.sb-decision-row[data-selected],
.sb-decision-row[data-selected="true"] {
  color: var(--act);
}

.sb-decision-row small,
.sb-muted {
  color: var(--ink);
  opacity: .7;
}

.sb-decision-queue a.sb-decision-row {
  text-decoration: none;
}

/* Shared text-only decision row: no avatar or status grid tracks. Use for
   field pickers and other choices whose name and explanation read together. */
.sb-decision-row.sb-decision-row--text {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  width: 100%;
}

.sb-decision-row.sb-decision-row--text > * {
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Group 12. Queue and detail: the queue lane is the decision list, the detail
   lane the record being read. The row inside the split keeps its identity
   mark, its content and its trailing verb in fixed slots. */
.sb-queue-detail .sb-decision-row {
  align-items: center;
  grid-template-columns: auto minmax(0, 1fr) auto;
}

.sb-queue-detail .sb-decision-row .sb-clickable-text {
  display: grid;
  gap: .15rem;
  text-align: left;
}

@container workspace (max-width: 640px) {
  .sb-parallel-workflow {
    grid-template-columns: 1fr;
  }

  .sb-queue-detail {
    grid-template-columns: 1fr;
  }

  .sb-queue-detail .sb-decision-row {
    grid-template-columns: auto minmax(0, 1fr);
  }

  .sb-row-verbs {
    grid-column: 1 / -1;
  }
}

/* Group 12. The two lanes' own surfaces. Both carry the same top line; the
   decision surface takes it from the shared border-top family higher in this
   file, and the context surface needs its own declaration to match. */
.sb-decision-surface,
.sb-context-surface {
  min-width: 0;
  padding-top: 1rem;
}

.sb-context-surface {
  border-top: var(--divider-width) solid var(--divider);
}

/* Shared full-row decision surface. Spans every column of a parent workflow
   grid and stacks ordinary content. */
.sb-decision-surface.sb-decision-surface--span {
  display: grid;
  gap: 1.5rem;
  grid-column: 1 / -1;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* Group 7. Record rows respond to their own list width, including inside a
   master/detail column. */
.sb-record-list {
  container-type: inline-size;
  display: grid;
  min-width: 0;
}

/* Group 7. A five-column row is the base (name plus four business facts).
   A row that carries only a name and one action has no third child, so it
   collapses to Team's two tracks and its action stays at the right edge --
   this is the graceful collapse the ruling requires, not a second component. */
.sb-record-row:not(:has(> :nth-child(3))) {
  grid-template-columns: minmax(0, 1fr) auto;
}

/* LAYOUT-3 (Matt, 2026-09-23). The two-child collapse above and the phone
   single-column collapse (`@media (max-width: 700px) .sb-record-row` higher
   in this file) are equal-specificity class selectors, so whichever one is
   LATER in this file wins outright at every width where both could apply --
   the plain `@media` rule losing that tie is not "narrower than intended",
   it never applied to a two-child row at all. A two-child row (the /today
   photo prompt: avatar-and-help-text, then its buttons) kept the desktop
   `1fr auto` split at 390px, where `auto` sizes to the action column's
   button labels and starves the text column down to a one-word-wide
   read. Repeating the two-child selector inside the same 700px query, AFTER
   the rule above, restores the phone stack for this case the same way it
   already works for a three-or-more-column row. */
@media (max-width: 700px) {
  .sb-record-row:not(:has(> :nth-child(3))) {
    grid-template-columns: 1fr;
  }
}

.sb-record-row:first-child {
  border-top: 1px solid var(--divider);
}

.sb-record-row:hover,
.sb-record-row:focus-visible,
.sb-record-row[data-selected="true"] {
  color: var(--act);
}

.sb-record-main,
.sb-record-field {
  display: grid;
  gap: .2rem;
  min-width: 0;
  overflow-wrap: anywhere;
}

.sb-record-main strong {
  font-weight: 400;
}

/* Card fact labels stay lowercase but never below 13px (Matt,
   MATT-DECISIONS.md 2026-09-13); .75rem measured 12px on the live page.
   .82rem is 13.12px at the app's 16px root. */
.sb-record-main small,
.sb-record-field small {
  font-size: .82rem;
  opacity: .65;
}

.sb-record-field {
  font-size: .85rem;
  opacity: 1;
  overflow-wrap: anywhere;
}

@container (max-width: 900px) {
  .sb-record-row {
    grid-template-columns: minmax(11rem, 1.5fr) repeat(2, minmax(6rem, 1fr));
  }
}

/* Named (Matt, 2026-09-14): this is the defect block. An unnamed
   `@container (max-width: 640px)` here resolved against the NEAREST
   inline-size container -- `.sb-record-list` for a directory row, which can
   be well under 640px at a full desktop viewport (measured live: 565px at
   1920px), so this "phone-only" collapse fired on desktop and squeezed a
   Catalog row's text into a 267px column. Keying it to the named `workspace`
   container means it only fires at real phone width, regardless of how narrow
   any nested list or split happens to be. `.sb-record-row--compact` is added
   here for the same reason: without its own single-column rule it would
   inherit the two-equal-column collapse above and lose half its width to an
   empty second track on an actual phone. */
@container workspace (max-width: 640px) {
  .sb-record-row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .sb-record-row--compact {
    grid-template-columns: 1fr;
  }

  .sb-record-main {
    grid-column: 1 / -1;
  }
}

/* Group 10. The reading tile inside the hairline grid. Exact provider IDs and
   receipts must remain selectable, not clipped or ellipsized, so the tile
   wraps rather than truncates. Shared by operational readbacks and analytical
   evidence. */
.sb-reading {
  background: var(--canvas);
  display: grid;
  gap: .35rem;
  margin: 0;
  padding: 1rem;
  min-width: 0;
  overflow-wrap: anywhere;
}

.sb-reading > * {
  min-width: 0;
  overflow-wrap: anywhere;
}

.sb-reading dt,
.sb-reading dd {
  margin: 0;
}

/* Group 10. Lowercase labels at reduced opacity. The size stays 13px: Matt's
   2026-09-13 ruling puts a 13px floor under a card fact label, and the OS
   sheet's .8rem is 12.8px. */
.sb-reading-label,
.sb-reading dt,
.sb-reading > span,
.fact dt,
.fact > span,
.sb-record-label {
  opacity: .65;
  text-transform: lowercase;
}

/* A reading that is itself the control that opens the record. */
button.sb-reading {
  appearance: none;
  border: 0;
  color: inherit;
  cursor: pointer;
  text-align: left;
  width: 100%;
}

button.sb-reading:hover,
button.sb-reading:focus-visible {
  color: var(--act);
}

/* Group 11. Clickable text keeps its own reading rhythm: the label is text,
   so it takes a text line's padding, never a button's box. */
.sb-clickable-text {
  background: transparent;
  border: 0;
  padding: .25rem 0;
}

.sb-clickable-text[aria-current="true"] {
  font-weight: 600;
}

/* Group 13. The floating overlay box Creosote OS opens under a list trigger.
   The confirm and date-picker overlays higher in this file add the shared
   card ground, line, radius and shadow to the same class. */
.sb-overlay {
  background: var(--canvas);
  border: 1px solid var(--divider);
  display: grid;
  gap: .25rem;
  min-width: 16rem;
  padding: .75rem;
  position: absolute;
  z-index: 2;
}

/* Group 13. One device-action button per column of the actions row, and the
   actions row itself. */
.sb-actions {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem;
  margin: 1rem 0;
}

.sb-device-actions {
  margin: .25rem 0 0;
}

.sb-device-actions .btn {
  flex: 1 1 8rem;
}

/* Group 9. The shared chart primitive: graphic geometry scales while labels
   retain the product's readable text sizing and normal document flow. The
   store color itself resolves through --store-color, declared once per store
   higher in this file; nothing here carries a literal color. */
.sb-time-series {
  border-bottom: 1px solid var(--divider);
  container-type: inline-size;
  margin: 0;
  min-width: 0;
  padding: 1rem 0;
}

.sb-time-series figcaption {
  font-size: 1rem;
  overflow-wrap: anywhere;
}

.sb-chart-key {
  display: grid;
  font-size: .875rem;
  gap: .35rem;
  margin: 0 0 .35rem;
}

.sb-chart-key span {
  white-space: nowrap;
}

.sb-chart-key [data-comparison="false"] {
  color: var(--act);
}

.sb-chart-key [data-comparison="true"] {
  text-decoration: underline dashed;
}

.sb-chart-ticks {
  font-size: .875rem;
  margin: 0 1.333333%;
  min-height: 1.25rem;
  position: relative;
}

.sb-chart-ticks span {
  left: var(--tick-position, 0);
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  white-space: nowrap;
  width: max-content;
}

.sb-chart-ticks span[data-tick-align="start"] {
  left: 0;
  transform: none;
}

.sb-chart-ticks span[data-tick-align="end"] {
  left: auto;
  right: 0;
  transform: none;
}

@container (max-width: 480px) {
  .sb-chart-ticks[data-tick-count="5"] span[data-tick-index="1"],
  .sb-chart-ticks[data-tick-count="5"] span[data-tick-index="3"] {
    display: none;
  }
}

@container (min-width: 640px) {
  .sb-chart-key {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
  }
}

.sb-chart-plot {
  display: block;
  width: 100%;
  height: 12rem;
}

.sb-chart-axis {
  stroke: var(--divider);
  stroke-width: 1;
}

.sb-chart-series {
  stroke: var(--act);
  fill: var(--act);
}

.sb-chart-series[data-comparison="true"] {
  stroke: var(--ink);
  fill: var(--ink);
}

.sb-chart-series path {
  fill: none;
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
}

.sb-chart-series[data-comparison="true"] path {
  stroke-dasharray: 6 4;
}

/* Group 9. The store's own rail: the summary band cell, the queue row and the
   filter chip all carry the same store color on the same inset rail. */
.sb-summary-cell.rpt-store-hbt,
.sb-summary-cell.rpt-store-sbc,
.sb-summary-cell.rpt-store-sbu {
  box-shadow: inset 3px 0 0 var(--store-color);
}

.queue-row.rpt-store-hbt,
.queue-row.rpt-store-sbc,
.queue-row.rpt-store-sbu,
.sb-queue-row.rpt-store-hbt,
.sb-queue-row.rpt-store-sbc,
.sb-queue-row.rpt-store-sbu,
.rpt-chip.rpt-store-hbt,
.rpt-chip.rpt-store-sbc,
.rpt-chip.rpt-store-sbu {
  box-shadow: inset var(--nest-rail) 0 0 var(--store-color);
}

/* ===== Creosote OS studio tiles, 2026-09-14 (13-group ruling, group 8) =====

   Matt ruled Creosote OS's look for the studio launch and calendar tiles.
   Team has no studio page, so there is no Team counterpart to match and
   nothing here changes a Team surface; the tiles extend this sheet's own
   is-sbc / is-shop / is-hbt / is-sbu / is-scheduled / is-ready / is-published
   / is-passed status classes onto OS's tile names, which is the shared color
   system used correctly. Moved verbatim out of the OS-only sheet, with three
   token corrections: --action is this file's --act, --orange-small is
   --shop-orange, and its one literal white field ground is --field.
   ========================================================================= */

.sb-studio-lede { max-width: 40rem; margin: 0 0 1.25rem; }
.sb-studio-quiet { color: var(--ink); opacity: .62; margin: .35rem 0 1rem; }
.sb-studio-hold { color: var(--shop-orange); max-width: 36rem; margin: 0 0 1.5rem; }
.sb-studio-proper { text-transform: none; }
.sb-studio-yours-kind { color: var(--shop-orange); font-size: 1rem; font-weight: 600; margin: 1.25rem 0 .4rem; }
.sb-studio-yours-layer:first-child .sb-studio-yours-kind { margin-top: 0; }
.sb-studio-week-day {
  display: grid;
  grid-template-columns: 7.5rem minmax(0, 1fr);
  gap: .35rem 1.25rem;
  padding: .85rem 0;
  border-top: 1px solid var(--divider);
}
.sb-studio-day-name { color: var(--shop-orange); }
.sb-studio-launch, .sb-studio-row-open {
  display: grid;
  grid-template-columns: 12px minmax(0, 1fr) auto;
  gap: .85rem;
  align-items: baseline;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  padding: .2rem 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.sb-studio-row-open { grid-template-columns: minmax(0, 1fr); gap: .1rem; }
.sb-studio-mark { width: 12px; height: 12px; background: var(--ink); align-self: center; }
.sb-studio-launch.is-sbc .sb-studio-mark, .sb-studio-cal-item.is-sbc { border-left-color: var(--act); }
.sb-studio-launch.is-sbc .sb-studio-mark { background: var(--act); }
.sb-studio-launch.is-shop .sb-studio-mark { background: var(--shop-orange); }
.sb-studio-launch.is-hbt .sb-studio-mark { background: var(--berry); }
.sb-studio-launch.is-sbu .sb-studio-mark { background: var(--unroasted-green); }
.sb-studio-launch-title { font-weight: 600; }
.sb-studio-launch:hover .sb-studio-launch-title, .sb-studio-list-row:hover .sb-studio-launch-title { color: var(--act); }
.sb-studio-launch.is-shop:hover .sb-studio-launch-title { color: var(--shop-orange); }
.sb-studio-launch.is-hbt:hover .sb-studio-launch-title { color: var(--berry); }
.sb-studio-launch.is-sbu:hover .sb-studio-launch-title { color: var(--unroasted-green); }
.sb-studio-meta { color: var(--ink); opacity: .62; }
.sb-studio-launch.is-scheduled, .sb-studio-cal-item.is-scheduled, .sb-studio-list-row.is-scheduled { border-left: 3px solid currentColor; }
.sb-studio-launch.is-ready, .sb-studio-cal-item.is-ready, .sb-studio-list-row.is-ready { border-left: 3px dashed currentColor; }
.sb-studio-launch.is-published, .sb-studio-cal-item.is-published, .sb-studio-list-row.is-published { border-left: 6px solid currentColor; }
.sb-studio-launch.is-passed, .sb-studio-cal-item.is-passed, .sb-studio-list-row.is-passed { border-left: 3px dotted currentColor; }
.sb-studio-capture { margin-top: 2.5rem; padding-top: 1.5rem; border-top: 1px solid var(--divider); }
.sb-studio-field {
  width: 100%;
  min-height: 44px;
  border: 1px solid var(--ink);
  background: var(--field);
  font: inherit;
  color: inherit;
  padding: 0 .9rem;
  border-radius: 0;
}
.sb-studio-form {
  display: grid;
  grid-template-columns: 8.5rem minmax(0, 1fr);
  gap: .55rem 1.25rem;
  align-items: center;
}
.sb-studio-form .sb-studio-span, .sb-studio-checks { grid-column: 1 / -1; }
.sb-studio-checks { display: flex; flex-wrap: wrap; gap: .35rem 1.2rem; }
.sb-studio-actions { display: flex; flex-wrap: wrap; gap: .75rem 1rem; margin-top: 1rem; }
.sb-studio-period, .sb-studio-place {
  background: none;
  border: 0;
  padding: .35rem 0;
  font: inherit;
  color: var(--ink);
  cursor: pointer;
  text-transform: lowercase;
}
.sb-studio-place { opacity: .5; }
.sb-studio-place.is-on { opacity: 1; font-weight: 600; }
.sb-studio-period:hover, .sb-studio-place:hover { color: var(--act); }
.sb-studio-go {
  background: var(--act);
  color: var(--field);
  border: 0;
  font: inherit;
  text-transform: lowercase;
  padding: .7rem 1.15rem;
  min-height: 44px;
  cursor: pointer;
}
.sb-studio-go:hover { background: var(--ink); }
.sb-studio-list-row {
  display: grid;
  grid-template-columns: 6.5rem minmax(0, 1fr) auto;
  gap: .2rem 1rem;
  padding: .55rem 0;
  border-top: 1px solid var(--divider);
  align-items: baseline;
}
.sb-studio-row-date { grid-column: 1; opacity: .7; }
.sb-studio-row-open { grid-column: 2; }
.sb-studio-row-status { grid-column: 3; opacity: .7; }
.sb-studio-row-people, .sb-studio-row-dest { grid-column: 1 / -1; font-size: .92em; opacity: .7; }
.sb-studio-places { display: flex; flex-wrap: wrap; gap: .5rem .75rem; margin: 0 0 1.5rem; }
.sb-studio-cal-wrap { min-width: 0; }
.sb-studio-cal-weekdays, .sb-studio-cal { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); }
.sb-studio-cal-weekdays span { color: var(--shop-orange); padding: 0 .45rem .6rem; }
.sb-studio-cal-cell {
  min-width: 0;
  padding: .45rem .5rem .55rem;
  border-top: 1px solid var(--divider);
  border-right: 1px solid color-mix(in srgb, var(--ink) 10%, transparent);
}
.sb-studio-cal-cell:nth-child(7n) { border-right: 0; }
.sb-studio-cal-item {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  border-left: 3px solid var(--ink);
  padding: .15rem 0 .15rem .45rem;
  margin: .15rem 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
}
.sb-studio-cal-item.is-sbc { border-left-color: var(--act); }
.sb-studio-cal-item.is-shop { border-left-color: var(--shop-orange); }
.sb-studio-cal-item.is-hbt { border-left-color: var(--berry); }
.sb-studio-cal-item.is-sbu { border-left-color: var(--unroasted-green); }
.sb-studio-cal-dest { margin: .1rem 0 0; padding: 0; list-style: none; opacity: .7; }
.sb-studio-history { padding-left: 1.2rem; }
.sb-studio-door { min-width: 0; }
.sb-studio-board-group { margin: 0 0 1.5rem; }
.sb-studio-board-group h2 { margin: 0 0 .4rem; }
.sb-studio-cost-total { margin: .35rem 0 0; opacity: .7; }
.sb-studio-settings-list { display: grid; gap: 1rem; max-width: 40rem; }
.sb-studio-settings-list dt { color: var(--shop-orange); }
.sb-studio-settings-list dd { margin: .2rem 0 0; }

@container workspace (max-width: 640px) {
  .sb-studio-week-day { grid-template-columns: 1fr; }
  .sb-studio-form { grid-template-columns: 1fr; }
  .sb-studio-list-row { grid-template-columns: 1fr; }
  .sb-studio-cal-weekdays { display: none; }
  .sb-studio-cal { display: block; }
  .sb-studio-cal-cell { border-right: 0; }
  .sb-studio-cal-cell:not(.has-item):not(.is-today) { display: none; }
  .sb-studio-settings-list { grid-template-columns: 1fr; }
  .sb-studio-board-group { display: block; }
}

/* ===== Creosote accessibility and source-readback primitives, 2026-09-07 =====

   Canonical sb-* replacements for Creosote catalog, marketing, Staff, and
   Shadow skip links plus marketing generated-source and Shadow evidence JSON
   readback. Consumer migration is paired and pending; this sheet does not
   claim those surfaces have moved. Do not add .email-*, .evidence-*,
   .shadow-*, or other app-local selectors here.

   .sb-skip-link is the skip-to-main control. It is not .sr-only: clip/1px
   hiding clips wrapped labels and opacity-only hiding leaves a keyboard
   target invisible. Rest uses a horizontal transform so a long label cannot
   widen the viewport. :focus (including programmatic focus) reveals it;
   :focus-visible alone is not enough. No transition or animation.

   .sb-source-readback is escaped source in normal flow. Figtree 16 / 400,
   ink on paper, pre-wrap, overflow-wrap anywhere. Framing belongs on the
   shared .box / .sb-panel (or .span in a grid). It must not lowercase
   source text or IDs and must not cap height or scroll.

   .sb-detail-section is the titled evidence subsection. .sb-decision-surface
   is a hairline only and .sb-section-head is a title-plus-action row, so
   neither covers a body-scale heading over a divided block. Heading rules
   are scoped to direct heading children of this class.
   */
.sb-skip-link {
  position: fixed;
  z-index: 80;
  top: var(--space);
  left: var(--space);
  transform: translateX(calc(-100% - var(--space)));
  display: inline-block;
  max-width: calc(100vw - (2 * var(--space)));
  min-width: 0;
  min-height: 44px;
  margin: 0;
  padding: var(--space) calc(var(--space) * 2);
  overflow-wrap: anywhere;
  white-space: normal;
  background: var(--act);
  color: var(--on-action);
  border-radius: var(--radius);
  font-family: "Figtree", sans-serif;
  font-size: var(--type);
  font-weight: 400;
  line-height: 1.35;
  text-align: left;
  text-decoration: none;
}

.sb-skip-link:focus {
  transform: none;
  outline: 3px solid var(--act);
  outline-offset: 2px;
  background: var(--act);
  color: var(--on-action);
}

.sb-skip-link:hover,
.sb-skip-link:active {
  background: var(--act);
  color: var(--on-action);
}

.sb-source-readback {
  display: block;
  position: static;
  margin: 0;
  padding: 0;
  min-width: 0;
  max-width: 100%;
  max-height: none;
  overflow: visible;
  overflow-wrap: anywhere;
  white-space: pre-wrap;
  background: var(--paper);
  color: var(--ink);
  border: 0;
  font-family: "Figtree", sans-serif;
  font-size: var(--type);
  font-weight: 400;
  line-height: 1.35;
  letter-spacing: 0;
  text-transform: none;
}

.sb-detail-section {
  margin-top: calc(var(--space) * 2);
  padding-top: calc(var(--space) * 2);
  border-top: var(--divider-width) solid var(--divider);
  min-width: 0;
}

.sb-detail-section > h2,
.sb-detail-section > h3,
.sb-detail-section > h4 {
  margin: 0 0 var(--space);
  font-family: "Figtree", sans-serif;
  font-size: var(--type);
  font-weight: 400;
  line-height: 1.35;
  letter-spacing: 0;
  color: var(--head);
}

/* ===== Creosote catalog primitives, 2026-09-07
   (progress ring, history row, media gallery) =====

   Promoted from the Creosote catalog local compositions (readiness-ring,
   timeline-row, product-gallery) as role-based shared primitives. Do not
   reintroduce those page-local names in this sheet. Originating catalog
   consumers are a paired migration after this commit is reviewed.

   Completeness is a 76px icon/gauge, not a page width. The filled arc is
   the SVG stroke-dasharray presentation attribute on __value (pathLength
   100, "N 100"); this sheet does not compute, interpolate, or animate it.
   Pair any tri-state word beside the ring with the existing .warn
   primitive; the stroke stays terracotta.

   A dated event row is not the schedule .tl-row bar track. Markup keeps
   chronological source order and the actual date/event/source strings;
   this sheet never invents a timestamp and never text-transforms them.

   The media strip is one square hero figure plus wrapped extra images.
   Extra images are not selection controls. Imagery is content, not a
   background, and the roles are valid for non-product photographs.
   Hero overflow:hidden clips the image to its frame; it is not a nested
   scroller. */

.sb-progress-ring {
  position: relative;
  display: grid;
  place-items: center;
  width: 76px;
  height: 76px;
  flex: 0 0 76px;
}

.sb-progress-ring svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
}

.sb-progress-ring__track,
.sb-progress-ring__value {
  fill: none;
  stroke-width: 4;
}

.sb-progress-ring__track {
  stroke: var(--hair);
}

.sb-progress-ring__value {
  stroke: var(--act);
  stroke-linecap: round;
}

.sb-progress-ring span {
  position: relative;
  z-index: 1;
  font-size: var(--type);
  font-weight: 400;
}

.sb-history-row {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 8rem) minmax(0, 1fr);
  gap: var(--space);
  padding: var(--space) 0 var(--space) 20px;
  min-width: 0;
}

.sb-history-row::before {
  position: absolute;
  top: 14px;
  left: 0;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--act);
  content: "";
}

.sb-history-row time,
.sb-history-row strong,
.sb-history-row span {
  display: block;
  min-width: 0;
  color: var(--ink);
  font-size: var(--type);
  font-weight: 400;
  overflow-wrap: anywhere;
}

@media (max-width: 900px) {
  .sb-history-row {
    grid-template-columns: minmax(0, 1fr);
  }
}

.sb-media-gallery {
  display: grid;
  gap: var(--space);
  min-width: 0;
}

.sb-media-gallery__hero {
  margin: 0;
  overflow: hidden;
  border: var(--divider-width) solid var(--divider);
  border-radius: var(--radius);
  background: var(--card);
}

.sb-media-gallery__hero img {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
}

.sb-media-gallery__hero figcaption {
  padding: var(--space) 12px;
  border-top: var(--divider-width) solid var(--divider);
  background: var(--card);
  color: var(--ink);
  font-size: var(--type);
  overflow-wrap: anywhere;
}

.sb-media-gallery__thumbs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space);
}

.sb-media-gallery__thumbs img {
  width: 52px;
  height: 52px;
  object-fit: cover;
  border: var(--divider-width) solid var(--divider);
  border-radius: var(--radius);
  background: var(--card);
}

/* ===== Creosote workflow primitives, 2026-09-07 =====

   Shared roles for the Lab flow row, the PIM transform, and Marketing's
   drag-and-drop launch lane. The originating consumers keep their existing
   data attributes and event handlers. These rules only supply the visual
   contract and responsive reflow. */

/* Lab flow rows fit as many stages as the available width allows. Interactive
   nodes use the same text-only action and focus treatment as other controls;
   static nodes do not receive selected-state paint. */
.flow-map--row {
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
}

.flow-node {
  color: var(--ink);
  text-align: left;
  border: var(--divider-width) solid var(--line);
}

.flow-node > * {
  display: block;
}

button.flow-node {
  width: 100%;
  border: 0;
  cursor: pointer;
}

button.flow-node:hover,
button.flow-node:focus-visible,
button.flow-node[aria-pressed="true"] {
  color: var(--act);
}

/* A transform keeps the two evidence panes readable at wide widths and
   stacks them before the panes become squeezed. The scoped box rule supplies
   the field-stack rhythm that a bare .box does not provide to its children. */
.sb-transform {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
  gap: 12px;
  align-items: center;
  margin-top: 13px;
}

.sb-transform > * {
  min-width: 0;
}

.sb-transform > .box {
  display: grid;
  gap: 6px;
  padding: 13px;
  background: var(--face);
  border-top: 0;
  border-radius: var(--radius);
}

.sb-transform-connector {
  color: var(--act);
  font-size: 1.8rem;
  line-height: 1;
  text-align: center;
}

@media (max-width: 760px) {
  .sb-transform {
    grid-template-columns: minmax(0, 1fr);
  }

  .sb-transform-connector {
    transform: rotate(90deg);
    justify-self: center;
  }
}

/* A drop zone follows the existing data-drop-active lifecycle. A dragend
   clears the item's data-state="dragging" in the consumer; lane cleanup remains
   owned by dragleave and drop, so this CSS does not invent a second lifecycle. */
.sb-drop-zone {
  display: block;
  margin: 0 0 16px;
  padding: 12px;
  background: var(--card);
  border: var(--divider-width) dashed var(--line);
  border-radius: var(--radius);
}

.sb-drop-zone[data-drop-active="true"] {
  border-color: var(--act);
  border-style: solid;
}

.sb-drop-zone[data-unscheduled="true"] {
  background: var(--paper);
}

.sb-drag-item {
  cursor: grab;
}

/* One name for the dragged item: data-state="dragging" (Matt, one source of
   truth). First consumers: the schedule grid and the shift-template pills. */
.sb-drag-item[data-state="dragging"] {
  cursor: grabbing;
  border-color: var(--act);
}

.sb-drag-item[data-state="drop-target"] {
  outline: 2px solid var(--act);
  outline-offset: -2px;
}

/* ===== Creosote quantity list and document preview, 2026-09-07 ===== */

/* Canonical sb-* replacements for two remaining app-local roles:
   Staff employee-instruction ol.task-list, and Marketing app-chrome
   email-canvas around non-sending authored email HTML. Consumer
   migration is paired and pending; this sheet does not claim those
   surfaces have moved. Do not add those app-local selectors here. Do
   not restyle descendant tables or images of the preview. Locked
   2026-09-04 promotion packets stay untouched below.

   .sb-quantity-list is a semantic ordered list with a 96px leading
   quantity column then a wrapping item column. It is not
   .sb-record-row--compact (flexible primary plus trailing action).
   list-style:none hides markers; WebKit then drops list semantics, so
   the consumer ol must keep role="list". Both columns wrap
   (overflow-wrap: anywhere, minmax(0, 1fr) on the item track) with no
   clipping, ellipsis, or nested scroll.

   .sb-document-preview is APP CHROME only: paper, 12px padding, line
   hairline, shared radius. data-preview-width=desktop|mobile caps the
   direct child at 40rem / 24rem. Authored email HTML, brand colors, and
   sent source stay unchanged. A 640px nested table with inline
   max-width:640px and 240px / 576px images can still overflow that
   child at 320/430; ancestor overflow-x:clip can hide document
   scrollWidth. Faithful scaled preview in page flow needs consumer JS
   to measure the inner document and apply transform:scale plus a
   reserved height. This sheet does not claim that clipping is fixed.
   */
.sb-quantity-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 8px;
  min-width: 0;
}

.sb-quantity-list > li {
  display: grid;
  grid-template-columns: 96px minmax(0, 1fr);
  align-items: center;
  gap: 8px;
  padding: 8px 0;
  border-top: 1px solid var(--divider);
  min-width: 0;
}

.sb-quantity-list > li:first-child {
  border-top: 0;
}

.sb-quantity-list > li > strong,
.sb-quantity-list > li > span {
  min-width: 0;
  overflow-wrap: anywhere;
}

.sb-document-preview {
  display: block;
  min-width: 0;
  max-width: 100%;
  padding: 12px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: visible;
}

.sb-document-preview > * {
  display: block;
  margin: 0 auto;
  min-width: 0;
  max-width: 40rem;
}

.sb-document-preview[data-preview-width="desktop"] > * {
  max-width: 40rem;
}

.sb-document-preview[data-preview-width="mobile"] > * {
  max-width: 24rem;
}

/* ===== Creosote control-group containment, 2026-09-07 =====
   Composite controls can wrap within a section heading without changing
   the intrinsic-width behavior of its ordinary disclosure door. */
.sb-section-head > .sb-field-stack,
.sb-section-head > .sb-section-actions {
  min-width: 0;
  max-width: 100%;
}

.sb-field-stack {
  min-width: 0;
}

.sb-field-stack > .field,
.sb-field-stack > .sb-list-select,
.sb-field-stack > textarea {
  width: 100%;
  min-width: 0;
  max-width: 100%;
}

/* manager requests: decision note is label above, textarea below, no boxed .field wrapper */
.sb-field-stack > textarea {
  min-height: 88px;
  padding: 8px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--field);
  font: inherit;
  color: inherit;
  resize: vertical;
  field-sizing: content;
}

/* manager requests: approve and turn down sit in the action slot under the
   note, never touching the textarea. */
.sb-field-stack + .sb-record-action {
  display: flex;
  margin-top: 12px;
}

/* ===== Creosote fitted document preview, 2026-09-07 =====
   Opt-in measurement controller supplies scale and rendered height. Keep
   authored document markup and its natural layout inside the stage intact.
   The viewport reserves page-flow height; it is never a scroll container. */
.sb-document-preview--fit {
  --sb-document-preview-document-width: 40rem;
  --sb-document-preview-scale: 1;
  --sb-document-preview-render-height: auto;
}

.sb-document-preview--fit[data-preview-width="mobile"] {
  --sb-document-preview-document-width: 24rem;
}

.sb-document-preview--fit > .sb-document-preview__viewport {
  position: relative;
  width: 100%;
  min-width: 0;
  height: var(--sb-document-preview-render-height);
  overflow: visible;
}

.sb-document-preview--fit .sb-document-preview__stage {
  display: block;
  width: var(--sb-document-preview-document-width);
  min-width: 0;
  max-width: none;
  margin: 0;
  overflow: visible;
  transform-origin: top left;
  transform: scale(var(--sb-document-preview-scale));
}

@supports (zoom: 1) {
  .sb-document-preview--fit .sb-document-preview__stage {
    zoom: var(--sb-document-preview-scale);
    transform: none;
  }
}

/* ===== Creosote promotions, 2026-09-04 (Matt approved packets 1 to 10 as written) ===== */

/* packet 1: desktop table wrap amendment */
@media (min-width: 901px) {
  .main table.sb-data-table--responsive {
    table-layout: fixed;
  }

  .main table.sb-data-table--responsive th,
  .main table.sb-data-table--responsive td {
    overflow-wrap: anywhere;
    hyphens: auto;
  }
}

/* packet 2: coffee-plan cluster (live only) */
.coffee-plan-source-state {
  display: grid;
  grid-template-columns: minmax(12rem, max-content) minmax(0, 1fr);
  gap: 0.75rem 1.25rem;
  align-items: baseline;
  margin: 2rem 0 1.25rem;
  padding: 1rem 1.25rem;
  background: var(--field);
  border-left: 8px solid var(--line);
  border-radius: var(--radius);
}
.coffee-plan-source-state.is-live { border-left-color: var(--ok); }
.coffee-plan-source-state.is-stale { border-left-color: var(--attn); }
.coffee-plan-source-state strong,
.coffee-plan-source-state span { min-width: 0; overflow-wrap: anywhere; font-weight: 400; }

.coffee-plan-row {
  min-width: 0;
  padding: 1.35rem;
  background: var(--field);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.coffee-plan-row.is-locked {
  border-color: var(--act);
  box-shadow: inset 0 0 0 2px var(--act);
}
.coffee-plan-row-head {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  align-items: start;
  justify-content: space-between;
  margin-bottom: 1rem;
}
.coffee-plan-row-head > * { min-width: 0; }
.coffee-plan-row-head h2 {
  margin: 0.15rem 0 0;
  font-size: 32px;
  font-weight: 400;
  line-height: 1.05;
  overflow-wrap: anywhere;
}
.coffee-plan-row-grid {
  display: grid;
  grid-template-columns: minmax(16rem, 0.85fr) minmax(0, 2.15fr);
  gap: 1.25rem;
  align-items: stretch;
}
.coffee-plan-source-column,
.coffee-plan-options-column {
  min-width: 0;
}
.coffee-plan-candidate-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1rem;
  align-items: stretch;
  min-width: 0;
}

.coffee-plan-coffee-card {
  position: relative;
  display: flex;
  flex-direction: column;
  min-width: 0;
  height: 100%;
  padding: 1.15rem;
  background: var(--field);
  border: 1px solid var(--line);
  border-left: 9px solid var(--line);
  border-radius: var(--radius);
}
.coffee-plan-coffee-card.is-selected {
  outline: 4px solid var(--act);
  outline-offset: -4px;
}
.coffee-plan-card-head {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  justify-content: space-between;
}

.coffee-plan-drag {
  padding: 0.45rem 0.65rem;
  color: inherit;
  background: transparent;
  border: 1px dashed currentColor;
  border-radius: var(--radius);
  font-weight: 400;
  cursor: grab;
  touch-action: none;
}
.coffee-plan-drag:active { cursor: grabbing; }
.coffee-plan-coffee-card h3 {
  margin: 0.8rem 0 0.45rem;
  color: var(--ink);
  font-size: 26px;
  font-weight: 400;
  line-height: 1.08;
  overflow-wrap: anywhere;
}

.coffee-plan-forecast {
  display: grid;
  gap: 0.2rem;
  margin: 1rem 0;
  padding: 0.85rem;
  color: var(--ink);
  background: var(--field);
  border-left: 4px solid var(--line);
  border-radius: var(--radius);
}
.coffee-plan-forecast span { color: var(--muted); font-weight: 400; }
.coffee-plan-forecast strong { font-size: 26px; font-weight: 400; line-height: 1.05; }
.coffee-plan-forecast small { font-size: var(--type); line-height: 1.35; }
.coffee-plan-forecast.is-observed { border-left-color: var(--ok); }
.coffee-plan-forecast.is-unknown { border-left-color: var(--attn); }
.coffee-plan-forecast.is-unknown span { color: var(--attn); }

.coffee-plan-controls select,
.coffee-plan-controls input,
.coffee-plan-row-footer input,
.coffee-plan-calendar-card input {
  box-sizing: border-box;
  width: 100%;
  min-width: 0;
  padding: 0.65rem;
  color: var(--ink);
  background: var(--field);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.coffee-plan-money {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  align-items: center;
  background: var(--field);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}
.coffee-plan-money span { padding-left: 0.65rem; color: var(--ink); }
.coffee-plan-money input { border: 0; background: transparent; }

.coffee-plan-controls .coffee-plan-check {
  display: flex;
  gap: 0.55rem;
  align-items: center;
  min-height: 2.75rem;
  padding: 0.55rem 0.7rem;
  color: var(--ink);
  background: var(--field);
  border-radius: var(--radius);
}
.coffee-plan-check input { width: 1.3rem; height: 1.3rem; accent-color: var(--act); }

.coffee-plan-add-option {
  width: 100%;
  margin-top: 0.75rem;
  padding: 0.85rem;
  color: inherit;
  background: transparent;
  border: 1px dashed var(--line);
  border-radius: var(--radius);
}

.coffee-plan-calendar {
  display: grid;
  gap: 2.5rem;
  margin-top: 2rem;
}
.coffee-plan-calendar-month > h2 { margin-bottom: 1rem; font-weight: 400; }
.coffee-plan-calendar-grid,
.coffee-plan-movement-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 1rem;
}

.coffee-plan-calendar-card,
.coffee-plan-movement-card {
  min-width: 0;
  padding: 1.25rem;
  background: var(--field);
  border: 1px solid var(--line);
  border-left: 7px solid var(--line);
  border-radius: var(--radius);
}
.coffee-plan-calendar-card[data-phase="prelaunch"] { border-left-color: var(--attn); }
.coffee-plan-calendar-card[data-phase="follow-up"] { border-left-color: var(--ok); }
.coffee-plan-calendar-card h3,
.coffee-plan-movement-card h3 {
  margin: 0.35rem 0 0.65rem;
  font-size: 26px;
  font-weight: 400;
  overflow-wrap: anywhere;
}
.coffee-plan-calendar-card p,
.coffee-plan-movement-card p { overflow-wrap: anywhere; }
.coffee-plan-movement-card.is-ethiopia {
  border-left-color: var(--act);
  box-shadow: inset 0 0 0 2px var(--act);
}
.coffee-plan-movement .sb-section-head { align-items: start; }

.coffee-plan-board {
  display: grid;
  gap: 2rem;
}

@media (max-width: 1200px) /* was 1180 in the packet; moved to creo's shared 1200 tier so iPad landscape (1180) never lands on a boundary (Team binding rule) */ {
  .coffee-plan-row-grid { grid-template-columns: minmax(14rem, 0.75fr) minmax(0, 2.25fr); }
  .coffee-plan-candidate-grid,
  .coffee-plan-calendar-grid,
  .coffee-plan-movement-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 900px) /* was 850 in the packet; moved to creo's shared 900 tier */ {
  .coffee-plan-source-state,
  .coffee-plan-row-grid { grid-template-columns: minmax(0, 1fr); }
  .coffee-plan-candidate-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 650px) {
  .coffee-plan-source-state,
  .coffee-plan-row,
  .coffee-plan-coffee-card,
  .coffee-plan-calendar-card,
  .coffee-plan-movement-card { padding: 1rem; }
  .coffee-plan-candidate-grid,
  .coffee-plan-calendar-grid,
  .coffee-plan-movement-grid { grid-template-columns: minmax(0, 1fr); }
  .coffee-plan-row-head h2 { font-size: 26px; }
}

/* packet 3: calendar (month-day, cal-item, cal-triage / cal-stack) */
.month-grid .month-day {
  display: flex;
  flex-direction: column;
  min-height: 0;
  height: 100%;
  overflow: hidden;
  position: relative;
}
.month-grid .month-day:nth-child(7n) { border-right: 0; }
.month-grid .month-day.is-drop {
  outline: 2px solid var(--act);
  outline-offset: -2px;
  background: var(--clay);
}
.month-grid .month-day:has(.cal-item:hover),
.month-grid .month-day:has(.cal-item:focus-visible) { z-index: 6; }
.month-grid .month-day .num {
  display: block;
  margin-bottom: 0.35rem;
  font-variant-numeric: tabular-nums;
}
.month-grid .month-day.is-today .num {
  display: inline-block;
  padding: 0 6px;
  background: var(--act);
  color: var(--on-action);
  border-radius: var(--radius);
}
.cal-cell-items {
  flex: 1;
  min-height: 0;
  overflow: hidden;
}
.cal-more {
  display: block;
  width: 100%;
  margin: 0.15rem 0 0;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--muted);
  text-align: left;
}
.cal-more:hover { color: var(--act); }

.cal-item {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 0;
  border-left: 3px solid var(--line);
  padding: 0.15rem 0 0.15rem 0.45rem;
  margin: 0.15rem 0;
  font: inherit;
  color: inherit;
  cursor: grab;
  position: relative;
  overflow: visible;
}
.cal-item:active { cursor: grabbing; }
.cal-item:hover { color: inherit; }
.cal-item.is-sbc:hover .cal-item-title { color: var(--act); }
.cal-item.is-shop:hover .cal-item-title { color: var(--shop-orange); }
.cal-item.is-hbt:hover .cal-item-title { color: var(--berry); }
.cal-item.is-sbu:hover .cal-item-title { color: var(--unroasted-green); }
.cal-dest { margin: 0.1rem 0 0; padding: 0; list-style: none; }
.cal-item .ch { display: block; color: var(--muted); }
.cal-item.is-sbc { border-left-color: var(--act); }
.cal-item.is-sbc .ch { color: var(--act); }
.cal-item.is-shop { border-left-color: var(--shop-orange); }
.cal-item.is-shop .ch { color: var(--shop-orange); }
.cal-item.is-hbt { border-left-color: var(--berry); }
.cal-item.is-hbt .ch { color: var(--berry); }
.cal-item.is-sbu { border-left-color: var(--unroasted-green); }
.cal-item.is-sbu .ch { color: var(--unroasted-green); }
.cal-item.is-scheduled { border-left-width: 3px; border-left-style: solid; }
.cal-item.is-ready { border-left-width: 3px; border-left-style: dashed; }
.cal-item.is-published { border-left-width: 6px; border-left-style: solid; }
.cal-item.is-passed { border-left-width: 3px; border-left-style: dotted; }
.cal-item.is-other { border-left-width: 2px; border-left-style: solid; }

.cal-triage,
.cal-stack {
  background: var(--field);
  border: var(--divider-width) solid var(--line);
  padding: 0.85rem 1rem 1rem;
}
.cal-triage-title { margin: 0 0 0.75rem; }
.cal-triage .field { margin: 0.25rem 0 0.75rem; }

/* packet 4: flow map (flow-map, flow-node) */
.flow-map {
  display: grid;
  gap: 8px;
  margin: 12px 0;
}

.flow-node {
  padding: 12px;
  background: var(--field);
  border-radius: var(--radius);
}

/* packet 5: popovers (list-pop, status-pop, date-pop, people-pop, and related) */
.list-pop {
  padding: 0.35rem 0 0.15rem;
  background: var(--field);
  border: var(--divider-width) solid var(--line);
}
.date-days {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 0.1rem 0.35rem;
  max-width: 22rem;
  margin: 0.35rem 0;
}
.status-pop,
.people-pop {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  text-align: right;
}
.status-pop .choice,
.people-pop .choice { width: 100%; justify-content: flex-end; }
.delete-pop {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.1rem;
}

.hold { color: var(--act); max-width: 28rem; }

/* packet 6: list rows (list-person, row-people fleet layout, row-dest, row-open, row-tip, sheet-row) */
.list-person {
  display: inline;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  text-align: left;
}
.list-person:hover { color: var(--act); }

.index-row .row-people {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.35rem;
  min-width: 0;
}
.index-row .row-people .row-people-owner,
.index-row .row-people .row-people-roles {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: flex-start;
  gap: 0.35rem;
  min-width: 0;
}
.index-row .row-people .list-person {
  min-width: 0;
  white-space: normal;
  overflow-wrap: anywhere;
}
.index-row .row-people .people-dot {
  display: inline;
  white-space: normal;
}

.row-people {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.15rem;
}
.cal-triage .row-people {
  flex-direction: row;
  flex-wrap: wrap;
  gap: 0.35rem 0.7rem;
  margin: 0.45rem 0;
}

.row-dest-item.is-sbc { color: var(--act); }
.row-dest-item.is-shop { color: var(--shop-orange); }
.row-dest-item.is-hbt { color: var(--berry); }
.row-dest-item.is-sbu { color: var(--unroasted-green); }
.dest-sep { color: var(--muted); }

.index-row .row-open {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.1rem;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  text-align: left;
}
.index-row:hover .launch-title,
.index-row:focus-within .launch-title { color: var(--act); }
.index-row.is-sbc:hover .launch-title,
.index-row.is-sbc:focus-within .launch-title { color: var(--act); }
.index-row.is-shop:hover .launch-title,
.index-row.is-shop:focus-within .launch-title { color: var(--shop-orange); }
.index-row.is-hbt:hover .launch-title,
.index-row.is-hbt:focus-within .launch-title { color: var(--berry); }
.index-row.is-sbu:hover .launch-title,
.index-row.is-sbu:focus-within .launch-title { color: var(--unroasted-green); }

.row-tip {
  position: absolute;
  left: 0;
  top: calc(100% - 0.2rem);
  z-index: 5;
  display: none;
  max-width: min(28rem, calc(100vw - 3rem));
  padding: 0.65rem 0.85rem;
  background: var(--paper);
  border: var(--divider-width) solid var(--line);
  color: var(--ink);
  font-size: 0.92em;
  line-height: 1.35;
  white-space: pre-wrap;
  pointer-events: none;
}
.index-row:hover .row-tip,
.index-row:focus-visible .row-tip,
.cal-item:hover .row-tip,
.cal-item:focus-visible .row-tip { display: block; }

.sheet-row { cursor: pointer; }
.sheet-row:hover .launch-title,
.sheet-row:focus-visible .launch-title { color: var(--act); }

/* packet 7: week strip (yours-kind, yours-layer, week-day) */
.yours-kind {
  font-size: var(--type);
  font-weight: 400;
  line-height: 1.2;
  margin: 1.4rem 0 0.45rem;
  color: var(--act);
}
.yours-layer:first-child .yours-kind { margin-top: 0; }

.week-day {
  display: grid;
  grid-template-columns: 7.5rem 1fr;
  gap: 0.35rem 1.25rem;
  padding: 0.85rem 0;
  border-top: var(--divider-width) solid var(--line);
}
.week-day:last-of-type { border-bottom: var(--divider-width) solid var(--line); }
.week-day-name { color: var(--muted); padding-top: 0.15rem; }

/* packet 8: disclosure (group-head, list-group, archive-year, archive-month) */
.group-head {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
  margin: 1.75rem 0 0.35rem;
  padding-top: 0.85rem;
  border-top: var(--divider-width) solid var(--line);
}
details.list-group { margin: 0; }
details.list-group > summary.group-head {
  cursor: pointer;
  list-style: none;
}
details.list-group > summary.group-head::-webkit-details-marker { display: none; }
details.list-group > summary.group-head:hover h2 { color: var(--act); }
.archive-year,
.archive-month { margin: 0 0 0.35rem; }
.archive-year > summary,
.archive-month > summary {
  display: flex;
  align-items: baseline;
  gap: 0.85rem;
  cursor: pointer;
  list-style: none;
  padding: 0.85rem 0 0.35rem;
}
.archive-year > summary::-webkit-details-marker,
.archive-month > summary::-webkit-details-marker { display: none; }
.archive-month { padding-left: 0.5rem; }

/* packet 9: email desk chrome (not the canvas) */
.email-campaign-actions { display: flex; flex-wrap: wrap; gap: 0.65rem 1rem; }
.email-template-mini {
  display: block;
  height: 7.5rem;
  overflow: hidden;
  pointer-events: none;
}
.email-template-mini .email-canvas {
  transform: scale(0.22);
  transform-origin: top left;
  width: 600px;
}

.span { grid-column: 1 / -1; }

.email-lot-photo { display: flex; gap: 0.45rem; align-items: center; }

button.email-template-row {
  display: grid;
  grid-template-columns: minmax(10rem, 1fr) minmax(12rem, 16rem);
  gap: 1rem;
  align-items: center;
  width: 100%;
  background: none;
  border: 0;
  border-top: var(--divider-width) solid var(--line);
  padding: 0.65rem 0;
  font: inherit;
  color: inherit;
  text-align: left;
}
button.email-template-row.is-on .email-template-name { color: var(--act); }

.email-check {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 6rem;
  gap: 0.2rem 1rem;
  padding: 0.7rem 0;
  border-top: var(--divider-width) solid var(--divider);
}
.email-check p { grid-column: 1 / -1; margin: 0; color: var(--muted); }
.email-check.blocked span,
.email-check.needed span { color: var(--act); }

/* packet 10: asset finder family and the field family */
.studio-asset-finder { margin: 1.5rem 0 0; padding: 1rem 0 0; }
.studio-asset-finder-head h2 { margin: 0 0 0.4rem; }
.studio-asset-search { display: grid; gap: 0.5rem; }
.studio-asset-controls { display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center; }
.studio-asset-controls input { flex: 1 1 16rem; }
.studio-asset-results { margin-top: 1rem; display: grid; gap: 0.75rem; }
.studio-asset-card { padding: 1rem 0; text-align: left; }
.studio-asset-thumb {
  display: block;
  max-width: 220px;
  max-height: 220px;
  width: auto;
  height: auto;
  border-radius: var(--radius);
}
.studio-asset-finder .is-held { color: inherit; }

.field {
  width: 100%;
  min-height: 44px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--field);
  font: inherit;
  color: inherit;
}
.field:hover,
.field:focus-visible { border-color: var(--act); }
textarea.field {
  height: auto;
  min-height: 88px;
  padding: 8px 12px;
  line-height: 1.45;
  resize: vertical;
  field-sizing: content;
}
