/* ============================================================
   New UI (Beta)
   ------------------------------------------------------------
   Product-facing redesign layer for the portal shell (header,
   top nav, cards, buttons, typography).

   Two sources, and they agree:

   1. EzeeFiber_BrandGuidelines_V2.pdf — the brand itself. Colors
      from the Cheat Sheet (p.5) and Color Palette (p.22-23);
      Outfit and its weight hierarchy from Typography (p.30-31);
      logo minimum sizes and approved color pairings from the
      Logo and Color sections (p.11-12, p.24-26). Page numbers
      are cited inline wherever a rule here traces to one.

   2. GRIT (~/Desktop/construction-ticket-app) — the other
      internal app built on the same brand. Its src/theme.css is
      the shared semantic token layer (surfaces, text, borders,
      status colors) and its src/App.css is the shell these
      styles mirror. Token NAMES AND VALUES are deliberately
      identical to GRIT's: the two apps are meant to read as one
      product, so a change to a token here belongs there too.

   Everything is scoped under `body.new-ui`, so the existing UI is
   completely untouched unless the toggle is on.

   Toggle lives in Profile → Display Settings → "New UI" and is
   admin-gated for now (see script.js). This is still a shell
   pass — page-specific tools (Competition Checker, QA tools,
   etc.) keep their current markup/styles and need dedicated
   passes. Dark mode is no longer improvised: it adopts GRIT's
   near-black surfaces wholesale, which also avoids the
   navy-on-navy pairing the guidelines rule out (p.26).
   ============================================================ */

body.new-ui {
    /* ---------- Brand colors ----------
       Verbatim from the Brand Guidelines V2 cheat sheet (p.5) and Color
       Palette / Secondary Palette (p.22-23). These five are the whole brand
       palette; everything else below is a semantic token built from them. */
    --brand-orange: #fb612f;
    --brand-navy: #1a2638;
    --brand-gray: #d9d9d6;
    --brand-gray-light: #e3e3e3;
    --brand: var(--brand-orange);

    /* ---------- Semantic surfaces / text ----------
       Names and values shared verbatim with GRIT (src/theme.css) so the two
       apps read as one product. Changing a value here means changing it
       there — they are meant to stay identical. */
    --surface-page: #f9f9f7;
    --surface-card: #ffffff;
    --text-primary: var(--brand-navy);
    --text-secondary: #52514e;
    --text-muted: #898781;
    --border: rgba(26, 38, 56, .12);
    --gridline: var(--brand-gray-light);

    /* Tinted orange for hover/selected washes. Not brand-book colors — they
       are the hero orange at low alpha, so they stay on-brand by
       construction rather than by adding new hues to the palette. */
    --brand-wash: rgba(251, 97, 47, .10);
    --brand-wash-strong: rgba(251, 97, 47, .16);
    --brand-ink: #e0501f;

    /* ---------- Status colors ----------
       GRIT's accessibility-validated set, deliberately NOT brand colors so a
       record's state can never be confused with a brand action color. Defined
       here so the portal's own state chips (QA queues, dispatch, tickets) stop
       inventing their own greens and reds per tool. */
    --status-open: #ec835a;
    --status-inprogress: #fab219;
    --status-pending: #3b7dd8;
    --status-closed: #0ca30c;
    --status-critical: #d03b3b;

    /* One radius, like GRIT. The old 8/14/20 ramp made the same nesting depth
       look different from screen to screen; --radius-pill survives only for
       genuine pills (badges, status chips), not for nav or inputs. */
    --radius: 8px;
    --radius-sm: 8px;
    --radius-md: 8px;
    --radius-lg: 8px;
    --radius-pill: 999px;

    /* Borders carry the elevation now, not shadows — GRIT's cards are a
       hairline on white with no shadow at all, and matching that is most of
       what makes the two apps look related. Kept as tokens (rather than
       deleted) because the dropdowns and modals genuinely float and still
       need to read as above the page. */
    --shadow-sm: 0 1px 2px rgba(26, 38, 56, .06);
    --shadow-md: 0 6px 18px -6px rgba(26, 38, 56, .18);
    --shadow-lg: 0 24px 48px -16px rgba(26, 38, 56, .25);

    --ease-standard: cubic-bezier(.2, .7, .2, 1);
    --dur-fast: 120ms;
    --dur-base: 200ms;

    /* ---------- Legacy aliases ----------
       The ~400 rules below this block were written against --ez-*. Rather than
       touch every one, the old names now point at the semantic tokens. New
       rules should use the semantic names; these exist so the rename was not a
       400-line diff, and can be deleted once nothing references them. */
    --ez-orange: var(--brand-orange);
    --ez-orange-600: var(--brand-ink);
    --ez-orange-50: var(--brand-wash);
    --ez-navy: var(--text-primary);
    --ez-navy-700: var(--surface-page);
    --ez-gray: var(--border);
    --ez-gray-50: var(--surface-page);
    --ez-gray-100: var(--brand-gray-light);
    --ez-gray-400: var(--text-muted);
    --ez-gray-700: var(--text-secondary);

    /* Outfit is the brand's primary typeface — "a beautiful geometric sans
       serif chosen for its similar characteristics to the Ezee Fiber Internet
       logo" (Brand Guidelines V2, p.30). It replaces Aptos: Aptos was the
       pragmatic choice (no webfont, ships with Office) but it is not the brand
       face, and GRIT already renders in Outfit — two internal apps in two
       different typefaces do not read as one product. Loaded from Google Fonts
       in index.html, which the guidelines themselves point to. */
    font-family: 'Outfit', system-ui, -apple-system, 'Segoe UI', sans-serif;
    background: var(--surface-page);
    color: var(--text-primary);
    -webkit-font-smoothing: antialiased;
}

/* Dark mode, adopted wholesale from GRIT rather than extended from navy.
   The old approach tinted everything with --ez-navy-700, which the brand
   guidelines don't sanction: navy-on-navy is listed as a pairing to avoid
   (p.26), and navy surfaces under a navy-inked wordmark is exactly that.
   GRIT's near-black surfaces sidestep it — text goes white, which IS an
   approved pairing, and the orange stays legible on both. */
body.new-ui.dark {
    --surface-page: #0d0d0d;
    --surface-card: #1a1a19;
    --text-primary: #ffffff;
    --text-secondary: #c3c2b7;
    --text-muted: #898781;
    --border: rgba(255, 255, 255, .1);
    --gridline: #2c2c2a;

    --brand-wash: rgba(251, 97, 47, .16);
    --brand-wash-strong: rgba(251, 97, 47, .24);
    --brand-ink: #ff8a5c;

    --status-pending: #5b9bf0;

    /* Dark mode needs the tint to lift off near-black, not sink into it. */
    --ez-gray-100: rgba(255, 255, 255, .08);

    background: var(--surface-page);
    color: var(--text-primary);
}

/* Form controls don't inherit font-family from the body by default in the
   UA stylesheet — the DS is "one typeface across everything". */
body.new-ui button,
body.new-ui input,
body.new-ui select,
body.new-ui textarea {
    font-family: inherit;
}

/* Brand type hierarchy (Brand Guidelines V2, p.5 + p.31): Outfit Extra Bold is
   the headline face, Medium is the subhead face, Regular is body copy. That's
   800/500/400 — the same three weights GRIT loads. Was 700 here, which is a
   weight the brand doesn't name; 700 is kept below only for UI chrome (table
   headers, total rows) that needs bold without display presence.

   The guidelines specify negative tracking at every level (-3 headline, -2
   subhead/body, in 1/1000 em) — a hair tighter than default, which is what the
   -0.01em below approximates. Not applied to body copy: at 14-16px the effect
   is invisible and it costs legibility in dense tables. */
body.new-ui h1,
body.new-ui h2,
body.new-ui h3,
body.new-ui .card-header h3 {
    font-weight: 800;
    letter-spacing: -0.01em;
    color: inherit;
}

/* DS voice rule: sentence case everywhere, never all-caps — the base UI
   leans on text-transform:uppercase for eyebrow/KPI labels; the DS gets
   the same emphasis from tracking + weight + color instead. */
body.new-ui .eyebrow,
body.new-ui .panel-label,
body.new-ui .tool-category,
body.new-ui .shortcut-meta,
body.new-ui .cc-kpi-label,
body.new-ui .cc-filter-field label {
    text-transform: none;
    letter-spacing: 0.04em;
}

/* Widget headings — "Announcements", "Pinned Favorites" and friends. The base UI
   renders these as 0.8rem uppercase at 70% opacity, which reads as a faint
   eyebrow above the content; GRIT renders the equivalent ("Grand total — all
   markets") as a solid sentence-case heading you can actually navigate by. Same
   change, and it also lands the brand's no-all-caps voice rule.

   Needs the extra specificity to beat `.widget .widget-body .card-header h3`
   (styles.css) — three classes plus an element, so the plain .card-header
   override above it never applied here. Table column headers keep their
   uppercase treatment; GRIT uses it there too. */
body.new-ui .widget .widget-body .card-header h3,
body.new-ui .widget .widget-body .hero-copy h2 {
    font-size: 0.95rem;
    font-weight: 700;
    opacity: 1;
    text-transform: none;
    letter-spacing: -0.01em;
}

/* ── Header ──
   No more orange hero / "Welcome back" banner — and no logo bar either:
   once the hero and the date/time came out, the header was a full-width
   row holding nothing but the logo. The DS shell is a single sticky bar,
   so applyNewUI() in script.js reparents the logo (and the date/time)
   into the nav row — real DOM moves, not just style swaps — and the
   header element itself is dropped. */
body.new-ui header {
    display: none;
}

/* Reset the absolute positioning it uses inside the header — in the nav
   it's a plain flex item at the head of the row. */
body.new-ui .top-nav .header-logo-group {
    position: static;
    transform: none;
    order: 0;
    flex-shrink: 0;
    margin-right: 4px;
}

/* The New UI shows the primary mark (dots + wordmark) in place of the classic
   UI's secondary mark, matching GRIT. */
body.new-ui .header-logo--classic {
    display: none;
}

body.new-ui .header-logo--light {
    display: block;
}

body.new-ui.dark .header-logo--light {
    display: none;
}

body.new-ui.dark .header-logo--dark {
    display: block;
}

/* 46px, not the 36px this used to be, and not a free choice: the primary logo
   has a hard "minimum width: 150px" in the guidelines (p.11) and its aspect
   ratio is 3.40:1, so anything under ~44px tall renders it below spec. 46px is
   the first round number that clears it (156px wide) — which is also why GRIT
   uses 46px. The cost is ~83px more of the nav row than the old secondary mark
   took, so the tabs stack at a slightly wider viewport than before;
   syncNavStacking() measures that rather than assuming it, so nothing needs
   retuning. */
body.new-ui .header-logo {
    height: 46px;
}

body.new-ui h1 {
    display: none;
}

/* Where GRIT puts its product-name lockup (logo │ "GRIT" / "Ground Restoration
   Inspection Tracker"), this puts the search field: the portal doesn't need to
   caption itself — users arrive from a bookmark and the tab title already says
   Ezee Links — and search is the most-used control in the bar, so it earns the
   most prominent slot.

   The hairline is GRIT's divider doing the same job: keeping the logo and what
   follows from reading as one wordmark. It lives on the logo group rather than
   on the search pill because that pill already carries a full 1px border of its
   own (styles.css .banner-search), so a border-left there would just thicken its
   own edge instead of drawing a separator. 28px rather than full height so it
   reads as a divider and not as a second border on the pill. */
body.new-ui .header-logo-group::after {
    content: '';
    width: 1px;
    height: 28px;
    margin-left: 12px;
    background: var(--border);
    flex: none;
}

/* Relocated date/time — compact horizontal readout next to the bell/gear
   icons instead of the big flush-right column it was in the header. */
body.new-ui .nav-left-controls .header-datetime {
    flex-direction: row;
    align-items: baseline;
    gap: 6px;
    color: var(--ez-navy);
    margin-right: 4px;
}

body.new-ui.dark .nav-left-controls .header-datetime {
    color: rgba(255, 255, 255, .85);
}

body.new-ui .nav-left-controls .header-date {
    font-size: .72rem;
}

body.new-ui .nav-left-controls .header-time {
    font-size: .82rem;
}

/* ── Top nav ──
   DS spec: "sticky top, rgba(255,255,255,0.8) background with
   backdrop-filter: blur(12px), 1px gray border-bottom, links 15px/500,
   hover color orange." Reordered via flex `order` (not absolute
   positioning) so the search flyout pushes the tab list over instead of
   covering it. */
/* Solid card surface with a hairline underneath, matching GRIT's .app-nav. This
   replaced a translucent white + backdrop-blur treatment: the blur was fine in
   light mode but its dark counterpart was a navy (rgba(17,26,40,.8)) that now
   clashes with the near-black dark surfaces, and a solid token-driven
   background needs no per-mode override at all. Sticky rather than GRIT's
   flex-column shell — the portal scrolls the document, not an inner pane, and
   changing that would touch every one of the 25 tool pages. */
body.new-ui .top-nav {
    position: sticky;
    top: 0;
    z-index: 40;
    background: var(--surface-card);
    border-bottom: 1px solid var(--border);
    box-shadow: none;
    padding: 0.3rem 1rem;
    display: flex;
    align-items: center;
    gap: 12px;
}

/* Always-open search pill. The base UI grows the box from 44px to 320px on
   click, which reflows every flex sibling after it — the tab list visibly
   slides right mid-animation. Here the box is a fixed-width field that's
   simply always there, so the row never moves. The `.expanded` class is
   ignored (it still gets toggled by bindSearch() for the base UI); the
   magnifier just focuses the input — see bindSearch() in script.js. */
body.new-ui .top-nav .banner-search {
    position: static;
    transform: none;
    order: 1;
    flex-shrink: 0;
    width: 260px;
    height: 40px;
    overflow: visible;
    /* 8px, not a pill — GRIT's inputs and tabs share one radius, and a pill
       here next to 8px tabs was the most visible mismatch between the two. */
    border-radius: var(--radius);
    background: var(--surface-page) !important;
    border: 1px solid var(--border);
    transition: box-shadow var(--dur-base) var(--ease-standard), border-color var(--dur-base) var(--ease-standard);
}

/* Now that the bar is a solid card surface, the field reads as recessed by
   sitting on the page color — one token, correct in both modes, so the dark
   override this used to need is gone. */
body.new-ui .top-nav .banner-search:focus-within {
    border-color: var(--brand);
    box-shadow: none;
}

body.new-ui .top-nav .banner-search .search-toggle {
    height: 40px;
}

/* Fills whatever the magnifier leaves, rather than the base UI's hardcoded
   260px — that number assumed a 320px box and overflows this one. */
body.new-ui .top-nav .banner-search .search-input.inline-search-input {
    flex: 1;
    width: auto;
    min-width: 0;
    opacity: 1;
    padding: 0 14px 0 4px;
    font-size: 0.9rem;
    transition: none;
}

/* The base rule pins the input's text to black !important for the white pill
   it assumed; the dark pill here needs it back. */
body.new-ui.dark .top-nav .banner-search .search-input.inline-search-input {
    color: rgba(255, 255, 255, .92) !important;
}

body.new-ui.dark .top-nav .banner-search .search-input.inline-search-input::placeholder {
    color: rgba(255, 255, 255, .5);
}

/* The list never wraps. It used to (`flex-wrap: wrap`) as a safety valve, but
   ragged wrapping was worse than the overflow it prevented: the tabs broke into a
   centered second row at laptop widths, and a wrapped list also mis-anchors every
   dropdown, since the panels hang off the <ul>'s bottom edge (see below).
   syncNavStacking() in script.js measures the row and adds .nav-collapsed when
   the tabs stop fitting, replacing them with a single All Tools link — so the list
   is either fully inline or gone, never split across two rows. */
/* position: relative is load-bearing for the mega-dropdowns, not cosmetic.
   They're absolutely positioned with top:100% and rely on this <ul> being
   their containing block: the list's bottom edge is the tab row's bottom
   edge, so the panel opens 0.25rem below the tab and the open toggle's
   ::before bridge covers exactly that gap. Making the list static hands the
   containing block to .top-nav, dropping the panel to the bottom of the
   whole sticky bar with unbridged dead space above it — the pointer leaves
   the <li> on the way down and mouseleave closes the menu before it can be
   used. */
body.new-ui .top-nav > ul {
    position: relative;
    order: 2;
    flex: 1;
    min-width: 0;
    flex-wrap: nowrap;
}

/* Tabs hold their natural width instead of being squeezed. This is what makes
   the measurement in syncNavStacking() stable: each li reports the same
   offsetWidth whether the list is inline or stacked, so the two states can't
   disagree about whether the tabs fit and flip-flop between each other. */
body.new-ui .top-nav > ul > li {
    flex-shrink: 0;
}

body.new-ui .top-nav .nav-left-controls {
    position: static;
    transform: none;
    order: 3;
    flex-shrink: 0;
}

body.new-ui .top-nav > ul > li + li {
    border-left: none;
}

/* Tabs match GRIT's .app-nav__item: 8px corners rather than full pills, 14px,
   and secondary-text color so the active tab is the only saturated thing in the
   row. Weight is 500 (brand Medium, the named UI/subhead weight) where GRIT
   hardcodes 600 — 600 isn't a weight the guidelines name, and it only renders
   at all because Outfit is variable. */
body.new-ui .dropdown-toggle,
body.new-ui .nav-collapsed-link,
body.new-ui .top-nav > ul > li > .page-link {
    color: var(--text-secondary);
    font-weight: 500;
    font-size: 0.875rem;
    border-radius: var(--radius);
    padding: 0.55rem 1rem;
    white-space: nowrap;
    transition: background-color var(--dur-base) var(--ease-standard), color var(--dur-base) var(--ease-standard);
}

/* Hover/dark-hover now fall out of the tokens — --brand-wash and --brand-ink are
   both redefined under body.new-ui.dark, so the separate dark override this used
   to carry is gone. */
body.new-ui .dropdown-toggle:hover,
body.new-ui .nav-collapsed-link:hover,
body.new-ui .top-nav > ul > li > .page-link:hover {
    background-color: var(--brand-wash);
    color: var(--brand-ink);
}

/* Active tab — GRIT's .app-nav__item--active, filled orange with white text.
   The portal had no active state at all: nothing in the bar told you which page
   you were on, because .active is only ever set on the .page element (see
   showPage() in script.js). markActiveNavTab() now mirrors it onto the tab that
   owns the current page, including the parent tab of a mega-dropdown item.

   White-on-orange is an explicitly approved brand pairing (Guidelines p.24-26),
   which is why the active state can be solid rather than a wash.

   Home is deliberately excluded. The fill means "the page you're on is in this
   tab" — it labels a group of pages, the way GRIT's tabs do. Home is a single
   icon link with no pages under it, so filling it reads as a pressed button
   rather than a location, and an orange block around a glyph is much louder than
   the same block around a word. */
body.new-ui .top-nav > ul > li > .dropdown-toggle.nav-current,
body.new-ui .top-nav > ul > li > .page-link.nav-current:not(.nav-home-btn) {
    background-color: var(--brand);
    color: #fff;
}

/* Hover must not wash out the active tab — without this the orange fill drops
   back to a 10% tint the moment the pointer crosses it. */
body.new-ui .top-nav > ul > li > .dropdown-toggle.nav-current:hover,
body.new-ui .top-nav > ul > li > .page-link.nav-current:not(.nav-home-btn):hover {
    background-color: var(--brand);
    color: #fff;
}

body.new-ui .dropdown-toggle.open {
    background-color: var(--ez-orange);
    color: #fff;
    border-radius: var(--radius) var(--radius) 0 0;
}

body.new-ui.dark .dropdown-toggle.open {
    background-color: var(--ez-orange);
    color: #fff;
}

body.new-ui .dropdown-toggle::after {
    opacity: .65;
}

/* Icon contrast — these were white-on-orange; the nav is a light/dark
   surface now, so they need a real foreground color. DS: nav links are
   navy at rest, orange on hover. */
body.new-ui .notification-bell-button,
body.new-ui .profile-icon-link,
body.new-ui .tz-nav-btn {
    color: var(--ez-navy);
}

body.new-ui.dark .notification-bell-button,
body.new-ui.dark .profile-icon-link,
body.new-ui.dark .tz-nav-btn {
    color: rgba(255, 255, 255, .85);
}

body.new-ui .notification-bell-button:hover,
body.new-ui .profile-icon-link:hover,
body.new-ui .tz-nav-btn:hover {
    color: var(--ez-orange);
    background-color: var(--ez-orange-50);
    border-radius: var(--radius-sm);
}

body.new-ui.dark .notification-bell-button:hover,
body.new-ui.dark .profile-icon-link:hover,
body.new-ui.dark .tz-nav-btn:hover {
    background-color: rgba(251, 97, 47, .16);
}

/* Avatar (initials or uploaded photo) replaces the gear icon — set up in
   Profile → Display Settings → Profile Photo. Orange fill matches the
   brand mark (the orange dot in the logo). */
body.new-ui .profile-icon-gear {
    display: none;
}

body.new-ui .profile-icon-avatar {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    border-radius: var(--radius-pill);
    background: var(--ez-orange);
    color: #fff;
    font-weight: 500;
    font-size: .78rem;
    overflow: hidden;
}

body.new-ui .profile-icon-avatar img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* ── Cards ──
   A hairline border on a card surface, no shadow — GRIT's card treatment, and
   the single biggest thing making the two apps look like one product. The base
   UI's `box-shadow: 0 12px 30px` (styles.css) has to be actively cancelled, not
   just left unset, or every card keeps floating.

   Both mode variants now come from tokens, so the dark override that hardcoded
   navy surfaces is gone — that navy is what made dark mode look unrelated to
   GRIT's near-black. */
body.new-ui .dashboard-card,
body.new-ui .today-panel,
body.new-ui .hero-copy {
    background: var(--surface-card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: none;
}

/* Widgets clip their children to their own corners (styles.css sets
   overflow: hidden with a 12px radius), so a 12px sleeve around an 8px card
   leaves a visible sliver of page color at each corner. */
body.new-ui .widget {
    border-radius: var(--radius);
}

body.new-ui .card-header h3 {
    font-weight: 700;
    letter-spacing: -0.01em;
}

/* ── Buttons ──
   DS: 500 weight, hover fades in 200ms, press = scale(0.98). The app's single
   quick-action-button class doesn't distinguish primary/secondary/ghost like the
   DS does — it's used as one general "ghost that fills orange on interaction"
   style everywhere, which is kept, just reshaped/retimed to match the DS's
   button mechanics.

   --radius, not --radius-pill, which is what this used to be. The token comment
   above already reserves the pill for badges and status chips, and a button is
   neither: a pill button next to an 8px input and an 8px tab is the same
   mismatch the search field had before it was squared off. */
body.new-ui .quick-action-button,
body.new-ui .urgent-toggle-label {
    border-radius: var(--radius);
    font-weight: 500;
    border-color: var(--ez-gray);
    transition: background-color var(--dur-base) var(--ease-standard), color var(--dur-base) var(--ease-standard), border-color var(--dur-base) var(--ease-standard), transform var(--dur-fast) var(--ease-standard);
}

body.new-ui.dark .quick-action-button,
body.new-ui.dark .urgent-toggle-label {
    border-color: rgba(255, 255, 255, .2);
}

body.new-ui .quick-action-button:hover {
    background: var(--ez-orange-600);
    border-color: var(--ez-orange-600);
}

body.new-ui .quick-action-button:active {
    transform: scale(0.98);
}

/* ── Toggle switches ── */
body.new-ui input:checked + .slider {
    background-color: var(--ez-orange);
}

/* ── Responsive ──
   One row has to hold the logo, the search pill, five or six tabs, the date/time
   and three icons — roughly 1200-1400px of content depending on which tabs the
   signed-in user can see, so it stops fitting well above phone widths.

   Two stages, both driven by measurement rather than a breakpoint (the content
   width depends on the user's permissions, not the viewport — see
   syncNavStacking() in script.js):

     .nav-stacked    tabs move to a full second row of their own
     .nav-collapsed  tabs are replaced by a single All Tools link

   Stacking comes first and handles most laptop widths, keeping every tab one
   click away for one row of chrome. Collapsing is reserved for when the tabs
   can't fit even a dedicated row, where the alternatives are a ragged wrapped
   list (which also mis-anchors the dropdowns) or horizontal scrolling —
   `overflow-x` on the list makes it a scroll container, and the mega-dropdowns
   are absolutely positioned off each `li`, so they'd be clipped to the height of
   the nav. */
body.new-ui .top-nav.nav-stacked {
    flex-wrap: wrap;
    row-gap: 2px;
}

/* Full-width basis forces the break; the icons pull right to close out the first
   row now that the tabs no longer sit between them. Unlike the earlier version of
   this rule the list does NOT get `flex-wrap: wrap` as a safety valve — a list too
   wide for its own row is exactly the case .nav-collapsed now handles, and letting
   it wrap here would pre-empt that and reintroduce the ragged two-row split. */
body.new-ui .top-nav.nav-stacked > ul {
    order: 4;
    flex: 1 0 100%;
    justify-content: flex-start;
    flex-wrap: nowrap;
}

body.new-ui .top-nav.nav-stacked .nav-left-controls {
    margin-left: auto;
}

body.new-ui .top-nav.nav-collapsed > ul > li:not(.nav-tab-home) {
    display: none;
}

/* Hidden until the tabs give up their spot, and it takes their place in the row
   rather than appending after the icon cluster. */
body.new-ui .nav-collapsed-link {
    display: none;
}

body.new-ui .top-nav.nav-collapsed .nav-collapsed-link {
    display: flex;
    order: 2;
    flex: none;
    margin-right: auto;
    /* The base .page-link is built for the tool grid, where it's a full-width
       bordered tile (styles.css: width 100%, 2px white border). Unset both or it
       claims the entire row as a flex item and shoves the logo and search off the
       left edge — which is exactly what it did. */
    width: auto;
    min-height: 0;
    border: none;
}

/* The tab list still owns `flex: 1` and would otherwise keep the whole middle of
   the row reserved for a single home icon, pushing All Tools against the
   date/time. */
body.new-ui .top-nav.nav-collapsed > ul {
    flex: none;
}

@media (max-width: 720px) {
    /* The full logo's 150px minimum width doesn't fit a phone row, so it gives
       way to the app-tile mark. Not the secondary wordmark, which would have been
       the obvious pick: that file is solid black and disappears against the dark
       surface, and there's no white version of it in Images/. */
    body.new-ui .header-logo--light,
    body.new-ui.dark .header-logo--dark {
        display: none;
    }

    body.new-ui .header-logo--mark {
        display: block;
        height: 30px;
        border-radius: 6px;
    }

    body.new-ui .header-logo-group::after {
        margin-left: 8px;
        height: 24px;
    }

    body.new-ui .top-nav {
        padding: 0.3rem 0.5rem;
        gap: 6px;
    }

    body.new-ui .dropdown-toggle,
    body.new-ui .nav-collapsed-link,
    body.new-ui .top-nav > ul > li > .page-link {
        padding: 0.45rem 0.6rem;
        font-size: 0.82rem;
    }

    /* The tabs are always collapsed at this width, so the search pill takes the
       slack between the logo and the icons instead of holding a fixed 260px. */
    body.new-ui .top-nav.nav-collapsed .banner-search {
        flex: 1;
        width: auto;
        min-width: 0;
    }

    body.new-ui .nav-left-controls .header-date {
        display: none;
    }
}

/* ============================================================
   Home page
   ------------------------------------------------------------
   Everything above is the shell — nav, logo, search, tabs, card
   surfaces. This is the page body, and it is scoped to
   `#home-page` rather than applied portal-wide on purpose: the
   base rules it overrides (.dashboard-card padding,
   .shortcut-item, .todo-item, .main-content padding) are shared
   with all 25 tool pages, and retuning those blind is how you
   break a table you never opened. Each tool page gets its own
   pass; this one is the template for them.

   Three habits of the base UI are what made the redesigned
   shell look bolted onto an older page, and all three are
   replaced here:

     1. Orange washes as surfaces. rgba(--primary-rgb, .03-.12)
        is the base UI's fill for cards, tiles, inputs, buttons,
        list rows and clock cells alike, so the whole page is
        faintly orange and nothing is emphasised. Here orange
        means action or the single headline number in a card;
        surfaces are --surface-card / --surface-page with a
        --border hairline, as in GRIT.
     2. Weight and caps as emphasis — labels at 700/800
        uppercase on 0.6rem type. Replaced by the DS hierarchy
        already used in the nav: 500 weight, muted color,
        sentence case, and size doing the work.
     3. A radius per component. One card could hold 6px, 7px,
        10px and 12px corners at once. All --radius now.

   The user's custom accent (--primary) is deliberately left
   alone rather than pinned to brand orange: it's a real setting
   in Profile, and the rules below reach the same consistency by
   using brand tokens where the base UI used --primary for
   surfaces, so there is nothing left for a custom accent to
   tint badly.
   ============================================================ */

/* Markup that only the New UI renders — currently the Recent Articles card,
   which is not in the classic UI's widget config or its Customize Home list, so
   this class is the whole of what keeps it off that page.

   The topbar wrapper below collapses out of the classic layout entirely, so the
   widget bar stays a direct flex child of .tools-area exactly as it was. */
body:not(.new-ui) .new-ui-only {
    display: none !important;
}

body:not(.new-ui) .home-page-topbar {
    display: contents;
}

/* ── Layout: 601px and up ──
   Everything in this block is layout — page frame, header row, widget grid,
   tile geometry. Below 601px it all belongs to responsive.css, which makes
   genuinely different and correct choices for a phone.

   601px, not responsive.css's own 900px line, because that line is width-only
   and a portrait desktop monitor is ~750px wide: it has a mouse, hover, drag,
   and far more vertical room than a phone, but it lands on the phone side of a
   900px breakpoint and gets tight gutters, one column, and a swipe strip built
   for a thumb. So the band from 601-900px explicitly takes the layout back —
   the resets marked "601-900" below are the specific phone-only decisions that
   are wrong on a rotated monitor, restated because these selectors carry an id
   and win regardless of sheet order.

   Below 601px nothing here applies and responsive.css's phone pass stands
   whole. What stays its call at every width: widget mode itself, which
   script.js disables under 900px (widgetModeTooNarrow) because its canvas is a
   fixed 6x200px grid driven by mouse-only drag events — so the Add/Edit Widget
   buttons stay hidden here too, and only Customize survives.

   Skin — color, border, radius, type, hover — is NOT gated and sits outside
   this block. responsive.css only ever changes layout, so there is nothing to
   collide with, and a phone should get the redesigned surfaces too. */
@media (min-width: 601px) {

    /* The base UI pads .main-content 1rem and .tools-area 1rem again, and sets no
       max-width anywhere, so cards run the full width of the display with a
       doubled gutter. One gutter here, and a width that tracks the viewport
       between two limits instead of stopping dead.

       A flat max-width is what made this feel stuck: from 1280px up, every extra
       pixel of monitor became margin, so a 2560px display showed the same column
       as a 1300px one. `min(95vw, 2280px)` keeps a gutter either side as the window
       grows and still refuses to run edge to edge — full-bleed text has no margin
       to rest against.

       The cap was 1760px and that was too mean. On a wide monitor it left the
       content sitting in the middle half of the screen with everything past it
       empty, which reads as a bug rather than as a measure. 2280px is chosen with
       the grid below in mind, not on its own: the tracks auto-fill at a 360px
       minimum, so this width resolves to six of them, and the cards stay a
       readable size instead of stretching. Raising it further would mean either
       wider cards or thinner columns, and both are worse than a gutter.

       No lower bound is needed: max-width only ever caps, so below ~2400px this
       resolves to 95vw and below that the padding alone carries the gutter. */
    body.new-ui #home-page .main-content {
        padding: 0;
    }

    body.new-ui #home-page .tools-area {
        width: 100%;
        max-width: min(95vw, 2280px);
        margin: 0 auto;
        padding: 28px clamp(20px, 2.2vw, 44px) 56px;
    }

    /* Nothing in the topbar belongs to the New UI any more. The row held a
       greeting and the widget bar; the greeting is gone from the markup and the
       bar drives widget mode, which is classic-only — so the whole row goes and
       the page opens on its first card. Hidden rather than deleted because the
       classic UI still renders and uses all of it. */
    body.new-ui #home-page .home-page-topbar {
        display: none;
    }

    /* ── Widget grid ──
       The base grid is two columns with widgets pinned to named cells — world
       clock always column 1, TZ tracker always column 2, and so on — so turning
       one widget off leaves a hole where it was. Auto-fill packs whatever the
       user actually enabled, and `align-items: start` stops a short card being
       stretched to match a tall neighbour, which is what made the rows look
       ragged.

       Announcements and Favourites keep the full width: one is prose that wants
       a reading measure, the other is a tile grid that wants as many columns as
       it can get. `1 / -1` spans the generated tracks, so unlike `span 2` it
       can't force a second column into existence at narrow widths. */
    body.new-ui #home-page .widget-grid:not(.widget-active) {
        grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
        gap: 16px;
        align-items: start;
    }

    body.new-ui #home-page .widget-grid:not(.widget-active) .widget {
        grid-column: auto;
    }

    body.new-ui #home-page .widget-grid:not(.widget-active) #widget-announcements,
    body.new-ui #home-page .widget-grid:not(.widget-active) #widget-favorites {
        grid-column: 1 / -1;
    }

    /* ── Favourite tiles ──
       Was a 200px-minimum square with a 215px floor, a translucent white fill, a
       72px logo and a bold label over an uppercase category — so six pinned
       tools filled the viewport and shouted louder than the announcements above
       them. Roughly half the footprint now, on the same hairline-on-page-surface
       tile the rest of the DS uses, and the tile itself carries the hover rather
       than the text changing color.

       Geometry only below 901px is responsive.css's swipe strip; the tile
       surface is re-skinned for it further down. */
    /* Every selector below is qualified :not(.widget-active). Widget mode is a
       fixed 6x200px canvas whose favourite tiles are sized from JS
       (updateFavoritesLayout) against rules in styles.css scoped to
       .widget-active — 48px icons, 0.72rem labels, an 18px pin. These carry an
       id, so unqualified they outrank all of that and the canvas lays itself
       out against tile sizes that are not what it measured. */
    /* 601-900: unwind the swipe strip. A horizontal scroller is the right call
       for a thumb and the wrong one for a mouse — there is nothing to swipe, so
       reaching the last tile means dragging a 4px scrollbar. `grid-auto-flow`
       and `grid-auto-columns` have to be named explicitly: a template alone
       still wraps into column flow, which is what pushed the grid wider than
       its own card. */
    body.new-ui #home-page .widget-grid:not(.widget-active) .favorites-scroll-wrapper {
        overflow-x: visible;
        overflow-y: visible;
        scroll-snap-type: none;
        padding-bottom: 0;
    }

    body.new-ui #home-page .widget-grid:not(.widget-active) #favorites-list.shortcut-list {
        grid-auto-flow: row;
        grid-auto-columns: auto;
        grid-template-columns: repeat(auto-fill, minmax(172px, 1fr));
        gap: clamp(12px, 0.9vw, 16px);
    }

    body.new-ui #home-page .widget-grid:not(.widget-active) #favorites-list .shortcut-item {
        width: auto;
        aspect-ratio: auto;
        min-height: clamp(116px, 9vw, 148px);
        padding: clamp(16px, 1.2vw, 22px) 12px clamp(14px, 1.1vw, 20px);
        gap: 10px;
        scroll-snap-align: none;
    }

    body.new-ui #home-page .widget-grid:not(.widget-active) #favorites-list .shortcut-primary {
        gap: 8px;
        min-height: 0;
        justify-content: center;
    }

    body.new-ui #home-page .widget-grid:not(.widget-active) #favorites-list .shortcut-item-icon {
        height: clamp(40px, 3.2vw, 56px);
        max-height: clamp(40px, 3.2vw, 56px);
        max-width: 100%;
        margin-bottom: 0;
    }

    body.new-ui #home-page .widget-grid:not(.widget-active) #favorites-list .shortcut-link {
        font-size: clamp(0.8125rem, 0.72vw, 0.9375rem);
        line-height: 1.3;
        min-height: 0;
        align-items: center;
    }

    /* The category caption, which was uppercase 700 at the same size as the tool
       name's small print. Two lines at most: a tile that grows a third row
       because one tool has a long category breaks the grid's rhythm. */
    /* Also the reset for responsive.css's `display: none` at 601-900 — the
       category earns its line back once the tile is not a 132px square. */
    body.new-ui #home-page .widget-grid:not(.widget-active) #favorites-list .shortcut-meta {
        margin: 0;
        min-height: 0;
        max-width: 100%;
        display: -webkit-box;
        -webkit-line-clamp: 2;
        -webkit-box-orient: vertical;
        overflow: hidden;
    }

    /* Unpin, on a grid where everything is pinned by definition — so the base
       `.pinned` state painting it solid orange put an orange dot on every tile.
       It waits for hover or focus instead, and only colors on its own hover. */
    body.new-ui #home-page .widget-grid:not(.widget-active) #favorites-list .pin-button,
    body.new-ui #home-page .widget-grid:not(.widget-active) #favorites-list .pin-button.pinned {
        top: 6px;
        right: 6px;
        width: 26px;
        height: 26px;
        padding: 0;
        opacity: 0;
        transition: opacity var(--dur-fast) var(--ease-standard), background-color var(--dur-base) var(--ease-standard), color var(--dur-base) var(--ease-standard);
    }

    body.new-ui #home-page .widget-grid:not(.widget-active) #favorites-list .shortcut-item:hover .pin-button,
    body.new-ui #home-page .widget-grid:not(.widget-active) #favorites-list .pin-button:focus-visible {
        opacity: 1;
    }

    body.new-ui #home-page .widget-grid:not(.widget-active) #favorites-list .pin-button svg {
        width: 14px;
        height: 14px;
    }
}

/* ── Home dashboard columns ──
   Above 601px the block before this packs every card into `auto-fill` tracks of
   at least 300px, with Announcements and Favourites spanning the full row. That
   is right up to about 1000px and wrong past it: the two full-width cards become
   a tall stack of wide, mostly-empty prose.

   From 1040px the cards column up. The track count is not hard-coded — it
   auto-fills at a 430px minimum, so the layout gets two columns at ~1000px and
   four or five at the 2280px cap, and any monitor in between gets whatever it has
   room for.

   That minimum was 360px, which gave five or six narrow columns on a wide screen
   and a page three short rows deep. Wider tracks mean fewer of them, so the same
   cards stack rather than spread — the content is the same height either way, and
   this is which direction it runs. This replaced two fixed tiers (two tracks at 1040, three at 1560) that
   stopped adding columns past 1560px, so a very wide screen kept a three-column
   block with a third of the width empty beside it.

   Favourites spans two tracks because it is a tile grid: width there turns
   directly into more tools visible without scrolling, and one 360px track only
   fits two tiles. Everything else takes a single track. Announcements especially
   wants one — it is prose, and a 400px measure reads better than a 1200px one.

   `dense` matters because these cards differ a lot in height and any of them
   except Announcements can be absent: it lets a later short card backfill a gap
   instead of leaving a hole with everything below shifted down.

   Known limit, and the reason for `align-items: start` above: grid rows are rows.
   Cards in the same row that differ in height leave whitespace beneath the
   shorter ones until the next row starts. `dense` reduces it and cannot remove
   it — only a masonry layout would, and `grid-template-rows: masonry` is not
   shipped widely enough to rely on. Stretching them to match would just move the
   empty space inside the shorter card. */
@media (min-width: 1040px) {
    body.new-ui #home-page .widget-grid:not(.widget-active) {
        grid-template-columns: repeat(auto-fill, minmax(430px, 1fr));
        grid-auto-flow: row dense;
    }

    body.new-ui #home-page .widget-grid:not(.widget-active) #widget-favorites {
        grid-column: span 2;
    }

    /* Undoes the full-row span the 601px block gives it. Without this it would
       still take the whole row and the columns would only ever have one card in
       the first row. */
    body.new-ui #home-page .widget-grid:not(.widget-active) #widget-announcements {
        grid-column: auto;
    }
}

/* A tablet can be wider than 601px with no mouse, and the rule above hides the
   unpin button until hover — unreachable there, so a pinned tool could never be
   unpinned. responsive.css carries the same guard for widget mode and notes that
   the tiles outside it are always visible; that stopped being true above, so the
   guard is extended here rather than left stale. */
@media (hover: none) and (pointer: coarse) {
    body.new-ui #home-page #favorites-list .pin-button,
    body.new-ui #home-page #favorites-list .pin-button.pinned {
        opacity: 1;
    }
}

/* The page heading type and the widget-bar button skin used to live here. The
   heading markup is gone and the widget bar is classic-only, where styles.css
   already styles it — so both sets of rules went with the topbar above. */

/* ── Cards ──
   Surface, border and radius come from the shell rules above; this is the inside
   of the card. The margin-top is the one that made Announcements sit 1.5rem
   below every card beside it — .hero-copy carries it twice in styles.css, once
   in the general rule and once in the widget-grid rule. */
/* Fluid rather than fixed. 18/20px was set when a card was ~400px wide; in a
   dashboard column that can be 500px+ it reads as a big empty box with the
   content pressed into the corner. clamp() scales it with the viewport and keeps
   the phone value as the floor, so this is one rule instead of a breakpoint. */
body.new-ui #home-page .dashboard-card,
body.new-ui #home-page .hero-copy {
    padding: clamp(18px, 1.35vw, 26px) clamp(20px, 1.5vw, 30px) clamp(20px, 1.5vw, 30px);
    margin-top: 0;
}

/* Card titles get a rule under them, the way GRIT's section headers do — it's
   what lets a column of cards read as a list of sections rather than a stack of
   boxes with bold text at the top. */
body.new-ui #home-page .widget .widget-body .card-header {
    margin: 0 0 clamp(14px, 1.1vw, 20px);
    padding-bottom: clamp(12px, 0.9vw, 16px);
    border-bottom: 1px solid var(--gridline);
}

/* Card titles. The base h3 is sized for the classic UI's narrower cards and
   looked like a label rather than a heading once the cards grew. */
body.new-ui #home-page .widget .widget-body .card-header h3,
body.new-ui #home-page .hero-copy h2 {
    font-size: clamp(0.9375rem, 0.85vw, 1.125rem);
    font-weight: 700;
    letter-spacing: -0.01em;
}

/* Announcements has no .card-header — its h2 is a bare heading with an inline
   `display: inline-block` (it's the click target for the history view), so the
   rule goes on the list below it instead of under the heading. Same line, and it
   doesn't fight an inline style. */
body.new-ui #home-page .hero-copy h2 {
    margin: 0 0 12px;
}

body.new-ui #home-page .hero-announcements {
    margin: 0;
    padding-top: 14px;
    border-top: 1px solid var(--gridline);
    gap: 10px;
}

body.new-ui #home-page .empty-state {
    color: var(--text-muted);
    font-size: 0.875rem;
    padding: 18px 0;
}

/* ── Announcements ──
   The base treatment is an orange 8% wash with a 4px orange left rule, and the
   two variants go further: .urgent is a 25% red fill with dark-red bold text,
   .it-update a blue fill with blue text. On a page of hairline cards that reads
   as a browser alert, not a notice board. Same information here from the left
   rule alone, in the status colors the DS already defines for exactly this. */
body.new-ui #home-page .announcement-item {
    background: var(--surface-page);
    border: 1px solid var(--border);
    border-left: 3px solid var(--brand);
    border-radius: var(--radius);
    font-weight: 400;
    color: inherit;
}

body.new-ui #home-page .announcement-item.urgent {
    background: var(--surface-page);
    border-color: var(--border);
    border-left-color: var(--status-critical);
    color: inherit;
    font-weight: 400;
}

body.new-ui #home-page .announcement-item.it-update {
    background: var(--surface-page);
    border-color: var(--border);
    border-left-color: var(--status-pending);
    color: inherit;
    font-weight: 400;
}

body.new-ui #home-page .announcement-content {
    padding: 14px 16px;
    gap: 6px;
}

body.new-ui #home-page .announcement-date {
    font-size: 0.75rem;
    color: var(--text-muted);
    opacity: 1;
}

body.new-ui #home-page .announcement-footer {
    padding: 0 16px 12px;
}

/* Reaction pills. These are genuine pills, so the radius stays — but the "mine"
   state was an orange fill on orange text, which made every announcement you'd
   already reacted to look like it had an action button on it. Brand wash and
   brand ink is the same signal in the DS's own selected-state language. */
body.new-ui #home-page .reaction-pill {
    border-radius: var(--radius-pill);
    border-color: var(--border);
    background: transparent;
    font-size: 0.8125rem;
}

body.new-ui #home-page .reaction-pill:hover {
    background: var(--brand-wash);
    border-color: var(--brand);
}

body.new-ui #home-page .reaction-pill.mine {
    background: var(--brand-wash);
    border-color: var(--brand);
    color: var(--brand-ink);
    font-weight: 500;
}

body.new-ui #home-page .reaction-add-btn {
    width: 24px;
    height: 24px;
    border-radius: var(--radius-pill);
    /* Solid, not dashed — the dashed ring was the only dashed border on the page
       outside widget edit mode, where dashed means "drop something here". */
    border: 1px solid var(--border);
    color: var(--text-muted);
}

body.new-ui #home-page .reaction-add-btn:hover {
    border-color: var(--brand);
    background: var(--brand-wash);
    color: var(--brand-ink);
}

body.new-ui #home-page .reaction-picker {
    background: var(--surface-card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-md);
}

body.new-ui #home-page .reaction-emoji-option {
    border-radius: var(--radius);
}

body.new-ui #home-page .announcement-history-box {
    background: var(--surface-page);
    border-top: 1px solid var(--gridline);
    border-radius: var(--radius);
    font-size: 0.8125rem;
}

/* ── Buttons in cards ──
   The shared button is 44px tall at 1rem/600 — sized for the tool pages, where
   it's a page-level action. Inside a dashboard card it's the largest thing in
   the card. 36px at 0.875rem/500 lines up with the nav tabs instead. */
body.new-ui #home-page .quick-action-button {
    height: 36px;
    padding: 0 0.9rem;
    font-size: 0.8125rem;
    background: var(--surface-card);
    color: var(--text-secondary);
    border-color: var(--border);
}

body.new-ui #home-page .quick-action-button:hover,
body.new-ui #home-page .quick-action-button:focus {
    background: var(--brand);
    border-color: var(--brand);
    color: #fff;
}

body.new-ui #home-page .quick-action-button.small {
    height: 30px;
    padding: 0 0.75rem;
    font-size: 0.75rem;
}

/* ── Favourite tiles: surface ──
   Skin only; the geometry is in the desktop block above, because ≤900px the
   tiles belong to responsive.css's swipe strip. A hairline tile on the page
   surface, replacing a translucent-white fill that had no visible edge against
   a white card at all — and the tile carries the hover, rather than the label
   changing color under a static box. */
body.new-ui #home-page #favorites-list .shortcut-item {
    background: var(--surface-page);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    transition: background-color var(--dur-base) var(--ease-standard), border-color var(--dur-base) var(--ease-standard);
}

body.new-ui #home-page #favorites-list .shortcut-item:hover {
    background: var(--brand-wash);
    border-color: var(--brand);
}

body.new-ui #home-page #favorites-list .shortcut-link {
    font-weight: 500;
}

/* The category caption, which was uppercase 700 at the same size as the tool
   name's small print. */
body.new-ui #home-page #favorites-list .shortcut-meta {
    font-size: 0.6875rem;
    font-weight: 400;
    letter-spacing: 0;
    color: var(--text-muted);
    text-align: center;
}

body.new-ui #home-page #favorites-list .pin-button,
body.new-ui #home-page #favorites-list .pin-button.pinned {
    border-radius: var(--radius-pill);
    border: 1px solid var(--border);
    background: var(--surface-card);
    color: var(--text-muted);
}

body.new-ui #home-page #favorites-list .pin-button:hover {
    background: var(--brand-wash);
    border-color: var(--brand);
    color: var(--brand-ink);
}

/* ── Inputs ──
   Recessed on the page surface with a hairline, matching the nav's search
   field, rather than an orange-tinted box. The nav's TZ panel input is the same
   control in the shell, so it's included here rather than left as the one
   orange field in an otherwise finished bar. */
body.new-ui #home-page .tz-zip-input,
body.new-ui #home-page .todo-input,
body.new-ui #home-page .todo-edit-input,
body.new-ui .nav-panel .tz-zip-input {
    padding: 8px 10px;
    border-radius: var(--radius);
    border: 1px solid var(--border);
    background: var(--surface-page);
    font-size: 0.875rem;
    transition: border-color var(--dur-base) var(--ease-standard);
}

body.new-ui #home-page .tz-zip-input:focus,
body.new-ui #home-page .todo-input:focus,
body.new-ui #home-page .todo-edit-input:focus,
body.new-ui .nav-panel .tz-zip-input:focus {
    border-color: var(--brand);
}

body.new-ui #home-page .tz-check-btn,
body.new-ui #home-page .todo-add-btn,
body.new-ui .nav-panel .tz-check-btn {
    padding: 8px 14px;
    border-radius: var(--radius);
    background: var(--brand);
    font-size: 0.8125rem;
    font-weight: 500;
    transition: background-color var(--dur-base) var(--ease-standard);
}

body.new-ui #home-page .tz-check-btn:hover,
body.new-ui #home-page .todo-add-btn:hover,
body.new-ui .nav-panel .tz-check-btn:hover {
    background: var(--brand-ink);
}

/* ── List rows (to-do, recent tools) ──
   One row treatment for both: transparent until hovered, hairline border, brand
   wash on hover. They were two different orange tints at two different radii. */
body.new-ui #home-page .todo-item,
body.new-ui #home-page .recent-tool-btn {
    border-radius: var(--radius);
    border: 1px solid var(--border);
    background: transparent;
    transition: background-color var(--dur-base) var(--ease-standard), border-color var(--dur-base) var(--ease-standard), color var(--dur-base) var(--ease-standard);
}

body.new-ui #home-page .todo-item:hover,
body.new-ui #home-page .recent-tool-btn:hover {
    background: var(--brand-wash);
    border-color: var(--brand);
}

body.new-ui #home-page .todo-item {
    padding: 8px 10px;
    min-height: 38px;
}

body.new-ui #home-page .todo-text {
    font-size: 0.875rem;
}

body.new-ui #home-page .todo-completed .todo-text {
    color: var(--status-closed);
}

body.new-ui #home-page .recent-tool-btn {
    padding: 9px 12px;
    font-size: 0.875rem;
    font-weight: 400;
    color: var(--text-secondary);
}

body.new-ui #home-page .recent-tool-btn:hover {
    color: var(--brand-ink);
}

body.new-ui #home-page .recent-tools-list {
    gap: 8px;
    justify-content: flex-start;
}

body.new-ui #home-page .todo-empty,
body.new-ui #home-page .recent-tools-empty {
    font-size: 0.875rem;
    color: var(--text-muted);
}

body.new-ui #home-page .todo-action-btn {
    border-radius: var(--radius);
    background: transparent;
    color: var(--text-muted);
}

body.new-ui #home-page .todo-complete-btn:hover {
    background: rgba(12, 163, 12, .12);
    color: var(--status-closed);
}

body.new-ui #home-page .todo-edit-btn:hover {
    background: var(--brand-wash);
    color: var(--brand-ink);
}

body.new-ui #home-page .todo-delete-btn:hover {
    background: rgba(208, 59, 59, .12);
    color: var(--status-critical);
}

/* Scrollbars: the base UI tints them with the accent, which reads as a decorated
   scrollbar. A border-colored thumb disappears until it's needed. */
body.new-ui #home-page .todo-list,
body.new-ui #home-page .favorites-scroll-wrapper {
    scrollbar-color: var(--border) transparent;
}

body.new-ui #home-page .todo-list::-webkit-scrollbar-thumb,
body.new-ui #home-page .favorites-scroll-wrapper::-webkit-scrollbar-thumb {
    background: var(--gridline);
}

/* ── World clock ──
   Six cells, each previously an orange-tinted box with an orange 800-weight
   abbreviation, and the user's own zone marked with a 2px orange border plus an
   orange glow ring. The cells go neutral so the time is the loudest thing in
   them, and the user's zone is marked by a brand wash and a 1px brand border —
   no ring, which was the only glow left anywhere in the New UI. */
body.new-ui #home-page .world-clock-grid {
    gap: 8px;
}

body.new-ui #home-page .clock-cell {
    background: var(--surface-page);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 10px 8px;
    gap: 4px;
}

body.new-ui #home-page .clock-cell.user-zone {
    border: 1px solid var(--brand);
    background: var(--brand-wash);
    box-shadow: none;
}

body.new-ui #home-page .clock-abbr {
    font-size: 0.6875rem;
    font-weight: 700;
    color: var(--text-secondary);
    letter-spacing: 0.04em;
}

body.new-ui #home-page .clock-cell.user-zone .clock-abbr {
    color: var(--brand-ink);
}

body.new-ui #home-page .clock-label {
    font-size: 0.625rem;
    color: var(--text-muted);
}

/* 0.9375rem was sized for a full-width widget; with seconds shown, "12:23:27 PM"
   at that size does not clear the 120px track the auto-fit grid uses in a
   dashboard column, and the base rule sets `white-space: nowrap`. */
body.new-ui #home-page .clock-time {
    font-size: clamp(0.875rem, 0.8vw, 1.0625rem);
    font-weight: 700;
    color: var(--text-primary);
}

body.new-ui #home-page .clock-date {
    font-size: 0.5625rem;
    color: var(--text-muted);
}

body.new-ui #home-page .clock-view-toggle {
    width: 28px;
    height: 28px;
    border-radius: var(--radius);
    border: 1px solid var(--border);
    background: transparent;
    color: var(--text-muted);
    transition: background-color var(--dur-base) var(--ease-standard), color var(--dur-base) var(--ease-standard), border-color var(--dur-base) var(--ease-standard);
}

body.new-ui #home-page .clock-view-toggle:hover {
    background: var(--brand-wash);
    border-color: var(--brand);
    color: var(--brand-ink);
}

/* ── Time zone tracker ──
   The looked-up time is the answer, so it's the one thing at display size; the
   city was orange and the time neutral, which had it backwards. */
body.new-ui #home-page .tz-result {
    font-size: 0.875rem;
    color: var(--text-secondary);
    gap: 4px;
}

body.new-ui #home-page .tz-result-city {
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--text-secondary);
}

body.new-ui #home-page .tz-result-time {
    font-size: 1.5rem;
    font-weight: 800;
    color: var(--text-primary);
    letter-spacing: -0.02em;
}

body.new-ui #home-page .tz-result-date {
    font-size: 0.75rem;
    color: var(--text-muted);
}

/* ── Shift countdown ──
   The one card where orange on the number is right: it's a single live value and
   nothing competes with it. */
body.new-ui #home-page .widget-shift-cd-card {
    padding: 18px 20px 20px;
}

body.new-ui #home-page .widget-shift-cd-card .card-header {
    padding: 0 0 12px;
    margin-bottom: 14px;
    border-bottom: 1px solid var(--gridline);
}

body.new-ui #home-page .widget-shift-cd-label {
    font-size: 0.75rem;
    font-weight: 500;
    text-transform: none;
    letter-spacing: 0.04em;
    color: var(--text-muted);
}

body.new-ui #home-page .widget-shift-cd-value {
    font-size: 1.75rem;
    color: var(--brand);
}

body.new-ui #home-page .widget-shift-today {
    border-bottom: 1px solid var(--gridline);
    padding-bottom: 10px;
}

body.new-ui #home-page .widget-shift-today-time {
    font-size: 0.9375rem;
    color: var(--text-primary);
}

body.new-ui #home-page .widget-shift-today-break {
    font-size: 0.6875rem;
    color: var(--text-muted);
}

body.new-ui #home-page .widget-shift-today-off {
    font-size: 0.75rem;
    font-style: normal;
    color: var(--text-muted);
}

/* ── Display settings widget ──
   The S/M/L/XL row becomes a segmented control: hairline buttons, brand fill on
   the active one. Same mechanics as the nav's active tab, so the page has one
   way of showing "this is the current choice". */
body.new-ui #home-page .widget-ds-label {
    font-size: 0.8125rem;
    font-weight: 500;
    color: var(--text-secondary);
}

body.new-ui #home-page .widget-ds-divider {
    background: var(--gridline);
}

body.new-ui #home-page .widget-ds-sizes {
    gap: 6px;
}

body.new-ui #home-page .widget-ds-size-btn {
    padding: 7px 4px;
    border-radius: var(--radius);
    border: 1px solid var(--border);
    background: transparent;
    color: var(--text-secondary);
    font-size: 0.8125rem;
    font-weight: 500;
    transition: background-color var(--dur-base) var(--ease-standard), color var(--dur-base) var(--ease-standard), border-color var(--dur-base) var(--ease-standard);
}

body.new-ui #home-page .widget-ds-size-btn:hover {
    background: var(--brand-wash);
    border-color: var(--brand);
    color: var(--brand-ink);
}

body.new-ui #home-page .widget-ds-size-btn.active {
    background: var(--brand);
    border-color: var(--brand);
    color: #fff;
}

/* ── Side-tab toggles ── */
body.new-ui #home-page .widget-sidetab-slider {
    background: var(--gridline);
    border-color: var(--border);
}

body.new-ui #home-page .widget-sidetab-slider::after {
    background: var(--text-muted);
}

body.new-ui #home-page .widget-sidetab-checkbox:checked + .widget-sidetab-slider {
    background: var(--brand-wash-strong);
    border-color: var(--brand);
}

body.new-ui #home-page .widget-sidetab-checkbox:checked + .widget-sidetab-slider::after {
    background: var(--brand);
}

body.new-ui #home-page .widget-sidetab-label {
    font-size: 0.75rem;
    font-weight: 500;
    color: var(--text-muted);
}

/* ── Modals ──
   Customize Home and Add Widget. Both are card surfaces that float, so they keep
   a shadow where the page's own cards don't — --shadow-lg is in the token set
   for exactly this. */
body.new-ui #home-page .home-customize-modal,
body.new-ui #home-page .widget-picker-modal {
    background: var(--surface-card);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-lg);
}

body.new-ui #home-page .home-customize-header,
body.new-ui #home-page .widget-picker-header {
    border-bottom: 1px solid var(--gridline);
}

body.new-ui #home-page .widget-picker-header {
    padding-bottom: 14px;
    margin-bottom: 14px;
}

body.new-ui #home-page .home-customize-desc {
    font-size: 0.8125rem;
    color: var(--text-muted);
}

body.new-ui #home-page .home-customize-row,
body.new-ui #home-page .widget-picker-item {
    border-radius: var(--radius);
}

body.new-ui #home-page .home-customize-row:hover {
    background: var(--brand-wash);
}

body.new-ui #home-page .widget-picker-item {
    border: 1px solid var(--border);
}

body.new-ui #home-page .home-customize-label,
body.new-ui #home-page .widget-picker-item-label {
    font-size: 0.875rem;
    font-weight: 500;
}

body.new-ui #home-page .widget-active-badge {
    background: var(--brand-wash-strong);
    color: var(--brand-ink);
    border-radius: var(--radius-pill);
    font-size: 0.6875rem;
    font-weight: 500;
    letter-spacing: 0.02em;
}

body.new-ui #home-page .widget-picker-size-btn {
    padding: 6px 12px;
    border-radius: var(--radius);
    border: 1px solid var(--border);
    background: transparent;
    color: var(--text-secondary);
    font-size: 0.8125rem;
    font-weight: 500;
}

body.new-ui #home-page .widget-picker-size-btn:hover {
    background: var(--brand-wash);
    border-color: var(--brand);
    color: var(--brand-ink);
}

body.new-ui #home-page .home-customize-checkbox {
    accent-color: var(--brand);
}

/* ── Widget edit mode ──
   Drop targets and drag outlines, previously dashed orange over an orange-tinted
   cell. The empty grid is scaffolding, so it recedes; only the live drop target
   is brand-colored. */
body.new-ui #home-page .widget-cell {
    border-color: var(--border);
    background: transparent;
    border-radius: var(--radius);
}

body.new-ui #home-page .widget-cell.drop-preview {
    background: var(--brand-wash);
    border-color: var(--brand);
}

body.new-ui #home-page .widget-grid.editing .widget:not(.hidden) {
    outline-color: var(--brand-wash-strong);
}

body.new-ui #home-page .widget-grid.editing .widget:not(.hidden):hover {
    outline-color: var(--brand);
    box-shadow: none;
}

/* ── Card link lists (Recent Articles, Newest Tools) ──
   The `.kb-feed-*` classes are shared by both cards — they are the house style for
   "a list of links inside a card", not something specific to the knowledge base.
   A list of links, not cards: short groups under their own small captions,
   each row a title with its category and date under it. Rows are buttons because
   they run script (mount the KB page, then open the article) rather than
   navigate, so they get an explicit reset — the base UI's button styling would
   otherwise centre the text and give them a background. */
body.new-ui #home-page .kb-feed-group + .kb-feed-group {
    margin-top: 16px;
    padding-top: 14px;
    border-top: 1px solid var(--gridline);
}

body.new-ui #home-page .kb-feed-label {
    margin: 0 0 8px;
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-muted);
}

body.new-ui #home-page .kb-feed-list {
    margin: 0;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

body.new-ui #home-page .kb-feed-link {
    display: flex;
    flex-direction: column;
    gap: 2px;
    width: 100%;
    padding: 7px 8px;
    border: 0;
    border-radius: var(--radius);
    background: none;
    text-align: left;
    font: inherit;
    color: inherit;
    cursor: pointer;
    transition: background-color var(--dur-fast) var(--ease-standard);
}

body.new-ui #home-page .kb-feed-link:hover {
    background: var(--brand-wash);
}

body.new-ui #home-page .kb-feed-link:hover .kb-feed-title {
    color: var(--brand-ink);
}

/* Two lines at most. An article title can run long, and a third line would push
   the card's row height past its neighbours' for one entry. */
body.new-ui #home-page .kb-feed-title {
    font-size: clamp(0.8125rem, 0.75vw, 0.9375rem);
    font-weight: 500;
    line-height: 1.35;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

body.new-ui #home-page .kb-feed-meta {
    font-size: clamp(0.6875rem, 0.62vw, 0.8125rem);
    color: var(--text-muted);
}

/* "Browse all", and any other quiet action that sits opposite a card title. */
body.new-ui #home-page .card-header-link {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    font-size: 0.75rem;
    font-weight: 500;
    color: var(--text-secondary);
    cursor: pointer;
    transition: color var(--dur-base) var(--ease-standard);
}

body.new-ui #home-page .card-header-link:hover {
    color: var(--brand-ink);
}

/* ── Time Zones: the lookup half ──
   In the New UI this block is moved into the clock card by applyTimeZoneMerge(),
   so it needs a rule above it to read as a second section rather than as more of
   the clock row. In the classic UI it stays in its own card, where the card
   title already says "Time Zone Tracker" — so the caption is redundant there and
   hidden, and there is no divider to draw because there is nothing above it. */
body.new-ui #home-page .tz-lookup {
    margin-top: 16px;
    padding-top: 14px;
    border-top: 1px solid var(--gridline);
}

body:not(.new-ui) .tz-lookup-label {
    display: none;
}

body.new-ui #home-page .tz-lookup-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 10px;
}

body.new-ui #home-page .tz-lookup-label {
    margin: 0;
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-muted);
}

/* ── Phones ──
   Below 601px responsive.css's pass stands: tight gutters, one column,
   Favourites as a swipe strip. These are the few places the redesign still has
   something to say there, and they sit at the end of the file on purpose — the
   rules they override carry the same specificity, so source order decides. */
@media (max-width: 600px) {
    /* responsive.css adds a border to the strip tiles because the base fill is
       white-on-white; the tile skin above already gives them one, so this just
       keeps the two from stacking into a doubled edge. */
    body.new-ui #home-page #favorites-list .shortcut-item {
        border: 1px solid var(--border);
    }
}

/* ── Docked Chat and Notes ──
   Both panels' contents move into these cards on the New UI home page
   (applyHomeDockedPanels in script.js). The panels are built as full-height
   slide-outs whose bodies are `flex: 1`, so all a dock needs to be is a flex
   column with a definite height for that flex to resolve against — without one
   the messages list collapses to nothing.

   These are also the dashboard's only tall cards, which is the point: they span
   two grid rows so the page has some vertical presence instead of three bands of
   short cards. The height is the greater of two rows of content and 30vh, so a
   short window does not get a card taller than itself. */
body.new-ui #home-page .widget-dock-card {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
}

body.new-ui #home-page .home-panel-dock {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-height: max(320px, 30vh);
    margin: calc(-1 * clamp(20px, 1.5vw, 30px));
    margin-top: 0;
    overflow: hidden;
    border-bottom-left-radius: var(--radius);
    border-bottom-right-radius: var(--radius);
}

/* The panels' own bodies carry their own padding and backgrounds, so the card
   padding is cancelled above and the moved content runs to the card's edge. */
body.new-ui #home-page .home-panel-dock > * {
    min-height: 0;
}

body.new-ui #home-page .home-dock-actions {
    display: flex;
    align-items: center;
    gap: 6px;
}

/* Hidden while their contents are on the page. A tab that slides the same thing
   over the top of itself is noise — and `display: none` here does not disturb the
   `hidden` class on #chat-panel-btn, which is a permission decision. */
body.panels-docked #chat-panel-btn,
body.panels-docked #onenote-panel-btn {
    display: none;
}

@media (min-width: 1040px) {
    body.new-ui #home-page .widget-grid:not(.widget-active) #widget-chat,
    body.new-ui #home-page .widget-grid:not(.widget-active) #widget-notes {
        grid-row: span 2;
    }
}

/* Coming Soon. Author-written announcement bodies, so each is its own block with
   a rule between rather than a bullet list — some of them are a sentence and some
   are a paragraph with an image in. */
body.new-ui #home-page .home-coming-soon-list {
    display: flex;
    flex-direction: column;
}

body.new-ui #home-page .home-coming-soon-item {
    font-size: clamp(0.8125rem, 0.75vw, 0.9375rem);
    line-height: 1.45;
    padding: 10px 0;
}

body.new-ui #home-page .home-coming-soon-item + .home-coming-soon-item {
    border-top: 1px solid var(--gridline);
}

body.new-ui #home-page .home-coming-soon-item img {
    max-width: 100%;
    height: auto;
    border-radius: var(--radius);
}

/* Marks a card whose contents are placeholder rather than live — the org chart or
   announcements call failed, or there is genuinely nothing there yet. Deliberately
   loud enough to read at a glance: sample content that looks live is the kind
   someone acts on, or screenshots and asks about. */
body.new-ui #home-page .sample-badge {
    display: inline-block;
    margin-left: 8px;
    padding: 2px 7px;
    border: 1px solid var(--status-pending);
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--status-pending);
    font-size: 0.625rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    vertical-align: middle;
}
