/* Color theme: a warm clay/terracotta palette (oklch, both light and
   dark), overriding every neutral+primary token Basecoat's stock "rhea"
   bundle otherwise sets — unlayered, so it wins over rhea's own
   @layer-wrapped :root/.dark regardless of selector order (see the file's
   own historical note near the bottom on why unlayered always beats
   @layer here). --chart-1..5 are carried over from the source theme even
   though nothing in this app charts anything yet, so a future dashboard
   graph doesn't need a separate palette decision.

   --success/--warning are NOT part of this theme swap — Basecoat ships
   primary/secondary/destructive but no success or warning token, and
   this app needs both for order/organization status badges. Left exactly
   as they were before this palette: status semantics are a separate
   concern from the neutral+primary re-theme.

   --radius: 0 is this theme's own choice (sharp, square corners
   throughout), not a rhea leftover. Tailwind's own default theme derives
   --radius-sm/md/lg/xl from --radius directly, but --radius-2xl/3xl/4xl
   are separate fixed Tailwind defaults (1rem/1.5rem/1.5rem) that do NOT
   derive from --radius on their own — and Basecoat's own CSS leans on
   --radius-2xl for the vast majority of components (.btn, inputs,
   badges, dropdowns) and --radius-3xl for cards/dialogs, so without an
   explicit tie-in here, cards/dialogs would keep Tailwind's rounded
   corners while every other control went square. All three are pinned
   to the bare --radius (0), not offset above it the way a earlier
   nonzero-radius pass here needed to dodge a pill/stadium look on short
   controls — that concern doesn't exist at a radius of 0, there's
   nothing left to round down further.

   --sidebar keeps its own distinct source-theme value here (a visible,
   deliberate difference from --background on desktop, where the sidebar
   is a permanent, always-visible panel rather than an overlay) — see the
   mobile-only override further down for why it's pinned to
   var(--background) below the md breakpoint specifically. */
:root {
  --background: oklch(1 0 0);
  --foreground: oklch(0.147 0.004 49.3);
  --card: oklch(1 0 0);
  --card-foreground: oklch(0.147 0.004 49.3);
  --popover: oklch(1 0 0);
  --popover-foreground: oklch(0.147 0.004 49.3);
  --primary: oklch(0.555 0.163 48.998);
  --primary-foreground: oklch(0.987 0.022 95.277);
  --secondary: oklch(0.967 0.001 286.375);
  --secondary-foreground: oklch(0.21 0.006 285.885);
  --muted: oklch(0.96 0.002 17.2);
  --muted-foreground: oklch(0.547 0.021 43.1);
  --accent: oklch(0.96 0.002 17.2);
  --accent-foreground: oklch(0.214 0.009 43.1);
  --destructive: oklch(0.577 0.245 27.325);
  --border: oklch(0.922 0.005 34.3);
  --input: oklch(0.922 0.005 34.3);
  --ring: oklch(0.714 0.014 41.2);
  --chart-1: oklch(0.868 0.007 39.5);
  --chart-2: oklch(0.547 0.021 43.1);
  --chart-3: oklch(0.438 0.017 39.3);
  --chart-4: oklch(0.367 0.016 35.7);
  --chart-5: oklch(0.268 0.011 36.5);
  --sidebar: oklch(0.986 0.002 67.8);
  --sidebar-foreground: oklch(0.147 0.004 49.3);
  --sidebar-primary: oklch(0.666 0.179 58.318);
  --sidebar-primary-foreground: oklch(0.987 0.022 95.277);
  --sidebar-accent: oklch(0.96 0.002 17.2);
  --sidebar-accent-foreground: oklch(0.214 0.009 43.1);
  --sidebar-border: oklch(0.922 0.005 34.3);
  --sidebar-ring: oklch(0.714 0.014 41.2);

  --radius: 0.45rem;
  --radius-2xl: var(--radius);
  --radius-3xl: var(--radius);
  --radius-4xl: var(--radius);
  --success: oklch(52% .14 150);
  --warning: oklch(52% .15 70);
}

/* Neither <html> nor <body> has ever had an explicit background-color of
   its own (every layout's <body> is bare — bg-background only ever lives
   on <main>, a child that doesn't cover the whole document). Turns out
   that's NOT always invisible on desktop the way it first looked: <main>
   has its own md:mt-2 top margin (see the layouts) so its rounded card
   floats slightly below the viewport's top edge — the strip that exposes
   is <body>/<html>'s own bare background, which visibly didn't match the
   sidebar sitting right next to it at that same top edge, a distinct
   seam confirmed live. On iOS Safari, viewport-fit=cover (see the <meta
   name="viewport"> in every layout/standalone page) additionally extends
   the document under the notch/status-bar safe area, and that strip —
   along with the elastic overscroll/rubber-band region at the top of the
   page — falls back to this same root background too.

   var(--sidebar), not var(--background): on desktop that's what visually
   continues into this gap (the sidebar sitting directly to its left at
   the same height) rather than main's own content color; on mobile
   --sidebar is already pinned to equal --background (see the md-scoped
   override further down), so this resolves to the exact same value the
   iOS fix originally used there — nothing about that behavior changes.
   A plain, static rule (not JS on document.body, which doesn't exist yet
   while the pre-paint <head> script that sets the "dark" class runs) is
   enough: both custom properties already resolve correctly the moment
   this stylesheet applies, since that script has already run by then. */
html {
  background-color: var(--sidebar);
}

/* viewport-fit=cover (every layout/standalone page's own <meta
   name="viewport">) lets the document paint under the iOS notch/
   status-bar and home-indicator safe areas — which is what the
   html{background-color} rule above needs to actually reach that area —
   but without this, real content (the sidebar's own header row, the
   main header's site name/hamburger/checkout button) would extend under
   the notch too and sit partly behind it. Padding the actual layout
   root by the safe-area inset keeps the background full-bleed while
   pulling visible content back into the safe area, the standard pairing
   for viewport-fit=cover. env() resolves to 0 on any browser/device
   without a notch or home indicator, so this is a no-op everywhere else. */
body {
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
}

.dark {
  --background: oklch(0.147 0.004 49.3);
  --foreground: oklch(0.986 0.002 67.8);
  --card: oklch(0.214 0.009 43.1);
  --card-foreground: oklch(0.986 0.002 67.8);
  --popover: oklch(0.214 0.009 43.1);
  --popover-foreground: oklch(0.986 0.002 67.8);
  --primary: oklch(0.473 0.137 46.201);
  --primary-foreground: oklch(0.987 0.022 95.277);
  --secondary: oklch(0.274 0.006 286.033);
  --secondary-foreground: oklch(0.985 0 0);
  --muted: oklch(0.268 0.011 36.5);
  --muted-foreground: oklch(0.714 0.014 41.2);
  --accent: oklch(0.268 0.011 36.5);
  --accent-foreground: oklch(0.986 0.002 67.8);
  --destructive: oklch(0.704 0.191 22.216);
  --border: oklch(1 0 0 / 10%);
  --input: oklch(1 0 0 / 15%);
  --ring: oklch(0.547 0.021 43.1);
  --chart-1: oklch(0.868 0.007 39.5);
  --chart-2: oklch(0.547 0.021 43.1);
  --chart-3: oklch(0.438 0.017 39.3);
  --chart-4: oklch(0.367 0.016 35.7);
  --chart-5: oklch(0.268 0.011 36.5);
  --sidebar: oklch(0.214 0.009 43.1);
  --sidebar-foreground: oklch(0.986 0.002 67.8);
  --sidebar-primary: oklch(0.769 0.188 70.08);
  --sidebar-primary-foreground: oklch(0.279 0.077 45.635);
  --sidebar-accent: oklch(0.268 0.011 36.5);
  --sidebar-accent-foreground: oklch(0.986 0.002 67.8);
  --sidebar-border: oklch(1 0 0 / 10%);
  --sidebar-ring: oklch(0.547 0.021 43.1);

  --success: oklch(68% .15 150);
  --warning: oklch(72% .16 70);
}

/* Below md (768px, matching every other md: breakpoint this app already
   uses to distinguish the sidebar's off-canvas mobile behavior from its
   permanent desktop one), --sidebar is pinned to var(--background)
   instead of its own distinct value above — confirmed live on iOS
   Safari via an A/B test that removed the sidebar entirely: with it
   gone, the browser's own chrome (status bar and address bar) matched
   the page correctly; with it back, the mismatch returned. The sidebar
   sits off-canvas via translate on mobile, not display:none, so it's
   still there for whatever heuristic Safari uses — giving it the same
   background as the page it's never visually distinguishable from on
   mobile anyway (an overlay drawer, not a permanent panel) removes the
   mismatch regardless of the exact mechanism, without touching the
   desktop sidebar's own deliberately-different look. Overriding :root
   alone (not :root and .dark separately) is enough: var(--background)
   itself already resolves to whichever theme is active. */
@media (max-width: 767px) {
  :root {
    --sidebar: var(--background);
  }
}

/* corner-shape only takes effect on an element that already has a
   border-radius — it changes the curve rounded corners are drawn with
   (circular arc vs. a squircle/superellipse), it doesn't add rounding of
   its own — so applying it to every element is inert everywhere there's
   no radius and reaches every rounded corner in the app (buttons, cards,
   inputs, badges, the sidebar's own outline above, ...) without having to
   chase down each of Basecoat's own radius-setting selectors individually.
   Still very new (Chromium 139+ at the time this was added); browsers
   without support just keep their normal circular-arc corners. */
* {
  corner-shape: squircle;
}

.badge[data-variant="success"] {
  background-color: color-mix(in oklab, var(--success) 10%, transparent);
  color: var(--success);
}
.badge[data-variant="success"]:hover {
  background-color: color-mix(in oklab, var(--success) 20%, transparent);
}
.badge[data-variant="warning"] {
  background-color: color-mix(in oklab, var(--warning) 10%, transparent);
  color: var(--warning);
}
.badge[data-variant="warning"]:hover {
  background-color: color-mix(in oklab, var(--warning) 20%, transparent);
}

/* The product-detail page's colour swatches (data-colour-swatch, see
   selectColour in app.js) toggle aria-pressed on click with no visual
   change otherwise — a plain outline badge reads identically whichever one
   is "on". Same primary near-black/near-white fill the sidebar's own
   active-nav-item state uses, since this is the same "you are here" role
   applied to a badge instead of a nav link. */
.badge[data-colour-swatch][aria-pressed="true"] {
  background-color: var(--primary);
  color: var(--primary-foreground);
  border-color: var(--primary);
}

/* Basecoat's own `@layer base` sets `* { border-color: var(--color-border);
   outline-color: var(--color-ring) }` so that any element which only sets a
   border *width* (e.g. Tailwind's bare `border-t`/`border-l` utilities,
   which this app uses directly in the layout templates, and which don't
   carry a color of their own) still gets the app's neutral hairline gray
   instead of a stray color. `@tailwindcss/browser`'s own Preflight reset
   also touches `*` in `@layer base` with a bare `border: 0px solid`
   shorthand — and a shorthand implicitly resets every longhand it doesn't
   mention, so it resets border-color too, to its initial value
   (`currentcolor`). Both stylesheets' `base` layers merge into one, and
   which of the two same-specificity `*` rules wins the tie for
   border-color/outline-color depends on load-order timing between a
   render-blocking `<script>` and an async `<link>` — not something worth
   depending on. Confirmed live: every border/outline that relies on this
   default (rather than setting its own color) was rendering in the
   near-black text color instead of the intended gray. Restated here,
   explicitly and unlayered, so it can't lose that race either way. */
/* * {
  border-color: var(--color-border);
  outline-color: var(--color-ring);
} */

/* Basecoat gives .card only a faint ring as its border — by itself an
   intentional "hairline", but in a dense multi-column grid (the
   catalogue, the b2b product grid) it's too faint to read as a card at
   all; items look like they're floating on the bare page with no
   boundary. Give cards a real, fully-opaque 1px border in the same
   hairline gray already used for tables and inputs elsewhere, so a card
   unambiguously reads as a bounded surface — on top of, not instead of,
   Basecoat's own ring and stock drop shadow (both render correctly on
   their own). */
/* .card {
  border: 1px solid var(--color-border) !important;
} */

/* Drawer content (the cart drawer, the organization/order detail drawer) has no
   visual separation between header/body/footer beyond padding — every other
   sectioned surface in the app marks that boundary with a hairline border
   (card footers, table headers, the page's own top header bar); drawers were
   the one place that didn't, so the header and footer visually ran straight
   into the scrollable body. Applies to every drawer instance automatically:
   no per-template change needed. */
.drawer > * > header {
  border-bottom: 1px solid var(--color-border);
}
.drawer > * > footer {
  border-top: 1px solid var(--color-border);
}

/* htmx adds .htmx-request to the element that triggered a request for the
   duration of that request (no hx-indicator is configured anywhere in this
   app, so this is otherwise invisible). Every action here — search-as-you-
   type, add to cart, quantity/status changes, save, delete, place order —
   fires an ajax request with zero feedback that a click or keystroke
   registered. This gives every triggering button/select a busy look for
   that window; disabling pointer-events on buttons only (not selects, which
   still need to be readable/interactive) also guards against double-firing
   a second request from an impatient double-click. Search inputs are left
   alone on purpose — they re-fire every ~300ms while typing, and a flicker
   on every keystroke would be worse than the missing feedback. */
.btn.htmx-request,
.select.htmx-request {
  opacity: 0.6;
  cursor: progress;
}
.btn.htmx-request {
  pointer-events: none;
}

/* Basecoat's .sidebar nav ships its own border-right (using --color-border,
   the same token <main>'s own border-l in the layout templates uses), so
   with both present the seam between sidebar and main doubled into two
   adjacent hairlines instead of one. Only <main> is meant to mark that
   boundary here, so the sidebar's copy is switched off. */
.sidebar[data-side="left"] nav {
  border-right-width: 0 !important;
}

/* <main> sits inset from the sidebar and the top of the viewport (see the
   border-l/border-t/mt-2/rounded-tl-xl on <main> in the layout templates),
   which leaves a few px of bare page background showing above and behind
   that corner. Tinting the page itself with --color-sidebar (the same
   background .sidebar nav's own background already uses) makes that gap
   read as the sidebar continuing behind <main>'s inset rather than a stray
   sliver of unrelated page color. <main> keeps the normal --color-background
   explicitly, so only the sidebar and the gap around it pick this up.
   Scoped to :has(> .sidebar) so the three sidebar-less standalone pages
   (both logins, 404) keep the plain page background instead of picking up
   a sidebar tint for a sidebar they don't have. */
body:has(> .sidebar) {
  background-color: var(--color-sidebar);
}

/* The mobile sidebar overlay (Basecoat's own off-canvas slide, opened by
   the header hamburger / closed by the X now mirrored in the sidebar's
   own header — see toggleSidebar() in app.js) defaults to Basecoat's
   stock --sidebar-mobile-width (18rem), leaving part of the dimmed page
   visible beside it. This app's mobile nav is meant to read as a
   full-screen takeover instead of a partial drawer, so this widens it to
   the full viewport below the same 48rem breakpoint Basecoat's own
   sidebar responsiveness switches on — height is already full-viewport
   regardless (Basecoat's own .sidebar nav sets inset-block: 0), so only
   the width token needs overriding. */
@media (max-width: 47.9375rem) {
  .sidebar {
    --sidebar-mobile-width: 100vw;
  }
}

/* Basecoat's stock sidebar nav gives every item — active or not — the same
   full-strength --color-sidebar-foreground text (its own rule only
   differentiates active/hover via a background fill and bold weight), so
   the whole list reads at equal visual weight with nothing pulling the eye
   to the current page. Muting the inactive state closer to
   --color-muted-foreground (only when neither active nor hovered, so
   Basecoat's own :hover rule — which restores full accent-foreground
   color — still wins on mouseover) gives the active item real contrast
   without touching the sidebar's own background tokens. */
.sidebar nav li > :is(a, button):not([aria-current="page"], [data-active="true"], :hover) {
  color: var(--color-muted-foreground);
}

/* Basecoat's active-item rule is a flat background fill with no border, so
   against this sidebar's own near-white background (--sidebar and
   --sidebar-accent sit within 1.5% lightness of each other in rhea's
   stock palette) the highlight barely reads as its own shape. A thin
   edge gives it a defined outline instead of relying on that faint fill
   alone to carry it — outline, not border: a real border adds to the box
   model, so a plain-to-active transition (e.g. clicking a nav link) would
   nudge that item's content inward by 1px as the border ate into its
   padding. Outline paints outside the box without occupying layout space,
   so nothing shifts. -1px offset pulls it flush with the edge a border
   would have sat at, rather than bleeding a pixel beyond the item's own
   rounded corners. */
/* .sidebar nav li > :is(a, button):is([aria-current="page"], [data-active="true"]) {
  outline: 1px solid var(--color-sidebar-border);
  outline-offset: -1px;
} */

/* #image-viewer (the product detail page's full-page photo viewer) is
   meant to fill the viewport — every other .dialog in this app is a small
   centered modal. Sizing+positioning the <dialog> element itself (via
   inset:0, an ID selector, so only this one dialog is affected) rather
   than fighting Basecoat's own centering math: inset:0 pins all four
   edges straight to the viewport with no unit-conversion or
   translate-centering step to get subtly wrong. The child then simply
   fills 100% of that already-correct box — plain percentage sizing,
   position:relative (not Basecoat's position:fixed) so its own
   absolutely-positioned buttons (close/prev/next) anchor to it directly,
   with top/left/translate zeroed out so nothing shifts it off that
   100%-filled box the way Basecoat's own centering math would.

   background-color here replaces a hardcoded bg-black in the markup with
   the same --color-popover every other .dialog/.drawer content surface
   uses, so the viewer reads as "this app's modal" rather than a plain
   black rectangle, and adapts with the light/dark theme instead of staying
   black in both. */
dialog#image-viewer {
  position: fixed !important;
  inset: 0 !important;
  margin: 0 !important;
  max-width: none !important;
  max-height: none !important;
}
#image-viewer > * {
  position: relative !important;
  top: auto !important;
  left: auto !important;
  translate: none !important;
  width: 100% !important;
  height: 100% !important;
  max-width: 100% !important;
  max-height: 100% !important;
  margin: 0 !important;
  border-radius: 0 !important;
  background-color: var(--color-popover) !important;
}

/* Every admin table (and b2b's own Orders table) used to carry
   `.table-container flex-1` in its own template, so it would stretch to
   fill the page's remaining height rather than leaving dead space below a
   short table. What that actually did, though: Basecoat's own
   `.table-container` sets `overflow-x: auto` but never touches
   `overflow-y` — and per the CSS overflow spec, leaving one axis
   `visible` while the other isn't computes the `visible` one to `auto`
   too, so `overflow-y` silently became `auto` as a side effect nobody
   asked for. Combined with `flex-1`'s default `flex-shrink: 1`, a flex
   item that's both shrinkable *and* independently scrollable is exactly
   what the flexbox algorithm prefers to compress: instead of the table
   overflowing up into the page's own scroll container (`<main>`'s content
   div, same as every non-table page), the table-container shrank to fit
   whatever space was left and became its own separate, nested scroll
   region — page header and search/filter row staying fixed, only the
   table body scrolling underneath them. Confirmed live at a short
   viewport: the outer content div's own scrollHeight matched its
   clientHeight exactly (no scroll needed there) while table-container's
   scrollHeight was nearly 3x its clientHeight (definitely scrolling, on
   its own). Every other page in this app — including the ones this was
   supposed to visually match — scrolls as one single region.

   `flex-1` is gone from every template now (no behavior loss: a short
   table's empty space below it was never visually distinct in the first
   place, .table-container has no background/border box of its own to
   "fill"). That alone wasn't enough, since flex-shrink:1 is flexbox's
   *default*, not something flex-1 uniquely added — this explicit
   flex-shrink:0 is what actually stops table-container from being
   compressed into its own scroll box, letting real overflow propagate up
   to the one scroll container every other page already uses. overflow-x
   stays auto (still needed — see the popover-positioning fix below, which
   depends on it existing). */
.table-container {
  flex-shrink: 0;
}

/* Basecoat's own .table gives the header row only a bottom border, no
   fill — .table tfoot gets a muted background (color-mix 50%) but thead
   doesn't get the equivalent treatment, so a header row reads as plain
   body text sitting above the data rather than a distinct header band.
   Matches tfoot's own mix exactly so header/footer read as the same
   "structural" band, applied here (not per-table) since every table in
   the app — admin and b2b alike — wants this. */
.table thead tr {
  background-color: color-mix(in oklab, var(--color-muted) 50%, transparent);
}

/* Basecoat's dropdown-menu/popover positions its [data-popover] content
   with plain `position: absolute` relative to the `.dropdown-menu`/
   `.popover` wrapper — not portalled, and (per Basecoat's own docs) with
   no CSS anchor-positioning setup to keep it clear of a scrolling
   ancestor's bounds. Closed is only `visibility: hidden`, not
   `display: none` — so even shut, its absolutely-positioned box
   (`min-width: max(100%, 9rem)`, no left/right offset until `data-align`
   applies on open) still counts toward any scrollable ancestor's overflow.
   Every admin table's `.table-container` has its own `overflow-x: auto`,
   so a closed row-actions kebab menu was silently forcing a permanent
   horizontal scrollbar just by existing. display:none removes it from
   layout/overflow entirely while closed; Basecoat's own JS flips
   aria-hidden to "false" on open, at which point this rule no longer
   applies and the popover displays and positions normally. */
[data-popover][aria-hidden="true"] {
  display: none !important;
}

/* Belt-and-suspenders for the open state: `[data-align="end"]{right:0}` in
   Basecoat's own CSS is what's supposed to keep an open popover's right
   edge pinned to its `.dropdown-menu` wrapper (so it grows left, staying
   inside the table, instead of spilling past the right edge) — but that's
   a bare, unscoped attribute selector with no fallback if it doesn't
   resolve for some reason, and this app's row-actions kebab sits in the
   table's last, right-most column, exactly where any positioning miss is
   most likely to push .table-container's own overflow-x: auto into a
   permanent scrollbar again. 14rem is comfortably enough for "Edit"/
   "Delete" and viewport-relative (not %, which for an absolutely
   positioned popover resolves against its tiny trigger-button containing
   block, not the table) so it can never itself be the reason the page
   grows wider than the screen. */
.dropdown-menu > [data-popover] {
  right: 0 !important;
  left: auto !important;
  max-width: min(14rem, 90vw) !important;
}

/* Every dialog form (product-form, organization-form, org-brand-settings-
   form, ...) wraps its fields in a `<section class="-mx-4 max-h-[60vh]
   overflow-y-auto px-4 scrollbar">` so a long form scrolls inside the
   dialog instead of growing it past the viewport. Basecoat's `.select`
   popover is plain `position: absolute; top: 100%` off the `.select`
   itself (see the [data-popover] rule above — no floating-ui/viewport-
   aware repositioning), so it's clipped by whatever ancestor first sets
   overflow to something other than visible — which this section always
   does, and (per the CSS overflow spec) on both axes at once even though
   only overflow-y was ever set. A select sitting anywhere but the very top
   of the section can have its open dropdown partly or fully clipped by the
   section's own scroll boundary instead of floating above the surrounding
   fields the way every other popover in this app does. Confirmed live: the
   Brand terms dialog's "add" row select is the section's last element, and
   its dropdown was being cut off entirely.
   overflow: visible is toggled on only while a popover inside is actually
   open (via :has(...[aria-hidden="false"])), not permanently — an
   unconditional overflow: visible here would also stop the section from
   ever scrolling long-enough content, even with every select closed. */
.dialog section:has(.select > [data-popover][aria-hidden="false"]) {
  overflow: visible;
}

/* The b2b Settings page (organization-settings.html) puts its three
   sections — org details, shipping addresses, team — in one shared
   left-nav/right-content `.tabs` panel instead of three stacked cards.
   Each section's own partial (organization-settings-form.html,
   shipping-addresses.html, org-users.html) is still its own `.card`,
   reused as-is because each is independently re-rendered via its own
   hx-target/outerHTML swap after a mutation (add address, add user, ...)
   — rebuilding that swap boundary as something other than the card's own
   root element wasn't worth it. Left as real `.card`s, they'd each get
   their own border/shadow/background nested inside the wrapping tabs
   card, reading as a box-within-a-box. This flattens only the outer box;
   `.card > header/section/footer`'s padding and title typography (scoped
   to the `.card` class itself, see Basecoat's own CSS) still apply, so
   each panel keeps the same heading/spacing rhythm a standalone card has. */
#organization-settings-tabs [role="tabpanel"] .card {
  border: none;
  box-shadow: none;
  background: transparent;
  border-radius: 0;
}

/* Basecoat's vertical tablist sets its own height to fit-content (sized to
   its 3 nav items), not stretched to match the taller content panel next
   to it — leaving a bare gap of card background below the nav on any
   panel taller than 3 rows. The `.tabs` row itself already stretches its
   children by default (plain flexbox align-items:stretch), but an
   explicit height always wins over that, so it never had a chance to
   apply. Overriding height back to auto lets the default stretch take
   over, so the nav's own background/border-e run the full height of
   whichever panel is currently tallest. */
/* Basecoat's default (non-"line") tabs variant wraps the tablist in a
   muted-gray track. Wanted here instead: the same --color-sidebar the
   actual sidebar rail uses, so this nav column reads as part of the app's
   navigation chrome rather than a generic gray box — with the selected
   item standing out via its own white background + shadow, still
   Basecoat's own selected-tab styling — kept its own `border-e` (see
   organization-settings.html) marking the boundary with the content
   column, same as the real sidebar's own edge. Basecoat's own default-
   variant tablist also carries its own rounded-2xl corners (sized for a
   small pill-shaped track, not a full-height column) — left in place,
   they show as a stray curve where the now-solid-colored column meets the
   card's straight edges. The card's own overflow:hidden + radius already
   handles the real corners (top-left/bottom-left), so this column itself
   has no business rounding anything. */
#organization-settings-tabs > [role="tablist"] {
  height: auto;
  border-radius: 0;
  background-color: var(--color-sidebar);
  justify-content: flex-start;
}

/* Basecoat's own tab-item padding (6px inline, 2px block) is sized for a
   compact horizontal tab strip, not a nav-style vertical list — reads as
   cramped here. A little more room on each row. Also cancels flex:1
   (each tab growing to fill an equal share of the now-stretched tablist's
   height) so the 3 rows stay their own natural size and any extra height
   just sits as empty space below the last one, like an ordinary nav list. */
#organization-settings-tabs > [role="tablist"] > [role="tab"] {
  flex: none;
  padding-block: 0.625rem;
  padding-inline: 0.75rem;
}

/* Confirmed live at 390px: the desktop nav-column-plus-content layout above
   has no room to stay side by side on a narrow screen — the w-56 nav
   column alone leaves barely any width for the content column, wrapping
   "Organization details" mid-word and squeezing the Contact/Email fields
   into an unreadable sliver. Basecoat's own `.tabs:has(>[role=tablist]
   [aria-orientation=vertical]){flex-direction:row}` has no viewport
   awareness (a fixed vertical-tabs layout, not a responsive one), so below
   the same 40rem/640px breakpoint this app's dialogs already switch at
   (Tailwind's `sm:`), stack nav-above-content instead: full-width tablist,
   items still full-width rows (unaffected — only the *column's own* width
   was ever capped), bottom divider instead of the desktop's end-edge one,
   full-width content below. */
@media (max-width: 39.9975rem) {
  #organization-settings-tabs {
    flex-direction: column;
  }
  #organization-settings-tabs > [role="tablist"] {
    width: 100%;
    border-inline-end: none;
    border-bottom: 1px solid var(--color-border);
  }
}

/* The settings dialog (#settings-dialog-body) reuses the same "organization
   -settings-tabs" card the full /settings page uses — but the dialog's own
   direct-child surface (Basecoat's `.dialog > *` rule) is already a white,
   rounded, *padded* (24px) box on its own. Two knock-on fixes follow from
   reusing a full card inside that already-boxed surface instead of a
   bespoke dialog body:
   1. The card's own border (see the Cards section of DESIGN.md —
      strengthened from Basecoat's near-invisible 10% ring to a real 1px
      border so it reads against a dense grid/plain page background) is
      redundant here, and with both surfaces nearly the same white at
      nearly the same radius, it read as a faint stray line rather than a
      deliberate second border, not the visible corner-highlight it's
      meant to be elsewhere.
   2. The dialog's own 24px padding plus the card's own edge left a
      visible gap of dialog-surface white all the way around the card —
      built for a header/section/footer sitting loose inside that
      padding, not for a card meant to fill the dialog edge-to-edge.
      Zeroed out so the card *is* the dialog's visible surface, the same
      "fits the modal" look the full-page version gets from the page's
      own content padding instead. */
#settings-dialog-body {
  padding: 0;
}

/* Claude's own settings modal (the reference for this whole panel) holds
   a fixed size regardless of which section is open, with only that
   section's own content scrolling past it — switching between a short
   section (Team, a handful of rows) and a long one (Shipping addresses
   with several saved addresses) doesn't resize the dialog at all, short
   or long. A fixed height, not just a cap, is what that takes — max-
   height alone still shrinks to fit short content. min-height:0 on both
   flex levels between the fixed-height card and the scrolling tabpanel is
   required for the overflow to actually take effect — without it a flex
   child refuses to shrink below its own content's natural height, so
   overflow-y-auto never gets the chance to engage (the classic nested-
   flexbox-scroll gotcha). Confirmed live: `.card` was correctly fixed at
   the full 70vh, but its own `flex: 0 1 auto` was still governing
   `.tabs`'s share of that height (the fixed height only pins `.card`
   itself — flex-grow is what decides whether a *child* claims the
   resulting extra space, and Basecoat's own default is 0, i.e. no). Short
   tabs (Organization details, Team) rendered `.tabs` at its own natural
   ~466px inside the 630px card, leaving unclaimed white space below the
   nav column and content pane alike rather than either one visibly
   filling the fixed height. flex: 1 makes `.tabs` claim all of it, which
   is what then lets the *tablist*'s own `align-items: stretch` (already
   correct on its own) stretch the nav column and the active tabpanel to
   match. */
#settings-dialog-body > .card {
  border: none;
  box-shadow: none;
  height: 70vh;
}
#settings-dialog-body #organization-settings-tabs {
  flex: 1;
  min-height: 0;
}
#settings-dialog-body [role="tabpanel"] {
  min-height: 0;
  overflow-y: auto;
}

/* #user-menu-trigger stacks two lines of text (org/admin name + user
   name) plus an avatar, but Basecoat's .btn[data-size=default] gives
   every button a fixed 2rem `height` (sized for one line) rather than a
   min-height — a plain py-* utility class can't override that, since a
   bare class selector's specificity loses to .btn[data-size=default]'s
   attribute selector regardless of source order, and confirmed live it
   only squeezed the two-line content into the same fixed box instead of
   growing it, clipping past the hover highlight's own edges. height:auto
   here lets the real padding below actually expand the box, giving the
   hover/pressed background comfortable room around the row instead of
   hugging the avatar and text edge to edge. */
#user-menu-trigger {
  height: auto;
  padding-block: 0.5rem;
}

/* The Theme/Language segmented controls in the preferences panel
   (#user-menu-panel, a standalone bordered block in the sidebar footer,
   not a popover) sit on their own bg-muted pill backdrop. Two
   separate tokens used to draw the "this option is active" state: a
   ghost .btn's own :hover uses --color-muted (identical to the backdrop
   it sits on — invisible), and the selected option's data-variant=
   "secondary" fill is --color-secondary, which sits only ~0.007 lightness
   from --color-muted in this theme (also barely visible, light or dark).
   --color-popover (the page/card surface, not a semantic "selected"
   token — chosen because it's simply the most visible option against
   this specific backdrop, ~0.04 lightness away from --color-muted in
   both themes) now draws both states in the same color, so there's only
   ever one visible "this one" pill in the group at a time rather than a
   selected fill and a separately-colored hover fighting for attention:
   the selected option shows it while nothing else in the group is
   hovered, and a hovered *other* option borrows it instead — :has() lets
   the plain selected-state rule get switched off from the group level
   rather than needing the selected item's own :hover state to know
   about its unselected siblings. */
#user-menu-panel [data-theme-group] > .btn[data-variant="ghost"]:hover,
#user-menu-panel [data-language-group] > .btn[data-variant="ghost"]:hover,
#user-menu-panel [data-theme-group]:not(:has(> .btn[data-variant="ghost"]:hover)) > .btn[data-variant="secondary"],
#user-menu-panel [data-language-group]:not(:has(> .btn[data-variant="ghost"]:hover)) > .btn[data-variant="secondary"] {
  background-color: var(--color-popover);
}
/* ...and explicitly cleared (not left to Basecoat's own --color-secondary
   fill, which is barely distinguishable from the backdrop but not
   nothing) while a sibling IS hovered, so exactly one pill is ever lit
   up at a time instead of a faint second one competing with it. */
#user-menu-panel [data-theme-group]:has(> .btn[data-variant="ghost"]:hover) > .btn[data-variant="secondary"],
#user-menu-panel [data-language-group]:has(> .btn[data-variant="ghost"]:hover) > .btn[data-variant="secondary"] {
  background-color: transparent;
}

/* #user-menu-panel's internal [role=separator] dividers used to get
   their look for free from `.dropdown-menu [role=separator]` while this
   content lived inside a real .dropdown-menu popover. The panel isn't a
   .dropdown-menu anymore (that class also sets display:inline-flex and
   position:relative, wrong for a static full-width stacked block), so
   its own height/color is reproduced directly here instead — sized to
   the panel's own content box, matching the width of the rows above/
   below it rather than touching the panel's actual border.

   margin-inline explicitly zeroed (not just omitted) because a much
   broader ambient rule, `.sidebar nav [role=separator]{margin-inline:
   calc(var(--spacing) * 2)}`, also matches here (this panel sits inside
   .sidebar nav same as everything else in the footer) and would
   otherwise inset the line an *extra* 0.5rem past the panel's own
   padding on top of it — confirmed live: without this the line rendered
   narrower than the Theme/Language/account rows around it instead of
   matching their width. */
#user-menu-panel [role="separator"] {
  margin-inline: 0;
  margin-block: 0.25rem;
  height: 1px;
  background-color: color-mix(in oklab, var(--color-border) 50%, transparent);
}

/* data-align="start" unreliability — confirmed live: without this, a
   filter dropdown (the catalogue's Brand/Availability, and every status/
   action filter that later reused the same dropdown-chip pattern —
   orders, organizations, the audit log, admin and b2b alike) rendered
   right-aligned to its own trigger regardless of data-align, pushing the
   popover's left edge behind the sidebar for any trigger sitting near
   the left edge of the page (all of these do). #order-status-filter-popover
   is shared by two different pages (admin/orders.html and
   b2b/orders.html) — fine, since only one of them is ever in the DOM at
   a time. These size to their own (short) option-label content just
   fine, only the side needs pinning. */
#brand-filter-popover,
#availability-filter-popover,
#order-status-filter-popover,
#organization-status-filter-popover,
#audit-action-filter-popover {
  left: 0 !important;
  right: auto !important;
}

/* #toaster gets popover="manual" from app.js (see the toaster-promotion
   IIFE there) so it can render above an open <dialog>'s top layer.
   Basecoat's own .toaster rule only ever set `bottom: 0` plus (via
   `.toaster:not([data-align])`) `right: 0` — it never had to set
   `top`/`left`/`margin`/`background`/`border` before, since without the
   popover attribute those simply defaulted to normal in-flow values that
   never mattered for a `position: fixed` element. The [popover] UA
   stylesheet fills exactly those gaps with its own defaults (`inset: 0`
   — so `top: 0` alongside Basecoat's `bottom: 0` stretches it the full
   viewport height instead of hugging the bottom; `margin: auto`; a solid
   `border`; an opaque `Canvas` background) — author rules for a property
   Basecoat's own class never touches don't exist to override those UA
   defaults, so they were showing through untouched. Confirmed live: the
   toaster rendered as a tall, opaque, top-left-pinned box instead of a
   transparent strip hugging the bottom-right corner. `left` must be
   reset to `auto` here too: the UA default is `inset: 0`, i.e. `left: 0`
   AND `right: 0` at once, and with margin no longer `auto` that leaves
   `left: 0` pinning the box to the left edge regardless of Basecoat's
   own `right: 0` — confirmed live via computed-style inspection
   (`left: 0px` and `right: 0px` both applied simultaneously). Clearing
   `left` lets `right: 0` plus the toaster's own `max-width` pull it to
   the right edge, sized to its content, matching Basecoat's non-popover
   default. No !important needed — any author-origin rule already beats
   a user-agent one for the same property, regardless of specificity. */
#toaster[popover] {
  top: auto;
  left: auto;
  margin: 0;
  border: none;
  background: transparent;
}

/* Native number-input spinners (up/down arrows) are hidden app-wide —
   not just on the catalogue matrix's cramped `w-14` cells — per explicit
   product decision, so every `input[type=number]` stays consistent. */
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
input[type="number"] {
  -moz-appearance: textfield;
  appearance: textfield;
}

/* Historical note: this file used to carry a large block of hardcoded
   box-shadow/backdrop-filter/translate overrides for the card shadow,
   every focus-visible ring, the switch-toggle knob shadow, the
   dropdown-menu popover's ring+shadow, the modal/drawer backdrop dim+blur,
   and dialog/sidebar translate composition. All of that was working
   around one root cause: this app used to load the legacy Tailwind Play
   CDN script (cdn.tailwindcss.com), whose injected Preflight reset sets
   --tw-shadow/--tw-ring-shadow/--tw-translate-x/--tw-translate-y on
   `*, ::before, ::after` as a plain, unlayered rule — which, per the CSS
   cascade-layer spec, always wins over Basecoat's own `@layer components`
   declarations for those same properties, regardless of selector
   specificity or source order. That silently zeroed out every shadow,
   ring, and translate-based effect app-wide.

   Now that the templates load the real `@tailwindcss/browser@4` compiler
   instead, its output is properly layered
   (`@layer theme, base, components, utilities;`) and its base reset only
   touches box-sizing/margin/padding/border — nothing about shadow, ring,
   or translate — so Basecoat's own component rules simply win the
   cascade the way they were always meant to, no workaround needed. Two
   of the old hardcoded values had also drifted from the real ones in the
   process (the backdrop blur was hardcoded to 4px; Basecoat's actual
   value is 8px — and the focus ring was hardcoded to a full-strength
   color; Basecoat's actual ring color carries 30% alpha), so removing
   them fixed an accuracy bug as well as a redundancy one. */

/* Basecoat's .tabs sets a flex gap (0.5rem) between its tablist and panel
   children — meant for a horizontal tab strip, where that gap is the only
   thing separating tabs from their content. The Company Settings tabs
   (organization-settings.html) are vertical, and the tablist column
   already carries its own border-e to mark that same boundary, so the two
   stacked together read as one much wider, oddly blank gutter with a
   stray line floating inside it rather than a single clean divider.
   Zeroed here rather than dropping border-e, since a bare gap with no
   border at all would leave the tablist and panel with no separator once
   this gap goes away. */
#organization-settings-tabs.tabs {
  gap: 0;
}

/* #line-entry-drawer-title (app.js's openLineEntryDrawer) is given
   tabindex="-1" and focused programmatically the instant the mobile
   quantity drawer opens — not because the heading is meant to look
   focused, but because showModal() otherwise auto-focuses the first
   *focusable* element inside the dialog, which for the variant matrix is
   its first qty <input>, popping the mobile keyboard open unprompted.
   Moving focus to the heading instead keeps it off any input while still
   properly trapping focus inside the open dialog (matters for Escape/
   Tab and screen readers) — but a plain heading showing the browser's
   default focus ring reads as a stray, meaningless highlight since nothing
   about it is interactive. Suppressed here rather than by removing the
   focus() call, which would leave the real bug (the keyboard popping up)
   back in place. */
#line-entry-drawer-title:focus {
  outline: none;
}
