/* ============================================================================
   APP.CSS — chrome + the generic component layer.
   ----------------------------------------------------------------------------
   Every .cd-* class here carries the SAME NAME as Pulse's nordic.css and is
   re-implemented at the new token values. Nothing is renamed, so the port is a
   file swap and no markup changes.

   RULE: app.css may not define a .pr-* rule (that is procurement.css's job),
   and procurement.css may not define a .cd-* rule. check-portability.sh
   enforces both. That is what keeps the two layers separable at port time.
   ========================================================================== */

*, *::before, *::after { box-sizing: border-box; }
html { font-size: 16px; -webkit-text-size-adjust: 100%; }
body {
  margin: 0; background: var(--page); color: var(--ink);
  font-family: var(--font); font-size: var(--t-body); line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
}
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
::selection { background: var(--accent-tint); }

/* CJK: glyphs fill the em box, so a 1.35 line-height that suits Latin clips
   中文 and makes stacked bilingual cells touch. 1.4 is the floor everywhere.
   Tracking on CJK reads as a spacing bug and text-transform is a no-op that
   only shouts at the Latin half of a mixed string — neither may appear on any
   element that can hold Chinese. */
:lang(zh), [lang="zh"] { line-height: var(--lh-cjk); }
.cd-table td, .cd-table th, .cd-badge { letter-spacing: 0; text-transform: none; }

/* Tabular figures by scope, not globally — global tnum makes prose numbers
   look mechanical, and SF Pro's proportional figures read better in a sentence.
   The second declaration is belt-and-braces for the older Chromium in the PDF
   Lambda, which honours font-feature-settings but not the shorthand. */
.cd-table tbody, .cd-table tfoot,
input[type="number"], input[type="date"] {
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: "tnum" 1, "lnum" 1;
}

/* ══════════════════════════════════════════════════════════════════════════
   CHROME — live Pulse's header, verbatim.

   The old 264px accordion sidebar is gone. Live Pulse does not have one: it
   has a 60px sticky header carrying a HORIZONTAL top-nav, each group opening
   a dropdown panel that can hold a third level of uppercase section headings.
   Group -> section -> page, three levels, no more.

   The header is joined above 768px by a DOCKED LEFT PANEL — see THE DOCKED
   PANEL below. It is not the old sidebar coming back: it is the mobile sheet
   this file already styles, re-posed by one media query, because a top-nav is
   the wrong instrument for moving within a twenty-nine-page module.

   Every class name below is live Pulse's own (`app-header-menu__*`,
   `top-nav__*`, `mobile-nav__*`, `mt3-dock__*`, `nav-badge*`) at live Pulse's
   own values, so porting the module back is a file swap and not one line of
   markup changes. Colour comes from the `--sh-*` tokens in shell-themes.css —
   nothing here hardcodes the gradient except as a fallback for the case where
   that file is missing.

   NOTE on the prefix: tokens.css has used `--sh-pop` / `--sh-modal` /
   `--sh-stick` / `--sh-col` for SHADOWS since the first commit. The shell
   theme's tokens are all `--sh-<role>` names that do not collide with those
   four; the overlap is in the prefix only and is called out here so nobody
   "tidies" one set into the other.

   DELIBERATE EXCEPTION to §10 ("colour/size literals live in tokens.css"):
   this block alone keeps live Pulse's literals — 60px, 13px, #374151,
   rgba(255,255,255,.85), 0 10px 40px rgba(0,0,0,.15). They are here BECAUSE
   they are the other app's values: the whole point of the change is that this
   file can be dropped into Pulse and the chrome comes out identical. Routing
   them through the prototype's own token names would re-skin the chrome, which
   is exactly what was NOT asked for. Everything a theme can change still goes
   through --sh-*, and every z-index, radius and weight that the module already
   names still uses the token — the exception is the colour/size of the bar,
   not a licence for the file.
   ══════════════════════════════════════════════════════════════════════════ */

/* The footer is the last thing in the body and has margin-top:auto, so the
   body has to be the column it sits at the bottom of — otherwise a short page
   floats its footer half way up the viewport. */
body { display: flex; flex-direction: column; min-height: 100vh; }

.app-header-menu {
  background: var(--sh-header-bg, linear-gradient(135deg, #667eea 0%, #764ba2 100%));
  color: var(--sh-fg, #fff);
  position: sticky; top: 0; z-index: var(--z-header);
  box-shadow: 0 2px 10px rgba(0, 0, 0, .1);
  flex: none;
}
.app-header-menu__main {
  display: flex; align-items: center; gap: 16px; padding: 0 20px; height: 60px;
}

/* ---- Hamburger (mobile only) --------------------------------------------- */
.app-header-menu__hamburger {
  display: none; flex-direction: column; justify-content: center; gap: 5px;
  width: 36px; height: 36px; padding: 6px; border: none;
  background: rgba(255, 255, 255, .1); border-radius: 6px; cursor: pointer;
  transition: background .2s ease;
}
.app-header-menu__hamburger:hover { background: rgba(255, 255, 255, .2); }
.app-header-menu__hamburger-line {
  display: block; width: 100%; height: 2px; background: #fff; border-radius: 1px;
}

/* ---- Logo. An inline SVG wordmark, never an <img src>: the whole module has
   to open off file:// with no request leaving the machine. ----------------- */
.app-header-menu__logo-link {
  display: inline-flex; align-items: center; flex-shrink: 0;
  padding: 5px 7px; border-radius: 10px; background: transparent;
  transition: background .15s ease;
}
.app-header-menu__logo-link:hover { background: var(--sh-hover, rgba(255,255,255,.12)); text-decoration: none; }
.app-header-menu__logo { height: 36px; width: auto; display: block; }

/* ---- DEMO pill. Live Pulse's DEV pill, at the one job this module has for
   it: saying out loud that every number on screen is seed data, and whether
   the browser is now holding edits on top of it. -------------------------- */
.app-header-menu__dev {
  flex: none; display: inline-flex; align-items: center; gap: 6px;
  height: 26px; padding: 0 10px; border: none; border-radius: var(--r-pill);
  background: linear-gradient(90deg, #f59e0b, #ef4444); color: #fff;
  font: inherit; font-size: 11px; font-weight: var(--w-bold); line-height: 1;
  white-space: nowrap; cursor: pointer;
}
.app-header-menu__dev.is-collapsed { width: 26px; padding: 0; justify-content: center; }
.app-header-menu__dev:focus-visible { outline: 2px solid var(--sh-fg, #fff); outline-offset: 1px; }

/* ---- Title section ------------------------------------------------------- */
.app-header-menu__title-section { display: flex; flex-direction: column; flex-shrink: 0; }
.app-header-menu__title {
  font-size: 16px; font-weight: var(--w-semi); color: var(--sh-fg, #fff);
  margin: 0; white-space: nowrap; letter-spacing: -.01em;
}
.app-header-menu__subtitle {
  font-size: 11.5px; color: var(--sh-fg-dim, rgba(255,255,255,.75));
  margin-top: 1px; white-space: nowrap;
}

.app-header-menu__spacer { flex: 1; min-width: 0; }
.app-header-menu__extra-controls { display: flex; align-items: center; gap: 10px; flex: none; }
.app-header-menu__search { display: flex; align-items: center; margin-right: 12px; position: relative; flex: none; }
/* Everything except the nav holds its size. Flex was shrinking the hamburger
   from 36px to 12px on a phone — three lines squeezed into a sliver — while
   the row still overflowed, so nothing was gained and the one control a phone
   user needs was the one that got eaten. The nav is the shrinkable element;
   that is what the fold is for. */
.app-header-menu__hamburger, .app-header-menu__logo-link,
.app-header-menu__dev, .app-header-menu__user { flex: none; }

/* ══════════════════════════════════════════════════════════════════════════
   TOP NAV — the horizontal bar. Real values from TopNavMenu.css.
   ══════════════════════════════════════════════════════════════════════════ */
.top-nav { display: flex; align-items: center; flex: 0 1 auto; min-width: 0; }
.top-nav__list { display: flex; align-items: center; gap: 4px; list-style: none; margin: 0; padding: 0; }
.top-nav__item { position: relative; }
.top-nav__link {
  display: flex; align-items: center; gap: 6px; padding: 8px 14px; border-radius: 6px;
  color: rgba(255, 255, 255, .85); text-decoration: none;
  font-family: inherit; font-size: 13px; font-weight: var(--w-med);
  background: transparent; border: none; cursor: pointer;
  transition: all .15s ease; white-space: nowrap;
}
.top-nav__link:hover { background: rgba(255, 255, 255, .1); color: #fff; text-decoration: none; }
.top-nav__link--active, .top-nav__link--open { background: rgba(255, 255, 255, .15); color: #fff; }
.top-nav__icon { flex: none; }
.top-nav__chevron { margin-left: 2px; transition: transform .2s ease; opacity: .7; }
.top-nav__link--open .top-nav__chevron { transform: rotate(180deg); }

.top-nav__dropdown {
  position: absolute; top: 100%; left: 0;
  width: max-content; min-width: 216px; max-width: 340px;
  margin-top: 4px; padding: 6px; background: #fff; border-radius: 8px;
  box-shadow: 0 10px 40px rgba(0, 0, 0, .15), 0 2px 10px rgba(0, 0, 0, .1);
  list-style: none; z-index: var(--z-navpanel);
  animation: dropdownFadeIn .15s ease;
  max-height: calc(100vh - var(--header-height, 60px) - 24px); overflow-y: auto;
}
@keyframes dropdownFadeIn {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: translateY(0); }
}
.top-nav__section { list-style: none; }
.top-nav__section + .top-nav__section { margin-top: 2px; border-top: 1px solid #f1f5f9; padding-top: 4px; }
.top-nav__section-label {
  padding: 7px 12px 3px; font-size: 10.5px; font-weight: var(--w-bold);
  letter-spacing: .07em; text-transform: uppercase; color: #94a3b8; user-select: none;
}
.top-nav__section-items { list-style: none; margin: 0; padding: 0; }
.top-nav__dropdown-link {
  display: flex; align-items: center; gap: 10px; padding: 10px 14px; border-radius: 6px;
  color: #374151; text-decoration: none; font-size: 13px; font-weight: var(--w-med);
  transition: all .15s ease;
}
.top-nav__dropdown-link:hover { background: #f3f4f6; color: #111827; text-decoration: none; }
.top-nav__dropdown-link--active, .top-nav__dropdown-link--active:hover {
  position: relative; background: var(--nav-active-bg);
  color: var(--nav-active-fg); font-weight: var(--w-semi);
}
.top-nav__dropdown-link--active::before {
  content: ''; position: absolute; left: 0; top: 6px; bottom: 6px;
  width: 3px; border-radius: 0 3px 3px 0; background: var(--nav-active-rail);
}
.top-nav__dropdown-link .top-nav__icon { width: 20px; text-align: center; color: var(--ink-4); }
.top-nav__dropdown-link--active .top-nav__icon { color: var(--nav-active-fg); }
/* Sized to the text, NOT to the row: the badge belongs immediately after the
   word it counts, and the pin takes the slack after it. */
.top-nav__dropdown-label { flex: 0 1 auto; min-width: 0; overflow-wrap: anywhere; }
.top-nav__pin {
  flex: none; margin-left: auto; display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; margin-left: 6px; border-radius: 5px;
  color: #cbd5e1; opacity: 0; cursor: pointer;
}
.top-nav__dropdown-link:hover .top-nav__pin,
.top-nav__dropdown-link:focus-visible .top-nav__pin,
.top-nav__pin.is-on { opacity: 1; }
.top-nav__pin:hover { background: #f1f5f9; color: #64748b; }
.top-nav__pin.is-on { color: #f59e0b; }

/* A neighbour module's row. It is REAL — the group, the section and the page
   are what the live tree holds — and it is INERT, because this prototype does
   not ship that screen. A dead link is worse than an honest one. */
.top-nav__dropdown-link.is-elsewhere { color: var(--ink-4); cursor: default; }
.top-nav__dropdown-link.is-elsewhere:hover { background: var(--sunken); color: var(--ink-3); }
.top-nav__dropdown-note {
  padding: 8px 12px 4px; margin-top: 2px; border-top: 1px solid #f1f5f9;
  font-size: var(--t-micro); line-height: 1.45; color: var(--ink-4);
}
.top-nav__dropdown--more { left: auto; right: 0; min-width: 460px; max-width: min(720px, 90vw); columns: 2; column-gap: 18px; }
.top-nav__dropdown--more .top-nav__section { break-inside: avoid; page-break-inside: avoid; }

/* Live count on a nav row. Which queue has work in it is what decides where a
   buyer goes next, so it sits ON the link — 7px after the label, not floated
   to the row's edge where the eye has to travel to pair the two. */
.nav-badge {
  flex: none; min-width: 20px; margin-left: 7px; padding: 1px 6px; border-radius: var(--r-pill);
  font-size: 10.5px; font-weight: var(--w-bold); line-height: 1.5; text-align: center;
  font-variant-numeric: tabular-nums;
}
.nav-badge--open { background: #e0e7ff; color: #3730a3; }
.nav-badge--warn { background: #fef3c7; color: #92400e; }

/* ══════════════════════════════════════════════════════════════════════════
   PINNED CHIPS
   ══════════════════════════════════════════════════════════════════════════ */
.app-header-menu__favs {
  display: none; align-items: center; gap: 6px; margin-right: 10px;
  flex: 0 0 auto; overflow: hidden;
}
@media (min-width: 1280px) {
  .app-header-menu__favs { display: flex; }
  /* Collapse to zero width rather than display:none — an unrendered box
     reports a width of 0, the measurement then concludes the chips are free,
     shows them again, and the two states flip forever. */
  :root[data-nav-chips='off'] .app-header-menu__favs { max-width: 0; margin-right: 0; }
}
.app-header-menu__fav-wrap { position: relative; display: inline-flex; align-items: center; flex: 0 0 auto; }
.app-header-menu__fav {
  display: inline-flex; flex: 0 0 auto; align-items: center; gap: 5px; max-width: 132px;
  padding: 4px 9px; border-radius: var(--r-pill);
  background: var(--sh-chip-bg, rgba(255,255,255,.12)); color: var(--sh-fg, #fff);
  font-size: 12px; font-weight: var(--w-semi); text-decoration: none; white-space: nowrap;
}
.app-header-menu__fav span { overflow: hidden; text-overflow: ellipsis; }
.app-header-menu__fav:hover { background: var(--sh-hover, rgba(255,255,255,.2)); text-decoration: none; }
.app-header-menu__fav--more { flex: none; cursor: default; opacity: .85; }
.app-header-menu__fav-remove {
  position: absolute; right: 1px; display: grid; place-items: center;
  width: 17px; height: 17px; padding: 0; border: none; border-radius: 50%;
  background: var(--sh-fg, #fff); color: var(--sh-accent, #4f46e5);
  cursor: pointer; opacity: 0; pointer-events: none; transition: opacity .12s ease;
}
.app-header-menu__fav-wrap:hover .app-header-menu__fav-remove,
.app-header-menu__fav-remove:focus-visible { opacity: 1; pointer-events: auto; }
.app-header-menu__fav-wrap:hover .app-header-menu__fav { padding-right: 20px; }

/* ══════════════════════════════════════════════════════════════════════════
   HEADER CONTROLS — company scope, role, search trigger, user menu.
   The company switcher is NOT in a menu: ?company= scopes every register, the
   numbering preview and the statutory block, and the wrong company is the
   wrong legal document.
   ══════════════════════════════════════════════════════════════════════════ */
.app-co {
  display: inline-flex; align-items: center; gap: var(--s2); height: 34px;
  padding: 0 10px 0 12px; border: 1px solid var(--sh-search-border, rgba(255,255,255,.28));
  border-radius: var(--r-md); background: var(--sh-search-bg, rgba(255,255,255,.14));
  font-family: inherit; font-size: var(--t-meta); font-weight: var(--w-semi);
  color: var(--sh-fg, #fff); cursor: pointer; white-space: nowrap;
}
.app-co:hover { background: var(--sh-hover, rgba(255,255,255,.2)); }
.app-co .nm { font-weight: var(--w-med); opacity: .9; }
.app-co .ccy { font-weight: var(--w-med); font-family: var(--mono); font-size: .9em; opacity: .8; }
.app-co .chev { opacity: .7; display: inline-flex; }
.app-rolechip {
  display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 10px;
  border-radius: var(--r-md); background: var(--sh-chip-bg, rgba(255,255,255,.12));
  border: 1px solid transparent; font-family: inherit; font-size: var(--t-meta);
  font-weight: var(--w-semi); color: var(--sh-fg, #fff); cursor: pointer;
}
.app-rolechip:hover { background: var(--sh-hover, rgba(255,255,255,.2)); }
.app-iconbtn {
  width: 30px; height: 30px; display: grid; place-items: center; border: 0;
  border-radius: var(--r-ctl); background: none; color: var(--sh-fg-dim, rgba(255,255,255,.82)); cursor: pointer;
}
.app-iconbtn:hover { background: var(--sh-hover, rgba(255,255,255,.2)); color: var(--sh-fg, #fff); }

/* ---- The acceptance strip and the simulation play control -------------------
   Two chrome controls, sitting in the extra-controls slot beside the company
   and role chips. The score is not decoration: green says every one of the 25
   checks passed ON THIS PAGE LOAD, and red carries the name of the first
   failure in its title so the answer is one hover away rather than one click.
   They follow .app-rolechip's shape so the row reads as one set of controls,
   and they take their colour from the SHELL tokens rather than the page
   palette — the header is a different surface in all three themes and a
   status hue picked for a white card is unreadable on the aurora gradient. */
.app-accept, .app-simplay {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  height: 30px; border-radius: var(--r-md); border: 1px solid transparent;
  background: var(--sh-chip-bg, rgba(255,255,255,.12)); color: var(--sh-fg, #fff);
  font-family: var(--mono); font-size: var(--t-micro); font-weight: var(--w-bold);
  letter-spacing: .02em; cursor: pointer; white-space: nowrap;
}
.app-accept { padding: 0 10px; max-width: 260px; overflow: hidden; text-overflow: ellipsis; }
.app-simplay { width: 30px; padding: 0; font-family: inherit; font-size: 11px; }
.app-accept:hover, .app-simplay:hover { background: var(--sh-hover, rgba(255,255,255,.2)); }
/* Green is quiet — a passing strip should not compete with the page. Red is
   not: a wrong demo is the thing this control exists to prevent. */
.app-accept.is-ok::before { content: '●'; color: var(--ok-dot); font-size: 9px; }
.app-accept.is-bad {
  background: var(--bad-fg); border-color: var(--bad-dot); color: #fff;
}
.app-accept.is-bad::before { content: '●'; color: #fff; font-size: 9px; }
@media (max-width: 1024px) { .app-accept { max-width: 74px; } }
@media (max-width: 768px)  { .app-accept, .app-simplay { display: none; } }

.cd-search-header { position: relative; display: flex; align-items: center; }
.cd-search-trigger {
  width: 38px; height: 38px; display: grid; place-items: center;
  border: 1px solid var(--sh-search-border, rgba(255,255,255,.28)); border-radius: 10px;
  background: var(--sh-search-bg, rgba(255,255,255,.14)); color: var(--sh-fg, #fff);
  cursor: pointer; transition: background .15s ease;
}
.cd-search-trigger:hover, .cd-search-trigger.active { background: var(--sh-hover, rgba(255,255,255,.2)); }
.cd-search-pop {
  position: absolute; top: calc(100% + 10px); right: 0; width: min(560px, 92vw);
  z-index: var(--z-searchpop);
}

.app-header-menu__user { position: relative; }
.app-header-menu__user-btn {
  display: flex; align-items: center; gap: 8px; padding: 6px 12px; border: none;
  background: rgba(255, 255, 255, .1); border-radius: 8px; cursor: pointer;
  font-family: inherit; transition: background .2s ease;
}
.app-header-menu__user-btn:hover { background: rgba(255, 255, 255, .2); }
.app-header-menu__user-avatar {
  width: 32px; height: 32px; border-radius: 50%; flex: none;
  background: rgba(255, 255, 255, .2); color: #fff;
  font-weight: var(--w-semi); font-size: 14px;
  display: flex; align-items: center; justify-content: center;
}
.app-header-menu__user-name {
  font-size: 13px; font-weight: var(--w-med); color: #fff;
  max-width: 120px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.app-header-menu__chevron { color: rgba(255, 255, 255, .7); flex: none; }

.app-header-menu__dropdown {
  position: absolute; top: 100%; right: 0; width: 260px; margin-top: 8px;
  background: #fff; border-radius: var(--r-surface);
  box-shadow: 0 10px 40px rgba(0, 0, 0, .15), 0 2px 10px rgba(0, 0, 0, .1);
  overflow-y: auto; overflow-x: hidden; max-height: calc(100vh - 72px);
  z-index: var(--z-navpanel); animation: dropdownSlideIn .2s ease; color: var(--ink);
}
@keyframes dropdownSlideIn {
  from { opacity: 0; transform: translateY(-8px); }
  to   { opacity: 1; transform: translateY(0); }
}
.app-header-menu__dropdown-header { padding: 16px; background: #f9fafb; border-bottom: 1px solid #e5e7eb; }
.app-header-menu__dropdown-name  { display: block; font-size: 14px; font-weight: var(--w-semi); color: #111827; }
.app-header-menu__dropdown-email { display: block; font-size: 12px; color: #6b7280; margin-top: 2px; }
.app-header-menu__dropdown-role {
  display: inline-block; margin-top: 8px; padding: 3px 8px;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: #fff;
  font-size: 10px; font-weight: var(--w-semi); text-transform: uppercase; border-radius: 4px;
}
.app-header-menu__dropdown-divider { height: 1px; background: #e5e7eb; }
.app-header-menu__dropdown-item {
  display: flex; align-items: center; gap: 10px; width: 100%; padding: 12px 16px;
  border: none; background: transparent; color: #374151;
  font-family: inherit; font-size: 13px; font-weight: var(--w-med);
  text-decoration: none; cursor: pointer; text-align: left; transition: background .15s ease;
}
.app-header-menu__dropdown-item:hover { background: #f3f4f6; text-decoration: none; }
.app-header-menu__dropdown-item--danger { color: #dc2626; border-top: 1px solid #e5e7eb; }
.app-header-menu__dropdown-item--danger:hover { background: #fef2f2; }
.app-header-menu__dropdown-item-text {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  min-width: 0; flex: 1; line-height: 1.2; white-space: nowrap;
}
.app-header-menu__dropdown-item-value {
  font-family: var(--mono); font-size: 10.5px; font-weight: 400; color: #9ca3af;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis;
}
.app-header-menu__appearance { padding: 8px 14px 10px; border-top: 1px solid #f1f5f9; }
.app-header-menu__appearance-label {
  display: block; font-size: 11px; font-weight: var(--w-semi); letter-spacing: .04em;
  text-transform: uppercase; color: #94a3b8; margin-bottom: 6px;
}
.app-header-menu__appearance-options { display: flex; gap: 4px; }
.app-header-menu__appearance-btn {
  flex: 1 1 0; padding: 6px 4px; font-family: inherit; font-size: 11.5px;
  font-weight: var(--w-semi); color: #475569; background: #f1f5f9;
  border: 1px solid transparent; border-radius: 6px; cursor: pointer;
  transition: background .15s ease, color .15s ease;
}
.app-header-menu__appearance-btn:hover { background: #e2e8f0; }
.app-header-menu__appearance-btn.is-on { background: #eef2ff; border-color: #c7d2fe; color: #4338ca; }
.app-header-menu__appearance-btn:focus-visible { outline: 2px solid #6366f1; outline-offset: 1px; }

/* ══════════════════════════════════════════════════════════════════════════
   MOBILE SHEET — full-height left panel, not a 280px drawer with the page
   showing through. Picking a destination is a whole task.
   ══════════════════════════════════════════════════════════════════════════ */
.mobile-nav__overlay {
  display: none; position: fixed; inset: 0; background: rgba(0, 0, 0, .5);
  z-index: var(--z-scrim); animation: fadeIn .2s ease;
}
.mobile-nav__overlay.is-open { display: block; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.mobile-nav {
  position: fixed; top: 0; left: 0; width: min(420px, 100vw); height: 100dvh;
  background: #fff; transform: translateX(-100%);
  transition: transform .3s cubic-bezier(.4, 0, .2, 1);
  z-index: var(--z-navpanel); display: flex; flex-direction: column; overflow: hidden;
}
.mobile-nav--open { transform: translateX(0); }
.mobile-nav__head {
  position: sticky; top: 0; z-index: 2; display: flex; align-items: center; gap: 8px;
  padding: 10px 12px; background: #fff; border-bottom: 1px solid #e2e8f0;
}
.mobile-nav__filter {
  flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 8px;
  padding: 0 10px; height: 42px; border-radius: 10px; background: #f1f5f9; color: #64748b;
}
.mobile-nav__filter input {
  flex: 1 1 auto; min-width: 0; border: none; background: none; outline: none;
  font: inherit; font-size: 15px; color: #0f172a;
}
.mobile-nav__filter input::-webkit-search-cancel-button { display: none; }
.mobile-nav__filter-clear, .mobile-nav__close, .mobile-nav__dock {
  flex: none; display: grid; place-items: center; width: 40px; height: 40px;
  border: none; border-radius: 10px; background: none; color: #64748b; cursor: pointer;
}
.mobile-nav__filter-clear { width: 26px; height: 26px; }
/* Rendered on every viewport, shown only where it means something — the sheet
   has nothing to collapse to. */
.mobile-nav__dock { display: none; }
.mobile-nav__close:hover, .mobile-nav__filter-clear:hover, .mobile-nav__dock:hover { background: #e2e8f0; }
.mobile-nav__content {
  flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 12px;
  background: #f8fafc; display: flex; flex-direction: column; gap: 10px;
}
.mobile-nav__empty { margin: 24px 8px; color: #64748b; font-size: 14px; text-align: center; }
/* Cards keep their natural height; the COLUMN scrolls. Without flex:0 0 auto
   every card squashes to make the set fit and overflow:hidden clips it. */
.mobile-nav__card { flex: 0 0 auto; background: #fff; border: 1px solid #e2e8f0; border-radius: 12px; overflow: hidden; }
.mobile-nav__card--pins { border-color: #fde68a; background: #fffbeb; }
.mobile-nav__card--pins .mobile-nav__group-header--static { color: #92400e; }
.mobile-nav__group-header {
  display: flex; align-items: center; gap: 12px; width: 100%; padding: 14px 16px;
  border-radius: 8px; background: transparent; border: none; color: #6b7280;
  font-family: inherit; font-size: 12px; font-weight: var(--w-semi);
  text-transform: uppercase; letter-spacing: .5px; cursor: pointer; transition: all .15s ease;
}
.mobile-nav__group-header:hover { background: #f9fafb; color: #374151; }
.mobile-nav__group-header--active { color: #667eea; }
.mobile-nav__group-header--static {
  display: flex; align-items: center; gap: 12px; padding: 13px 16px;
  font-size: 12px; font-weight: var(--w-bold); letter-spacing: .06em; text-transform: uppercase;
}
.mobile-nav__group-label { flex: 1 1 auto; min-width: 0; text-align: left; }
.mobile-nav__count {
  flex: none; min-width: 22px; padding: 2px 7px; border-radius: var(--r-pill);
  background: #f1f5f9; color: #64748b; font-size: 11px; font-weight: var(--w-bold); letter-spacing: 0;
}
.mobile-nav__icon { width: 24px; flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; }
.mobile-nav__chevron { margin-left: auto; transition: transform .22s ease; }
.mobile-nav__chevron--expanded { transform: rotate(180deg); }
/* grid-template-rows 0fr -> 1fr animates to the content's natural height with
   no JS measurement and no max-height guess that clips a long group. The body
   stays mounted so CLOSING animates too; visibility is delayed to the end so a
   collapsed link leaves the tab order instead of being merely clipped. */
.mobile-nav__group-body {
  display: grid; grid-template-rows: 0fr; visibility: hidden;
  transition: grid-template-rows .22s ease, visibility 0s linear .22s;
}
.mobile-nav__card.is-open > .mobile-nav__group-body {
  grid-template-rows: 1fr; visibility: visible;
  transition: grid-template-rows .22s ease, visibility 0s;
}
.mobile-nav__group-body > * { overflow: hidden; min-height: 0; }
.mobile-nav__group-items { min-height: 0; padding: 0 8px 8px; }
.mobile-nav__section-label {
  padding: 10px 12px 4px; font-size: 10.5px; font-weight: var(--w-bold);
  letter-spacing: .07em; text-transform: uppercase; color: #94a3b8;
}
.mobile-nav__item {
  display: flex; align-items: center; gap: 12px; padding: 14px 16px; margin: 1px 0;
  border-radius: 8px; color: #374151; text-decoration: none;
  font-size: 14px; font-weight: var(--w-med); transition: all .15s ease;
}
.mobile-nav__item:hover { background: #f3f4f6; text-decoration: none; }
.mobile-nav__item--child { padding-left: 12px; gap: 10px; }
.mobile-nav__item--active, .mobile-nav__item--active:hover {
  position: relative; background: var(--nav-active-bg); color: var(--nav-active-fg); font-weight: var(--w-semi);
}
.mobile-nav__item--active::before {
  content: ''; position: absolute; left: 0; top: 6px; bottom: 6px;
  width: 3px; border-radius: 0 3px 3px 0; background: var(--nav-active-rail);
}
.mobile-nav__item-label { flex: 0 1 auto; min-width: 0; }
.mobile-nav__item.is-elsewhere { color: var(--ink-4); cursor: default; }
.mobile-nav__note { padding: 4px 12px 10px; font-size: var(--t-micro); color: var(--ink-4); }

/* ══════════════════════════════════════════════════════════════════════════
   MOBILE DOCK — 4 tabs + Menu. Hides on scroll-down, returns on scroll-up.
   ══════════════════════════════════════════════════════════════════════════ */
.mt3-dock {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: var(--z-dock);
  display: none; align-items: stretch;
  background: var(--sh-dock-bg, #fff); border-top: 1px solid var(--sh-dock-line, #e2e8f0);
  padding-bottom: env(safe-area-inset-bottom, 0); transition: transform .22s ease;
}
.mt3-dock--off { transform: translateY(calc(100% + env(safe-area-inset-bottom, 0px))); }
.mt3-dock__tab {
  flex: 1 1 0; min-width: 0; min-height: var(--h-dock);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px; padding: 6px 2px; background: none; border: none; font: inherit;
  color: var(--sh-dock-fg, #64748b); text-decoration: none; cursor: pointer;
}
.mt3-dock__tab:visited { color: var(--sh-dock-fg, #64748b); }
.mt3-dock__tab:hover { text-decoration: none; }
.mt3-dock__tab--active, .mt3-dock__tab--active:visited { color: var(--sh-dock-active, #667eea); }
.mt3-dock__tab:focus-visible { outline: 2px solid var(--sh-dock-active, #667eea); outline-offset: -3px; }
.mt3-dock__icon { flex: none; }
.mt3-dock__label {
  font-size: 10.5px; font-weight: var(--w-semi); line-height: 1.15;
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* ══════════════════════════════════════════════════════════════════════════
   FOOTER
   ══════════════════════════════════════════════════════════════════════════ */
.app-footer {
  background: #fff; padding: 1.25rem 2rem; box-shadow: 0 -2px 8px rgba(0, 0, 0, .06);
  text-align: center; margin-top: auto; border-top: 1px solid #e2e8f0;
  width: 100%; box-sizing: border-box; flex: none;
}
.app-footer__content { max-width: 1920px; margin: 0 auto; padding: 0 1rem; }
.app-footer__text { margin: 0; color: #718096; font-size: .875rem; font-weight: 400; line-height: 1.5; }

/* ══════════════════════════════════════════════════════════════════════════
   PAGE SHELL
   ══════════════════════════════════════════════════════════════════════════ */
/* The header is STICKY and in flow, so no padding-top compensates for it —
   that was the fixed header's tax and it left a 56px hole on every print. */
.app-main { flex: 1 0 auto; min-width: 0; }

/* Three widths, and the modifier is on the page rather than guessed per
   section. is-wide for registers — a 20-column grid should use the monitor it
   is given. is-narrow for single-column forms and prose — line length. */
.app-page {
  max-width: var(--w-page); margin: 0 auto;
  padding: var(--s5) var(--pad-page) calc(var(--h-actionbar) + var(--s7));
}
.app-page.is-wide   { max-width: none; }
.app-page.is-narrow { max-width: var(--w-narrow); }

/* ══════════════════════════════════════════════════════════════════════════
   THE DOCKED PANEL — the SAME element as the sheet above, docked.

   The top-nav is a faithful port and it is right for moving BETWEEN modules.
   It is wrong as the only way to move WITHIN one: twenty-nine procurement
   pages behind a single hover dropdown is a wall, and the shape of the module
   is invisible until you open it. The answer is BOTH, so nothing below is a
   new component — every rule here re-poses the sheet that shell.js already
   renders at every viewport. One markup path, one NAV literal, one filter, and
   no second thing to keep in step.

   769 is the far side of the 768 breakpoint at the foot of this file and is
   written HERE and only here. shell.js never learns the number: it writes
   data-nav-dock on <html> and this block decides whether that means anything.
   Below 768 not one declaration applies and the sheet is exactly what it was.

   It sits HERE, after PAGE SHELL rather than beside the sheet it re-poses,
   because it overrides .app-main and .app-footer at the same specificity they
   are declared with — above them it loses the cascade and the footer keeps its
   width:100%, which is 100% PLUS the margin and scrolls the page sideways.
   ══════════════════════════════════════════════════════════════════════════ */
@media (min-width: 769px) {
  /* One width, read by the panel AND by the content it displaces, so the two
     can never disagree about how much room the chrome is taking. */
  :root                        { --w-dock: var(--w-side); }
  :root[data-nav-dock="rail"]  { --w-dock: var(--w-side-rail); }

  .mobile-nav {
    /* Under the header, not over it: the header is sticky and in flow, so
       offsetting by its height keeps the panel's top edge on the header's
       bottom edge at every scroll position without a second listener. */
    top: var(--h-header); bottom: 0; height: auto;
    width: var(--w-dock); transform: none;
    /* Below the dropdowns, deliberately — see --z-sidepanel in tokens.css. */
    z-index: var(--z-sidepanel);
    border-right: 1px solid #e2e8f0;
    transition: width var(--d3) var(--ease);
  }
  /* The scrim belongs to the overlay. A resize with the sheet left open must
     not strand a black sheet over a docked panel. */
  .mobile-nav__overlay, .mobile-nav__overlay.is-open { display: none; }
  .mobile-nav__close { display: none; }
  .mobile-nav__dock  { display: grid; }

  /* Content moves OVER, never under: a register that starts 264px in is a
     register you can read all of. The footer moves with it or it centres its
     line under the panel. Transitioned with the panel so the collapse is one
     movement rather than two. */
  .app-main, .app-footer {
    margin-left: var(--w-dock);
    transition: margin-left var(--d3) var(--ease);
  }
  /* width:100% + a margin is 100% + 264px of document, and the whole page
     scrolls sideways. */
  .app-footer { width: auto; }

  /* The two things you steer with stay put while twenty-nine rows scroll under
     them. The head is already outside the scroller; Pinned is inside it and
     has to be told. */
  .mobile-nav__card--pins { position: sticky; top: 0; z-index: 1; }

  /* ---- THE RAIL ---------------------------------------------------------
     The answer to a 20-column register losing 264px is to COLLAPSE, not to
     cap: the register gets the whole monitor back and every destination is
     still one click away. Labels go, icons stay, and shell.js moves the label
     into the row's title so an icon column is not a memory test. */
  :root[data-nav-dock="rail"] .mobile-nav__filter,
  :root[data-nav-dock="rail"] .mobile-nav__group-label,
  :root[data-nav-dock="rail"] .mobile-nav__count,
  :root[data-nav-dock="rail"] .mobile-nav__chevron,
  :root[data-nav-dock="rail"] .mobile-nav__section-label,
  :root[data-nav-dock="rail"] .mobile-nav__item-label,
  :root[data-nav-dock="rail"] .mobile-nav__note { display: none; }
  :root[data-nav-dock="rail"] .mobile-nav__head { padding: 10px 0; justify-content: center; }
  :root[data-nav-dock="rail"] .mobile-nav__content { padding: 8px 5px; gap: 6px; }
  :root[data-nav-dock="rail"] .mobile-nav__group-items { padding: 0 4px 4px; }
  :root[data-nav-dock="rail"] .mobile-nav__group-header,
  :root[data-nav-dock="rail"] .mobile-nav__group-header--static,
  :root[data-nav-dock="rail"] .mobile-nav__item {
    position: relative; padding: 10px 0; gap: 0; justify-content: center;
  }
  /* The count is the only thing in the panel that says "there is work here"
     without being asked, so it survives the collapse as a dot rather than
     being dropped. The number is in the title with the label. */
  :root[data-nav-dock="rail"] .nav-badge {
    position: absolute; top: 5px; right: 7px; z-index: 1;
    width: 7px; height: 7px; min-width: 0; margin: 0; padding: 0;
    border-radius: var(--r-pill); overflow: hidden; text-indent: -99px;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   BUTTONS — same .cd-* names, new values.
   Solid accent, no gradient, and NO translateY on hover: a register toolbar
   where every button lifts 1px is the fidgetiest thing in the app.
   ══════════════════════════════════════════════════════════════════════════ */
.cd-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  height: var(--h-ctl); padding: 0 14px; border: 1px solid transparent;
  border-radius: var(--r-ctl); font-family: inherit; font-size: var(--t-cell);
  font-weight: var(--w-semi); line-height: 1; white-space: nowrap; cursor: pointer;
  text-decoration: none; transition: background var(--d1), border-color var(--d1), color var(--d1);
}
a.cd-btn, a.cd-btn:hover { text-decoration: none; }
.cd-btn svg { width: 15px; height: 15px; flex: none; }
.cd-btn-sm { height: var(--h-ctl-sm); padding: 0 10px; font-size: var(--t-meta); }
.cd-btn-sm svg { width: 14px; height: 14px; }
.cd-btn-lg { height: var(--h-ctl-lg); padding: 0 18px; font-size: var(--t-body); }
.cd-btn-primary { background: var(--accent); color: #fff; }
.cd-btn-primary:hover:not(:disabled) { background: var(--accent-hi); }
.cd-btn-primary:active:not(:disabled) { background: var(--accent-lo); }
.cd-btn-secondary { background: var(--surface); color: var(--ink-2); border-color: var(--line-2); }
.cd-btn-secondary:hover:not(:disabled) { background: var(--sunken); border-color: var(--line-3); color: var(--ink); }
.cd-btn-ghost { background: transparent; color: var(--ink-3); }
.cd-btn-ghost:hover:not(:disabled) { background: var(--mute-bg); color: var(--ink); }
.cd-btn-danger { background: var(--bad-dot); color: #fff; }
.cd-btn-danger:hover:not(:disabled) { background: var(--bad-fg); }
.cd-btn:disabled { opacity: .45; cursor: not-allowed; }

/* ══════════════════════════════════════════════════════════════════════════
   BADGES — a dot and a label, not a 999px uppercase letter-spaced pill.
   Uppercase is a no-op on 中文 and tracking on a CJK-bearing label looks like
   a rendering bug, so both are retired.
   ══════════════════════════════════════════════════════════════════════════ */
.cd-badge {
  display: inline-flex; align-items: center; gap: 6px; min-height: 20px; height: auto;
  padding: 2px 8px; border-radius: var(--r-badge); border: 1px solid transparent;
  font-size: var(--t-micro); font-weight: var(--w-semi); line-height: 1.45; white-space: nowrap;
}
.cd-badge::before { content: ''; flex: none; width: 6px; height: 6px; border-radius: 2px; background: currentColor; opacity: .85; }
/* QUIET is the default inside a table: no fill, no border, just the dot and the
   label in ink. Twenty filled chips in a register is a paintbox; the dot
   carries the same hue and the eye still scans the column in one pass. */
.cd-badge.is-quiet { background: none !important; border-color: transparent !important; color: var(--ink) !important; padding-left: 0; }
.cd-badge.is-quiet::before { opacity: 1; }
.cd-badge-success { background: var(--ok-bg);   border-color: var(--ok-line);   color: var(--ok-fg);   }
.cd-badge-info    { background: var(--info-bg); border-color: var(--info-line); color: var(--info-fg); }
.cd-badge-cyan    { background: var(--prog-bg); border-color: var(--prog-line); color: var(--prog-fg); }
.cd-badge-warning { background: var(--warn-bg); border-color: var(--warn-line); color: var(--warn-fg); }
.cd-badge-error   { background: var(--bad-bg);  border-color: var(--bad-line);  color: var(--bad-fg);  }
.cd-badge-gray    { background: var(--mute-bg); border-color: var(--mute-line); color: var(--mute-fg); }
.cd-badge-purple  { background: var(--odd-bg);  border-color: var(--odd-line);  color: var(--odd-fg);  }
.cd-badge.is-quiet.cd-badge-success::before { background: var(--ok-dot);   }
.cd-badge.is-quiet.cd-badge-info::before    { background: var(--info-dot); }
.cd-badge.is-quiet.cd-badge-cyan::before    { background: var(--prog-dot); }
.cd-badge.is-quiet.cd-badge-warning::before { background: var(--warn-dot); }
.cd-badge.is-quiet.cd-badge-error::before   { background: var(--bad-dot);  }
.cd-badge.is-quiet.cd-badge-gray::before    { background: var(--mute-dot); }
.cd-badge.is-quiet.cd-badge-purple::before  { background: var(--odd-dot);  }
/* ENDED, not FAILED. A cancelled PO is dead, not wrong: the label goes muted
   and the dot stays red. This is what stops the register looking like an alarm. */
.cd-badge.is-ended { color: var(--ink-4) !important; }
/* Terminal-and-good gets a lock, not a colour: billed is over, not "greener". */
.cd-badge.is-locked::after { content: '· locked'; color: var(--ink-4); font-weight: var(--w-med); }

/* ══════════════════════════════════════════════════════════════════════════
   TABLE
   ══════════════════════════════════════════════════════════════════════════ */
.cd-table-container {
  position: relative; background: var(--surface);
  border: 1px solid var(--line); border-radius: var(--r-surface);
  overflow: auto; overscroll-behavior: contain;
  /* Both axes deliberately. overflow-x:auto computes overflow-y to auto
     anyway, which makes this the scroll container — so position:sticky on the
     thead sticks to the container, and does nothing unless the container has a
     bounded height. fitGrid() in core.js sets --h-grid; this is the fallback. */
  max-height: var(--h-grid, calc(100vh - 360px)); min-height: 280px;
}
.cd-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: var(--t-cell); }

/* Header: 12px, sentence case, 600, --ink-3. Pulse's 11px uppercase 0.6px-
   tracked header is retired (see the CJK note at the top). Separation now
   comes from the sunken ground, the rule and the weight, which is enough. */
.cd-table thead th {
  position: sticky; top: 0; z-index: var(--z-thead);
  height: var(--h-row-head); padding: 10px 14px; text-align: left;
  background: var(--sunken); color: var(--ink-3);
  font-size: var(--t-label); font-weight: var(--w-semi); line-height: 1.35;
  white-space: nowrap; user-select: none; border-bottom: 1px solid var(--line);
}
.cd-table tbody td {
  height: var(--h-row); padding: 12px 14px; vertical-align: middle;
  color: var(--ink); border-bottom: 1px solid var(--line); line-height: var(--lh-cell);
}
.cd-table tbody tr:last-child td { border-bottom: 0; }
.cd-table tbody tr:hover > td { background: var(--row-hover); }
.cd-table tbody tr.is-sel > td { background: var(--row-sel); }
.cd-table tbody tr.is-sel > td:first-child { box-shadow: inset 2px 0 0 0 var(--accent); }
/* No zebra. Zebra + 20 columns + status colour is soup; hairlines and hover win. */
.cd-table th.is-num, .cd-table td.is-num { text-align: right; }
.cd-table td.is-num { font-variant-numeric: tabular-nums; }
/* A vertical rule on every column is a spreadsheet; on the column that starts
   a group (the money block, the India block) it is structure. */
.cd-table th.is-groupstart, .cd-table td.is-groupstart { border-left: 1px solid var(--line); }
.cd-table tbody tr.is-bi td { height: var(--h-row-bi); }
/* BOM children under parent_line_id: indented, muted, and they do not enter
   the totals — the row says so rather than a footnote. */
.cd-table tbody tr.is-child td { color: var(--ink-3); background: var(--sunken); }
.cd-table tbody tr.is-child td:first-child { padding-left: 32px; background: var(--sunken); }

/* Sort: a real button. The arrow slot is always reserved so nothing shifts on
   click, and the inactive arrow appears only on hover. A non-sortable header
   renders no button and no slot. */
.cd-th-sort { all: unset; display: inline-flex; align-items: center; gap: 6px; cursor: pointer; color: inherit; font: inherit; }
.cd-th-sort:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
.cd-th-sort .arrow { flex: none; width: 12px; text-align: center; font-size: 9px; color: transparent; transition: color var(--d1); }
.cd-th-sort:hover .arrow { color: var(--ink-5); }
th[aria-sort] .cd-th-sort { color: var(--ink); }
th[aria-sort] .arrow { color: var(--accent); }

/* Sticky first column. The identifier must never scroll away in an 18-column
   grid or you lose which row you are reading. It carries its own opaque
   ground, so the hover rule has to repaint it explicitly. */
.cd-table th:first-child, .cd-table td:first-child {
  position: sticky; left: 0; z-index: var(--z-stickycol); background: var(--surface);
}
.cd-table thead th:first-child { z-index: calc(var(--z-thead) + 1); background: var(--sunken); }
.cd-table tbody tr:hover > td:first-child { background: var(--row-hover); }
.cd-table tbody tr.is-sel > td:first-child { background: var(--row-sel); }
/* Edge shadow only once the grid has actually been scrolled, or every register
   carries a permanent smudge down its left third. */
.cd-table-container.is-scrolled td:first-child,
.cd-table-container.is-scrolled th:first-child { box-shadow: var(--sh-col); }

.cd-table-toolbar { display: flex; align-items: center; gap: var(--s3); margin-bottom: var(--s3); flex-wrap: wrap; }
.cd-table-toolbar-info { font-size: var(--t-meta); color: var(--ink-3); }
.cd-table-toolbar-info strong { color: var(--ink); font-weight: var(--w-semi); }
.cd-table-toolbar-actions { margin-left: auto; display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }

.cd-pagination-bar {
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  padding: 14px 2px 0; font-size: var(--t-meta); color: var(--ink-3);
}
.cd-pagination-bar .spacer { margin-left: auto; }
.cd-page-btn {
  min-width: 30px; height: 30px; padding: 0 8px; border: 1px solid var(--line-2);
  border-radius: var(--r-ctl); background: var(--surface); font-family: inherit;
  font-size: var(--t-meta); font-weight: var(--w-med); color: var(--ink-2); cursor: pointer;
  font-variant-numeric: tabular-nums;
}
.cd-page-btn:hover:not(:disabled) { border-color: var(--line-3); background: var(--sunken); }
.cd-page-btn.is-on { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: var(--w-semi); }
.cd-page-btn:disabled { opacity: .4; cursor: not-allowed; }

/* ══════════════════════════════════════════════════════════════════════════
   TABS
   ══════════════════════════════════════════════════════════════════════════ */
.cd-tabs {
  display: flex; align-items: center; gap: var(--s1);
  border-bottom: 1px solid var(--line); overflow-x: auto; scrollbar-width: none;
}
.cd-tabs::-webkit-scrollbar { display: none; }
.cd-tab {
  position: relative; display: inline-flex; align-items: center; gap: 7px;
  height: 38px; padding: 0 12px; border: 0; background: none; cursor: pointer;
  font-family: inherit; font-size: var(--t-label); font-weight: var(--w-semi);
  color: var(--ink-3); white-space: nowrap; text-decoration: none;
}
.cd-tab:hover { color: var(--ink); text-decoration: none; }
.cd-tab.active { color: var(--accent); box-shadow: inset 0 -2px 0 0 var(--accent); }
.cd-tab .n {
  min-width: 18px; height: 17px; padding: 0 5px; border-radius: var(--r-pill);
  background: var(--mute-bg); color: var(--ink-3);
  font-size: 10.5px; font-weight: var(--w-semi); display: grid; place-items: center;
  font-variant-numeric: tabular-nums;
}
.cd-tab.active .n { background: var(--accent-tint); color: var(--accent); }

/* ══════════════════════════════════════════════════════════════════════════
   FORM CONTROLS
   ══════════════════════════════════════════════════════════════════════════ */
.cd-form-group { display: flex; flex-direction: column; margin: 0; min-width: 0; }
.cd-form-label {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 3px 6px;
  min-height: 34px; margin-bottom: 6px;   /* two label lines reserved, so
     controls across a row land at the same height and the form is not ragged */
  font-size: var(--t-label); font-weight: var(--w-semi); color: var(--ink-2);
}
.cd-form-label .req { color: var(--bad-dot); font-weight: var(--w-bold); }
.cd-form-label .prov { font-size: var(--t-micro); font-weight: var(--w-reg); color: var(--ink-4); }
.cd-form-input, .cd-form-select, .cd-form-textarea {
  width: 100%; height: var(--h-ctl); padding: 0 12px;
  border: 1px solid var(--line-2); border-radius: var(--r-ctl);
  background: var(--surface); color: var(--ink);
  font-family: inherit; font-size: var(--t-body); line-height: var(--lh-body);
  outline: none; transition: border-color var(--d1), box-shadow var(--d1);
}
.cd-form-select { padding-right: 30px; appearance: none; cursor: pointer;
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-4) 50%),
                    linear-gradient(135deg, var(--ink-4) 50%, transparent 50%);
  background-position: calc(100% - 15px) 15px, calc(100% - 11px) 15px;
  background-size: 4px 4px, 4px 4px; background-repeat: no-repeat; }
.cd-form-textarea { height: auto; min-height: 84px; padding: 9px 12px; resize: vertical; line-height: var(--lh-prose); }
.cd-form-input:hover, .cd-form-select:hover { border-color: var(--line-3); }
.cd-form-input:focus, .cd-form-select:focus, .cd-form-textarea:focus {
  border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-ring);
}
.cd-form-input::placeholder { color: var(--ink-4); }
.cd-form-input:disabled, .cd-form-select:disabled, .cd-form-textarea:disabled {
  background: var(--mute-bg); color: var(--ink-4); cursor: not-allowed;
}
.cd-form-hint { margin-top: 6px; font-size: var(--t-label); color: var(--ink-4); line-height: var(--lh-body); }
/* The error REPLACES the hint. Never both — a field with two grey lines under
   it and one red line under that is where people stop reading. */
.cd-form-group.is-err .cd-form-input,
.cd-form-group.is-err .cd-form-select { border-color: var(--bad-dot); box-shadow: 0 0 0 3px var(--bad-ring); }
.cd-form-group.is-err .cd-form-hint { display: none; }
.cd-form-err { margin-top: 6px; font-size: var(--t-label); font-weight: var(--w-med); color: var(--bad-fg); }

/* ══════════════════════════════════════════════════════════════════════════
   EMPTY / LOADING / ERROR
   ══════════════════════════════════════════════════════════════════════════ */
.cd-empty-state { padding: var(--s9) var(--s5); text-align: center; }
.cd-empty-state-icon { color: var(--ink-5); margin-bottom: var(--s3); }
.cd-empty-state-icon svg { width: 26px; height: 26px; }
.cd-empty-state-title { font-size: var(--t-node); font-weight: var(--w-semi); color: var(--ink); margin-bottom: 6px; }
.cd-empty-state-desc { font-size: var(--t-cell); color: var(--ink-3); max-width: 52ch; margin: 0 auto var(--s4); line-height: var(--lh-prose); }

/* Skeleton rows at the real row height, so the page does not jump 350px when
   the data lands. Never the word "Loading…". */
.cd-skel {
  display: block; height: 12px; border-radius: 4px;
  background: linear-gradient(90deg, #eef0f4 0%, #f7f8fa 50%, #eef0f4 100%);
  background-size: 200% 100%; animation: cd-shim 1.15s linear infinite;
}
@keyframes cd-shim { from { background-position: 200% 0; } to { background-position: -200% 0; } }
/* Refreshing WITH data: keep the rows, dim them, one 2px indeterminate bar. */
.cd-table-container.is-refreshing tbody { opacity: .45; pointer-events: none; }
.cd-table-container.is-refreshing::before {
  content: ''; position: absolute; inset: 0 0 auto 0; height: 2px;
  z-index: calc(var(--z-thead) + 2);
  background: linear-gradient(90deg, transparent, var(--accent), transparent);
  background-size: 40% 100%; animation: cd-bar 1s linear infinite;
}
@keyframes cd-bar { from { background-position: -40% 0; } to { background-position: 140% 0; } }

/* ══════════════════════════════════════════════════════════════════════════
   TOAST — a white surface with a 3px tone rail, not a solid saturated fill.
   White text on #ef4444 at 13px is the least readable thing in the live
   system, and the error toast is the one you most need to read.
   ══════════════════════════════════════════════════════════════════════════ */
.cd-toast-container { position: fixed; right: var(--s6); bottom: var(--s6); z-index: var(--z-toast); display: flex; flex-direction: column; gap: var(--s2); }
.cd-toast {
  display: flex; align-items: flex-start; gap: 10px;
  width: 360px; max-width: calc(100vw - var(--s8)); padding: 12px 14px;
  border-radius: var(--r-md); background: var(--surface);
  border: 1px solid var(--line); border-left: 3px solid var(--ink-5);
  box-shadow: var(--sh-pop); font-size: var(--t-cell); line-height: var(--lh-body);
  color: var(--ink); animation: cd-toast var(--d3) var(--ease);
}
.cd-toast-success { border-left-color: var(--ok-dot);   }
.cd-toast-error   { border-left-color: var(--bad-dot);  }
.cd-toast-warning { border-left-color: var(--warn-dot); }
.cd-toast-info    { border-left-color: var(--accent);   }
.cd-toast-msg { flex: 1; min-width: 0; }
.cd-toast-close { all: unset; cursor: pointer; color: var(--ink-4); flex: none; padding: 2px; border-radius: 4px; }
.cd-toast-close:hover { color: var(--ink); background: var(--mute-bg); }
@keyframes cd-toast { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* ══════════════════════════════════════════════════════════════════════════
   MODAL — confirmations and short pickers only. Forms are pages.
   ══════════════════════════════════════════════════════════════════════════ */
.cd-modal-backdrop {
  display: none; position: fixed; inset: 0; z-index: var(--z-modal);
  background: var(--scrim); backdrop-filter: blur(2px);
  align-items: center; justify-content: center; padding: var(--s6);
}
.cd-modal-backdrop.visible { display: flex; }
.cd-modal {
  display: flex; flex-direction: column; width: 100%; max-height: 88vh;
  background: var(--surface); border-radius: var(--r-modal);
  box-shadow: var(--sh-modal); overflow: hidden; animation: cd-modal var(--d2) var(--ease);
}
.cd-modal-sm { max-width: 440px; } .cd-modal-md { max-width: 560px; }
.cd-modal-lg { max-width: 760px; } .cd-modal-xl { max-width: 960px; }
.cd-modal-header { display: flex; align-items: center; justify-content: space-between; gap: var(--s3); padding: var(--s4) var(--s5); border-bottom: 1px solid var(--line); }
.cd-modal-header h3 { margin: 0; font-size: var(--t-node); font-weight: var(--w-semi); }
.cd-modal-body { flex: 1; overflow-y: auto; padding: var(--s5); font-size: var(--t-body); line-height: var(--lh-prose); }
.cd-modal-footer { display: flex; justify-content: flex-end; gap: var(--s2); padding: 14px var(--s5); border-top: 1px solid var(--line); background: var(--sunken); }
.cd-modal-footer .note { margin-right: auto; font-size: var(--t-label); color: var(--ink-3); }
@keyframes cd-modal { from { opacity: 0; transform: translateY(8px) scale(.985); } to { opacity: 1; transform: none; } }

/* ══════════════════════════════════════════════════════════════════════════
   COMMAND PALETTE
   ══════════════════════════════════════════════════════════════════════════ */
.cd-palette-backdrop { display: none; position: fixed; inset: 0; z-index: var(--z-palette); background: var(--scrim); padding: 12vh var(--s5) var(--s5); justify-content: center; align-items: flex-start; }
.cd-palette-backdrop.visible { display: flex; }
.cd-palette { width: 100%; max-width: 620px; background: var(--surface); border-radius: var(--r-modal); box-shadow: var(--sh-modal); overflow: hidden; display: flex; flex-direction: column; max-height: 70vh; }
.cd-palette-box { display: flex; align-items: center; gap: 10px; padding: 14px 18px; border-bottom: 1px solid var(--line); }
.cd-palette-box input { flex: 1; border: 0; outline: none; font-family: inherit; font-size: var(--t-lead); color: var(--ink); background: none; }
.cd-palette-results { overflow-y: auto; padding: var(--s2); }
.cd-palette-group { padding: 10px 12px 4px; font-size: var(--t-micro); font-weight: var(--w-semi); text-transform: uppercase; letter-spacing: var(--tr-caps); color: var(--ink-4); }
.cd-palette-item { display: flex; align-items: center; gap: 10px; padding: 9px 12px; border-radius: var(--r-ctl); cursor: pointer; text-decoration: none; color: var(--ink); font-size: var(--t-cell); }
.cd-palette-item:hover, .cd-palette-item.hl { background: var(--accent-tint); text-decoration: none; }
.cd-palette-item .sub { margin-left: auto; color: var(--ink-4); font-size: var(--t-meta); }

/* ══════════════════════════════════════════════════════════════════════════
   DENSITY — eight scoped rules, never a fork.
   The DEFAULT is the designed value; compact is a stated, reversible,
   URL-addressable exception. HR's earlier version restated the whole type
   scale unconditionally and then put it back under an attribute — a fork in
   override form, and the single biggest reason that module stopped looking
   like its host app.
   ══════════════════════════════════════════════════════════════════════════ */
[data-density="compact"] .cd-table tbody td { height: 36px; padding: 8px 12px; font-size: 13px; }
[data-density="compact"] .cd-table thead th { height: 32px; padding: 8px 12px; font-size: 11.5px; }
[data-density="compact"] .cd-form-input,
[data-density="compact"] .cd-form-select { height: 32px; font-size: 13px; }
[data-density="compact"] .cd-table tbody tr.is-bi td { height: 52px; }
[data-density="compact"] .cd-badge { min-height: 18px; font-size: 10.5px; }
[data-density="compact"] .cd-btn { height: 32px; font-size: var(--t-meta); }
[data-density="compact"] .cd-tab { height: 34px; }

/* ══════════════════════════════════════════════════════════════════════════
   RESPONSIVE / PRINT / MOTION
   ══════════════════════════════════════════════════════════════════════════ */
/* The breakpoints are live Pulse's own, in live Pulse's own order. Each one
   drops exactly one thing, and each drop buys the nav its width back. */

/* 1366 — the subtitle yields to the nav. Dropped whole, never ellipsised:
   show it fully or not at all. */
@media (max-width: 1366px) {
  .app-header-menu__subtitle { display: none; }
}
/* 1024 — the whole title section yields. */
@media (max-width: 1024px) {
  .app-header-menu__title-section { display: none; }
  .app-co .nm { display: none; }
}
/* 900 — the DEMO pill collapses to a dot and the More panel to one column. */
@media (max-width: 900px) {
  .app-header-menu__dev-text { display: none; }
  .app-header-menu__dev { width: 26px; padding: 0; justify-content: center; }
  .top-nav__dropdown--more { min-width: 260px; columns: 1; }
}
/* 768 — the hamburger and the dock appear, the horizontal nav goes. The
   breakpoint lives HERE and only here; shell.js reads the computed display of
   .top-nav rather than re-hardcoding 768 in a second place. */
@media (max-width: 768px) {
  :root { --pad-page: 16px; }
  .app-header-menu__hamburger { display: flex; }
  .top-nav { display: none; }
  .app-header-menu__user-name { display: none; }
  .app-rolechip { display: none; }
  .mt3-dock { display: flex; }
  /* Reserve the dock's height for as long as the dock EXISTS, not only while
     it is on screen: removing the padding mid-scroll reflowed the whole page
     and left a wide table's column layout visibly wrong until the next
     scroll. The bar hides to free the VIEW, not to hand the space back. */
  .app-main { padding-bottom: calc(var(--h-dock-pad) + env(safe-area-inset-bottom, 0px)); }
}
/* 640 — the search trigger shrinks and its panel goes full-width fixed. The
   row also tightens: 10px gaps, 12px ends, and the company chip drops its
   currency (the base currency is on every page that spends it). */
@media (max-width: 640px) {
  .cd-search-trigger { width: 36px; height: 36px; }
  .cd-search-pop {
    position: fixed; top: calc(var(--header-height, 60px) + 6px);
    left: 8px; right: 8px; width: auto;
  }
  .app-header-menu__main { gap: 10px; padding: 0 12px; }
  .app-co .ccy { display: none; }
}
/* 480 — a phone. The wordmark crops to its mark (preserveAspectRatio slice, so
   it is the SAME svg, not a second file) and the user button loses its padding.
   Below this the row was 477px wide inside a 360px viewport and the WHOLE PAGE
   scrolled sideways — a table you cannot read straight is worse than a logo
   you cannot read. */
@media (max-width: 480px) {
  .app-header-menu__logo { width: 26px; }
  .app-header-menu__user-btn { padding: 6px; gap: 6px; }
}
/* 400 — the smallest phones. The DEMO dot yields; the demo banner on every
   invented table still says what this data is. */
@media (max-width: 400px) {
  .app-header-menu__dev { display: none; }
  .app-header-menu__main { gap: 8px; padding: 0 8px; }
  .app-header-menu__search { margin-right: 4px; }
}
@media print {
  .app-header-menu, .mt3-dock, .mobile-nav, .mobile-nav__overlay,
  .app-footer, .cd-table-toolbar { display: none !important; }
  .app-main { margin: 0; padding: 0; }
  .app-page { max-width: none; padding: 0; }
  .cd-table-container { max-height: none; border: 0; }
}
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important; transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}
