/* ---- Fonts (self-hosted, no CDN) ---- */
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 300 900;
  font-display: swap;
  src: url('../fonts/fraunces-variable.woff2') format('woff2');
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/inter-variable.woff2') format('woff2');
}

/* App-specific components layered on top of the Tailwind design system.
   Bootstrap is intentionally not loaded, so framework defaults cannot
   override utility classes. */

/* ---- Mesh-gradient background (body) ---- */
.mesh-bg {
  background-color: var(--color-ink-900);
  background-image:
    radial-gradient(circle at 15% 20%, rgb(28 70 48 / 0.55) 0%, transparent 40%),
    radial-gradient(circle at 85% 15%, rgb(46 33 80 / 0.55) 0%, transparent 45%),
    radial-gradient(circle at 50% 95%, rgb(71 85 105 / 0.4) 0%, transparent 50%);
  background-size: 180% 180%, 160% 160%, 200% 200%;
  background-attachment: fixed;
}

/* Translucent surface used consistently for cards and pill-shaped actions. */
.glass-panel {
  background-color: rgb(255 255 255 / 0.05);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border: 1px solid rgb(255 255 255 / 0.15);
  box-shadow: var(--shadow-glass);
}

/* backdrop-filter makes each glass card its own stacking context. Temporarily
   raise the card containing an open custom select so its listbox can cross a
   neighboring card boundary without being painted underneath that card. */
.glass-panel.has-open-custom-select {
  position: relative;
  z-index: 50;
}

.two-factor-surface {
  overflow: hidden;
  border-radius: 1rem;
}

.two-factor-code-panel {
  border-radius: 1rem;
}


.dialog-open {
  overflow: hidden;
}

.storefront-dialog {
  width: min(42rem, calc(100% - 1rem));
  max-width: 42rem;
  max-height: min(88vh, 52rem);
  max-height: min(88dvh, 52rem);
  margin: auto auto 0.5rem;
  padding: 0;
  overflow: hidden;
  color: #fff;
  background: transparent;
  border-radius: 1.5rem 1.5rem 0 0;
}

.storefront-dialog::backdrop {
  background: rgb(2 6 23 / 0.72);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

.storefront-dialog[open] {
  display: block;
}

.storefront-dialog__surface {
  display: flex;
  flex-direction: column;
  max-height: min(88vh, 52rem);
  max-height: min(88dvh, 52rem);
  overflow: hidden;
  border-radius: inherit;
  background:
    linear-gradient(180deg, rgb(255 255 255 / 0.06), rgb(255 255 255 / 0.03)),
    radial-gradient(circle at top, rgb(255 255 255 / 0.08), transparent 55%);
}

.storefront-dialog__header {
  flex: 0 0 auto;
}

.storefront-dialog__body {
  min-height: 0;
  padding-block: 1.25rem;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.storefront-dialog__body:focus-visible {
  outline: 2px solid var(--color-accent-emerald);
  outline-offset: -3px;
}

.confirm-dialog {
  width: min(32rem, calc(100% - 1rem));
  max-width: 32rem;
  margin: auto;
  padding: 0;
  color: #fff;
  background: transparent;
  border: 0;
}

.confirm-dialog::backdrop {
  background: rgb(2 6 23 / 0.72);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

.confirm-dialog__surface {
  overflow: hidden;
  border-radius: 1.5rem;
  border: 1px solid rgb(255 255 255 / 0.1);
  background:
    linear-gradient(180deg, rgb(255 255 255 / 0.06), rgb(255 255 255 / 0.03)),
    radial-gradient(circle at top, rgb(255 255 255 / 0.08), transparent 55%);
}

.confirm-dialog__body {
  padding: 1.25rem 1.25rem 1rem;
}

.confirm-dialog__eyebrow {
  margin: 0;
  font-size: 0.75rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgb(255 255 255 / 0.45);
}

.confirm-dialog__title {
  margin: 0.5rem 0 0;
  font-family: 'Fraunces', serif;
  font-size: 1.5rem;
  font-weight: 600;
  line-height: 1.2;
}

.confirm-dialog__message {
  margin: 0.9rem 0 0;
  font-size: 0.95rem;
  line-height: 1.65;
  color: rgb(255 255 255 / 0.8);
}

.confirm-dialog__actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.75rem;
  padding: 0 1.25rem 1.25rem;
}

.confirm-dialog__button {
  border-radius: 9999px;
  padding: 0.7rem 1rem;
  font-size: 0.95rem;
  font-weight: 600;
  transition: background-color 160ms ease, color 160ms ease, border-color 160ms ease;
}

.confirm-dialog__button--secondary {
  border: 1px solid rgb(255 255 255 / 0.1);
  background: rgb(255 255 255 / 0.05);
  color: rgb(255 255 255 / 0.8);
}

.confirm-dialog__button--secondary:hover {
  background: rgb(255 255 255 / 0.1);
  color: #fff;
}

.confirm-dialog__button--primary {
  border: 1px solid transparent;
  background: var(--color-accent-emerald);
  color: rgb(6 40 29);
}

.confirm-dialog__button--primary:hover {
  background: rgb(82 224 171);
}

@media (min-width: 640px) {
  .storefront-dialog {
    margin: auto;
    border-radius: 1.75rem;
  }

  .storefront-dialog__body {
    padding-block: 1.5rem;
  }
}

.disabled-control:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}

@media print {
  body {
    color: #111;
    background: #fff;
  }
  header,
  footer,
  .no-print {
    display: none;
  }
  main {
    max-width: none;
    padding: 0;
  }
  .weekly-workspace .glass-panel,
  .weekly-workspace [class*="bg-"] {
    color: #111;
    background: #fff;
    border-color: #bbb;
    box-shadow: none;
  }
  .weekly-workspace a {
    color: #111;
  }
}

/* ---- Skip link: off-screen until keyboard-focused, so it never affects
   sighted mouse users but gives keyboard users a way to bypass the 6-link
   nav without tabbing through it every single page load. ---- */
.skip-link {
  position: absolute;
  top: -3rem;
  left: 1rem;
  z-index: 100;
  background-color: rgb(11 12 16 / 0.95);
  color: #fff;
  border: 1px solid var(--color-accent-emerald);
  border-radius: 0.5rem;
  padding: 0.6rem 1rem;
  font-size: 0.875rem;
  font-weight: 600;
  text-decoration: none;
  transition: top 150ms ease;
}
.skip-link:focus {
  top: 1rem;
  outline: none;
  box-shadow: 0 0 0 3px rgb(52 211 153 / 0.35);
}

:where(a, button, input, select, textarea, summary):focus-visible {
  outline: 2px solid var(--color-accent-emerald);
  outline-offset: 3px;
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ---- Tactile / neumorphic inset inputs ---- */
.tactile-input {
  background-color: rgb(0 0 0 / 0.25);
  box-shadow: var(--shadow-inset);
  border: 1px solid rgb(255 255 255 / 0.08);
  color: #fff;
}
.tactile-input:is(select):not([size]) {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  padding-right: 2.5rem;
  background-image:
    linear-gradient(45deg, transparent 50%, rgb(255 255 255 / 0.7) 50%),
    linear-gradient(135deg, rgb(255 255 255 / 0.7) 50%, transparent 50%);
  background-position:
    calc(100% - 1.15rem) calc(50% - 0.15rem),
    calc(100% - 0.8rem) calc(50% - 0.15rem);
  background-size: 0.4rem 0.4rem, 0.4rem 0.4rem;
  background-repeat: no-repeat;
}
.tactile-input:is(select[size]) {
  appearance: auto;
  -webkit-appearance: auto;
  -moz-appearance: auto;
  height: auto;
  background-image: none;
}
.tactile-input:is(select) option {
  color: #fff;
  background-color: rgb(25 32 28);
}

.custom-select {
  position: relative;
  min-width: 0;
}

.custom-select__native {
  position: absolute;
  inset: 0;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.custom-select__trigger {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  text-align: left;
  position: relative;
}

.custom-select__trigger::after {
  content: "";
  flex: 0 0 auto;
  width: 0.65rem;
  height: 0.65rem;
  border-right: 2px solid rgb(255 255 255 / 0.72);
  border-bottom: 2px solid rgb(255 255 255 / 0.72);
  transform: rotate(45deg) translateY(-10%);
  transition: transform 160ms ease;
}

.custom-select.is-open .custom-select__trigger::after {
  transform: rotate(-135deg) translateY(-10%);
}

.custom-select__trigger-label {
  min-width: 0;
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.custom-select__panel {
  position: absolute;
  left: 0;
  top: calc(100% + 0.45rem);
  z-index: 40;
  display: none;
  min-width: 100%;
  width: max-content;
  max-width: min(26rem, calc(100vw - 2rem));
}

.custom-select.is-open .custom-select__panel {
  display: block;
}

.custom-select__listbox {
  min-width: 100%;
  max-height: 18rem;
  overflow-y: auto;
  border-radius: 1rem;
  border: 1px solid rgb(255 255 255 / 0.12);
  background:
    linear-gradient(180deg, rgb(31 46 40 / 0.98), rgb(18 28 24 / 0.98));
  box-shadow: 0 18px 44px rgb(0 0 0 / 0.35);
  padding: 0.4rem;
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
}

.custom-select__option {
  display: block;
  width: 100%;
  min-width: 0;
  border-radius: 0.75rem;
  padding: 0.8rem 0.9rem;
  text-align: left;
  font-size: 0.95rem;
  line-height: 1.35;
  color: rgb(255 255 255 / 0.86);
}

.custom-select__option:hover,
.custom-select__option:focus-visible {
  background: rgb(255 255 255 / 0.08);
  outline: none;
}

.custom-select__option.is-selected {
  background: color-mix(in srgb, var(--color-accent-emerald) 26%, transparent);
  color: #fff;
}

.custom-select__option.is-disabled,
.custom-select.is-disabled .custom-select__trigger {
  opacity: 0.5;
}
.tactile-input::placeholder {
  color: rgb(255 255 255 / 0.35);
}
.tactile-input:focus {
  /* Confirmed live via Puppeteer that a plain click-triggered focus (not a
     scripted .focus() call, which doesn't reliably reflect real :focus
     styling in headless Chrome) DID apply this rule correctly - but the
     original white-at-8%-opacity ring was so subtle it barely read as a
     focus indicator next to an unfocused sibling field. Strengthened to
     the brand's own emerald accent at higher opacity so a keyboard user
     tabbing through a form can actually tell which field has focus. */
  outline: none;
  border-color: var(--color-accent-emerald);
  box-shadow: var(--shadow-inset), 0 0 0 3px rgb(52 211 153 / 0.35);
}

/* ---- Live Status Ring: a solid circle masked so only the outer ring (not
   a filled disc) is visible. is-good/is-bad pick the color; the
   animate-ring-pulse-good, animate-ring-pulse-bad and animate-ring-shake
   utility classes (Tailwind, driven by the theme's animate tokens) add
   the motion. ---- */
.status-ring {
  --ring-color: var(--color-accent-emerald);
  aspect-ratio: 1;
  border-radius: 9999px;
  background: var(--ring-color);
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 14px), #000 calc(100% - 13px));
  mask: radial-gradient(farthest-side, transparent calc(100% - 14px), #000 calc(100% - 13px));
}
.status-ring.is-good { --ring-color: var(--color-accent-emerald); }
.status-ring.is-bad { --ring-color: var(--color-accent-crimson); }

/* ---- Skeuomorphic paper label preview ---- */
.label-card {
  background-color: var(--color-paper);
  color: #2b2118;
  box-shadow: var(--shadow-label);
  transform: perspective(1000px) rotateX(0deg) rotateY(0deg);
  transition: transform 0.2s ease;
}
.label-card .label-sticker {
  font-size: 11px;
  background-color: var(--color-paper-dark);
  border: 1px dashed rgb(0 0 0 / 0.25);
  box-shadow: var(--shadow-inset);
  transform: rotate(-1.5deg);
}

/* Faithful mini-label preview internals (Products/Edit sidebar). Mirrors the
   fields the printed PDF actually carries — identity, ingredients, an emphasized
   "Contains" callout, net weight, and the mandated disclaimer — so the baker
   previews the real label rather than a UI card. Price is deliberately absent:
   it is not a field on the printed statutory label. All tones are ink on the
   card's cream paper. */
.label-preview__eyebrow {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: rgb(43 33 24 / 0.5);
}
.label-preview__name {
  margin-top: 0.15rem;
  font-size: 1.2rem;
  font-weight: 600;
  line-height: 1.15;
}
.label-preview__identity {
  margin-top: 0.35rem;
  font-size: 0.72rem;
  line-height: 1.4;
  color: rgb(43 33 24 / 0.72);
}
.label-preview__rule {
  height: 1px;
  margin: 0.7rem 0;
  background: rgb(43 33 24 / 0.18);
}
.label-preview__ingredients {
  font-size: 0.72rem;
  line-height: 1.45;
  color: rgb(43 33 24 / 0.75);
}
.label-preview__field {
  font-weight: 600;
}
.label-preview__muted {
  font-style: italic;
  opacity: 0.75;
}
.label-preview__contains {
  margin-top: 0.5rem;
  padding: 0.3rem 0.55rem;
  border: 1px solid rgb(43 33 24 / 0.28);
  border-radius: 0.28rem;
  font-size: 0.72rem;
  font-weight: 600;
  line-height: 1.35;
}
.label-preview__netwt {
  margin-top: 0.5rem;
  font-size: 0.82rem;
  font-weight: 500;
}

/* Products/Edit's "label workshop" scale-up — the same faithful preview,
   just given real presence now that its column is wide enough to earn it,
   rather than reading as a squeezed-in sidebar extra. Every field keeps its
   exact wording/order; only type scale and spacing grow. */
.label-card--lg .label-preview__eyebrow {
  font-size: 11px;
}
.label-card--lg .label-preview__name {
  font-size: 1.55rem;
}
.label-card--lg .label-preview__identity {
  font-size: 0.86rem;
}
.label-card--lg .label-preview__rule {
  margin: 0.9rem 0;
}
.label-card--lg .label-preview__ingredients {
  font-size: 0.86rem;
}
.label-card--lg .label-preview__contains {
  font-size: 0.86rem;
  padding: 0.4rem 0.65rem;
}
.label-card--lg .label-preview__netwt {
  font-size: 0.95rem;
}
.label-card--lg .label-sticker {
  font-size: 12px;
}

/* ---- Homepage hero: the product artifact as the page's thesis. A jar of
   preserves (hand-authored SVG, .hero-jar) with the real cottage food label
   the tool generates (.hero-label) propped against it. The warm paper/jam
   tones are a deliberate analog accent against the cool digital-dark system:
   your handmade product, our precise compliance. The whole group tilts on
   pointer via .tilt-card (reduced-motion-aware — see ui.js), so it carries
   no motion rules of its own. ---- */
.hero-grid {
  position: relative;
  display: grid;
  gap: 2.5rem;
  align-items: center;
}
@media (min-width: 900px) {
  .hero-grid {
    grid-template-columns: 1.05fr 0.95fr;
    gap: 3.5rem;
  }
}
/* Content sits above the ambient canvas. */
.hero-grid > .reveal {
  position: relative;
  z-index: 1;
}

/* Ambient three.js "flour motes" canvas — behind the hero content, never
   interactive, masked toward the edges so it reads as depth rather than a
   rectangle. Empty (transparent) when motion is off, WebGL is unavailable, or
   the hero is off-screen — see hero-atmosphere.js. */
.hero-atmos {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 0;
  pointer-events: none;
  -webkit-mask-image: radial-gradient(125% 100% at 62% 45%, #000 52%, transparent 100%);
  mask-image: radial-gradient(125% 100% at 62% 45%, #000 52%, transparent 100%);
}

/* ---- First-run empty states: open and airy (not a bordered "content is
   missing" box), centered around an original hand-drawn line illustration in
   the same register as the hero jar. A reusable pattern — extend to other
   pages by dropping the same markup in. ---- */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 3.5rem 1.5rem 4rem;
}
.empty-state__art {
  position: relative;
  width: 8.5rem;
  height: 8.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 1.5rem;
}
.empty-state__art::before {
  content: "";
  position: absolute;
  inset: 10%;
  background: radial-gradient(circle at 50% 45%, rgb(52 211 153 / 0.16) 0%, transparent 70%);
  filter: blur(7px);
}
.empty-state__art svg {
  position: relative;
  width: 100%;
  height: 100%;
  filter: drop-shadow(0 12px 22px rgb(0 0 0 / 0.4));
}
.empty-state__title {
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.empty-state__body {
  margin-top: 0.5rem;
  max-width: 26rem;
  font-size: 0.9rem;
  line-height: 1.5;
  color: rgb(255 255 255 / 0.6);
}
.empty-state__cta {
  margin-top: 1.5rem;
}

.hero-title {
  font-size: clamp(2.2rem, 4.6vw, 3.15rem);
  line-height: 1.06;
  letter-spacing: -0.015em;
}

.hero-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: rgb(255 255 255 / 0.55);
}
.hero-eyebrow::before {
  content: "";
  width: 1.9rem;
  height: 1px;
  background: var(--color-accent-emerald);
  opacity: 0.75;
}

/* Stage: soft radial glow behind the artifact + perspective for the tilt. */
.hero-figure {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 23rem;
  perspective: 1200px;
}
.hero-figure::before {
  content: "";
  position: absolute;
  inset: 6% 8%;
  background: radial-gradient(58% 52% at 52% 40%, rgb(52 211 153 / 0.22) 0%, transparent 72%);
  filter: blur(8px);
  z-index: 0;
}

.hero-stage {
  position: relative;
  z-index: 1;
  width: min(26rem, 100%);
  transform-style: preserve-3d;
}

.hero-jar {
  display: block;
  width: 80%;
  margin: 0 auto;
  filter: drop-shadow(0 24px 32px rgb(0 0 0 / 0.5));
}

/* The paper label, propped in front of the jar's lower body — visible as a
   label leaning on the jar, not a card that swallows it. Live-verified via
   Puppeteer (r160 screenshot) after this app's headless-Chrome deps
   (libnss3 etc.) became available mid-session; the first version was sized
   nearly as wide as the jar itself and hid almost the whole glass body
   behind it — text-only/curl checks couldn't have caught that. */
.hero-label {
  position: absolute;
  left: 50%;
  bottom: 6%;
  width: 10.5rem;
  max-width: 50%;
  transform: translateX(-53%) rotate(-3.5deg);
  background-color: var(--color-paper);
  color: #2b2118;
  border-radius: 0.5rem;
  padding: 0.7rem 0.8rem 0.65rem;
  box-shadow: var(--shadow-label);
}
.hero-label__brand {
  font-family: var(--font-display);
  font-size: 0.92rem;
  font-weight: 600;
  line-height: 1.1;
}
.hero-label__rule {
  height: 1px;
  margin: 0.4rem 0;
  background: rgb(43 33 24 / 0.22);
}
.hero-label__product {
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.01em;
}
.hero-label__meta {
  margin-top: 0.24rem;
  font-size: 0.52rem;
  line-height: 1.35;
  color: rgb(43 33 24 / 0.72);
}
.hero-label__contains {
  margin-top: 0.34rem;
  font-size: 0.54rem;
  font-weight: 600;
  letter-spacing: 0.02em;
}
.hero-label__contains span {
  color: rgb(43 33 24 / 0.6);
  font-weight: 700;
}
.hero-label__disclaimer {
  margin-top: 0.5rem;
  padding: 0.32rem 0.42rem;
  font-size: 0.46rem;
  line-height: 1.3;
  color: rgb(43 33 24 / 0.82);
  background-color: var(--color-paper-dark);
  border: 1px dashed rgb(43 33 24 / 0.28);
  border-radius: 0.28rem;
}

/* At narrow widths the label's fixed-content height doesn't shrink with its
   width the way the jar's does (text wraps into more lines instead), so the
   label/jar height RATIO grows the narrower the viewport gets — confirmed
   live via Puppeteer at 390px: the label ballooned to 81% of the jar's
   height (vs 63% at desktop width), swallowing most of the jar it's meant
   to lean against, reproducing the original desktop jar/label bug but on
   mobile. This is a decorative hero illustration, not the real compliance
   label PDF, so it's safe to drop the least-essential line (the full
   ingredients list) here to keep the card shorter — brand, product name,
   allergen callout, net weight, and the disclaimer all still show. */
@media (max-width: 480px) {
  .hero-label {
    width: 9.25rem;
    padding: 0.55rem 0.65rem 0.5rem;
  }
  .hero-label__rule {
    margin: 0.3rem 0;
  }
  .hero-label__meta {
    margin-top: 0.16rem;
    line-height: 1.25;
  }
  .hero-label__contains {
    margin-top: 0.22rem;
  }
  .hero-label__disclaimer {
    margin-top: 0.34rem;
    padding: 0.24rem 0.32rem;
    line-height: 1.2;
  }
  .hero-label__meta--ingredients {
    display: none;
  }
}

/* ---- Tilt-on-hover cards (JS supplies the actual rotation via inline
   transform — see wwwroot/js/ui.js's enableTiltOnHover — this just sets up
   the transition/perspective so the effect is smooth, and is skipped
   entirely under prefers-reduced-motion by that same JS). ---- */
.tilt-card {
  transform-style: preserve-3d;
  transition: transform 0.35s ease, box-shadow 0.35s ease;
  will-change: transform;
}

/* ---- Row lift: shadow/translate only, no rotation — used on table rows
   instead of tilt-card, since rotating tabular data reads as broken. ---- */
.row-lift {
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.row-lift:hover {
  transform: translateY(-2px);
  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.35);
  position: relative;
  z-index: 1;
}

/* ---- Scroll-triggered reveal (marketing sections) ---- */
.reveal {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}
.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* ---- Sales-cap glowing progress bar (Sales page). --glow-color is set
   inline per-row from the level Razor already computed (cap-good/warn/bad),
   so there's no duplicated threshold logic in CSS or JS. ---- */
.glow-bar {
  box-shadow: 0 0 12px 1px var(--glow-color, transparent);
  transition: width 300ms ease;
}

/* ---- Per-baker accent color picker (Settings page) ---- */
.color-swatch {
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  border: 2px solid transparent;
  padding: 0;
  cursor: pointer;
  box-shadow: 0 0 0 1px rgb(255 255 255 / 0.15);
  transition: transform 150ms ease, box-shadow 150ms ease;
}
.color-swatch:hover {
  transform: scale(1.08);
}
.color-swatch.is-selected {
  box-shadow: 0 0 0 2px rgb(0 0 0 / 0.4), 0 0 0 4px #fff;
}
.color-swatch-custom {
  width: 2.25rem;
  height: 2.25rem;
  padding: 0.15rem;
  border: 1px dashed rgb(255 255 255 / 0.3);
  border-radius: 50%;
  background: none;
  cursor: pointer;
}
/* A native <input type="color">'s own swatch isn't clipped by the outer
   border-radius above — Chromium/Firefox both render it as their own
   rounded-rect regardless, so without this it visually reads as a small
   square sitting inside a circular dashed ring, not the circle the other
   swatches are. Confirmed via an actual render (Puppeteer), not just
   reading the CSS — the mismatch isn't visible from source alone. */
.color-swatch-custom::-webkit-color-swatch-wrapper {
  padding: 0;
  border-radius: 50%;
  overflow: hidden;
}
.color-swatch-custom::-webkit-color-swatch {
  border: none;
  border-radius: 50%;
}
.color-swatch-custom::-moz-color-swatch {
  border: none;
  border-radius: 50%;
}

@media (prefers-reduced-motion: reduce) {
  .mesh-bg,
  .label-card,
  .tilt-card,
  .reveal,
  .animate-mesh-drift,
  .animate-ring-pulse-good,
  .animate-ring-pulse-bad,
  .animate-ring-shake,
  .animate-reveal-in {
    animation: none !important;
    transition: none !important;
  }
  .label-card:hover {
    transform: perspective(1000px) rotateX(0deg) rotateY(0deg) !important;
  }
}

/* Minimal compatibility for the few unscaffolded ASP.NET Core Identity
   screens. These rules implement the app's own design system; Bootstrap is
   not loaded and cannot leak into the primary UI. */
.col-lg-4.col-lg-offset-2 {
  display: none;
}

.row {
  display: grid;
  gap: 1.5rem;
}

.col-lg-6 form,
.col-md-4 form,
.col-md-6 form,
.col-xl-6 form {
  background-color: rgb(255 255 255 / 0.05);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border: 1px solid rgb(255 255 255 / 0.15);
  box-shadow: var(--shadow-glass);
  border-radius: 1rem;
  padding: 2rem;
  max-width: 28rem;
}

.form-floating {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  margin-bottom: 1rem;
}
.form-floating > label {
  order: -1;
  color: rgb(255 255 255 / 0.7);
  font-size: 0.875rem;
}
.form-control {
  display: block;
  width: 100%;
  border-radius: 0.5rem;
  padding: 0.65rem 0.75rem;
  background-color: rgb(0 0 0 / 0.25);
  box-shadow: var(--shadow-inset);
  border: 1px solid rgb(255 255 255 / 0.08);
  color: #fff;
}
.form-control:focus {
  outline: none;
  background-color: rgb(0 0 0 / 0.3);
  border-color: var(--color-accent-emerald);
  box-shadow: var(--shadow-inset), 0 0 0 3px rgb(52 211 153 / 0.35);
  color: #fff;
}
.form-control:disabled {
  background-color: rgb(255 255 255 / 0.04);
  color: rgb(255 255 255 / 0.5);
}

.col-lg-6 form hr,
.col-md-4 form hr,
.col-md-6 form hr,
.col-xl-6 form hr {
  border-color: rgb(255 255 255 / 0.15);
  opacity: 1;
}

.btn,
.btn-primary,
.btn-danger {
  display: inline-block;
  border: 0;
  border-radius: 0.5rem;
  padding: 0.65rem 1rem;
  cursor: pointer;
  font: inherit;
}
.btn-primary {
  background-color: var(--color-accent-emerald);
  color: #06281d;
  font-weight: 600;
}
.btn-primary:hover,
.btn-primary:focus {
  background-color: #2bbd86;
  color: #06281d;
}
.btn-danger {
  background-color: var(--color-accent-crimson);
  color: #fff;
  font-weight: 600;
}

.col-lg-6 form a,
.col-md-4 form a,
.col-md-6 form a,
.col-xl-6 form a,
.btn-link {
  color: var(--color-accent-emerald);
  text-decoration: underline;
}
.col-lg-6 form a:hover,
.col-md-4 form a:hover,
.col-md-6 form a:hover,
.col-xl-6 form a:hover,
.btn-link:hover,
.btn-link:focus {
  color: #fff;
}

.form-check-input {
  width: 1rem;
  height: 1rem;
  accent-color: var(--color-accent-emerald);
}
.form-check-input:checked {
  background-color: var(--color-accent-emerald);
  border-color: var(--color-accent-emerald);
}

/* Unscaffolded Identity pages emit bare headings. */
main > h1:not([class]),
main > h3,
main > h4 {
  font-family: var(--font-display, 'Fraunces', serif);
  font-weight: 600;
  margin-bottom: 1rem;
}
main > h1:not([class]),
main > h3 {
  font-size: 1.875rem;
  margin-bottom: 1.5rem;
}
main > h4 {
  font-size: 1.25rem;
  color: rgb(255 255 255 / 0.85);
}

/* Alert class names originate in unscaffolded Identity markup. */
.alert {
  border: none;
  border-radius: 0.75rem;
  padding: 1rem 1.25rem;
}
.alert-warning {
  background-color: rgb(251 191 36 / 0.12);
  color: #fbbf24;
}
.alert-danger {
  background-color: rgb(255 255 255 / 0.05);
  color: var(--color-accent-crimson, #f87171);
  border-left: 4px solid var(--color-accent-crimson, #f87171);
}
.alert-success {
  background-color: rgb(255 255 255 / 0.05);
  color: var(--color-accent-emerald);
  border-left: 4px solid var(--color-accent-emerald);
}
.alert-info {
  background-color: rgb(255 255 255 / 0.05);
  color: rgb(255 255 255 / 0.8);
  border-left: 4px solid rgb(255 255 255 / 0.3);
}

code {
  color: rgb(255 255 255 / 0.85);
}

/* ---- Account menu: consolidates the 3 Manage/* nav links + Logout into a
   native <details>/<summary> disclosure, since the flat list started
   wrapping the nav onto two lines at every width once a third link (Personal
   data) was added — confirmed live via screenshot. ---- */
.account-menu {
  position: relative;
}
.account-menu > summary {
  list-style: none;
  cursor: pointer;
  color: rgb(255 255 255 / 0.8);
}
.account-menu > summary::-webkit-details-marker {
  display: none;
}
.account-menu > summary:hover {
  color: #fff;
}
.account-menu[open] > summary {
  color: #fff;
}
.account-menu__panel {
  position: absolute;
  right: 0;
  top: calc(100% + 0.5rem);
  z-index: 30;
  min-width: 12rem;
  background-color: rgb(20 24 28 / 0.98);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border: 1px solid rgb(255 255 255 / 0.15);
  box-shadow: var(--shadow-glass);
  border-radius: 0.75rem;
  padding: 0.4rem;
  font-size: 0.875rem;
}
.account-menu__panel a,
.account-menu__panel button {
  display: block;
  width: 100%;
  text-align: left;
  padding: 0.5rem 0.75rem;
  border-radius: 0.5rem;
  color: rgb(255 255 255 / 0.8);
  background: none;
  border: none;
}
.account-menu__panel a:hover,
.account-menu__panel button:hover {
  background-color: rgb(255 255 255 / 0.08);
  color: #fff;
}
.account-menu__panel hr {
  margin: 0.35rem 0.25rem;
  border-color: rgb(255 255 255 / 0.12);
}

.nav-order-count,
.nav-cap-status {
  border-radius: 9999px;
  padding: 0.2rem 0.55rem;
  background: rgb(52 211 153 / 0.14);
  color: #6ee7b7;
  font-size: 0.75rem;
  font-weight: 600;
  white-space: nowrap;
}
.nav-cap-status {
  background: rgb(251 191 36 / 0.14);
  color: #fcd34d;
}
.nav-cap-status.is-critical {
  background: rgb(240 71 91 / 0.14);
  color: #fb7185;
}

@media (max-width: 639px) {
  .app-nav {
    width: 100%;
    border-top: 1px solid rgb(255 255 255 / 0.1);
    padding-top: 0.75rem;
  }
  .account-menu__panel {
    right: auto;
    left: 0;
  }
}

/* text-white/40 fails WCAG AA contrast for normal-size text against this
   app's dark background — measured live (getComputedStyle + WCAG relative-
   luminance formula) at ~3.76:1 against the base rgb(11,12,16), below the
   4.5:1 required for text under 18px/14px-bold. This utility is used
   pervasively for captions/hints throughout the app, so a global override
   here (rather than hunting down every call site) is the safe fix — bumping
   to the same 50% opacity already used and visually vetted elsewhere in
   this codebase (e.g. text-white/50 hint text) comfortably clears AA at
   ~5.3:1, and increasing opacity can only ever improve contrast, never
   make any existing usage look wrong. */
.text-white\/40 {
  color: rgb(255 255 255 / 0.5);
}

/* Visual busy state for file-download links (see enableDownloadFeedback in
   ui.js) — a plain <a> has no native "disabled" state like a <button> does,
   so this supplies the same dimmed/inert feedback while a CSV/PDF export is
   generating. */
.is-busy {
  opacity: 0.6;
  pointer-events: none;
}

/* ---- Print stylesheet ----
   This is a dark-glass screen design with zero prior print handling — a
   baker hitting Ctrl+P on Sales or Orders (the two pages someone would
   plausibly want a paper copy of) would get white text on a dark
   background, which most browsers either print as solid black rectangles
   (wasting ink) or, if "background graphics" is off (a common default),
   drop the background and leave white text on white paper — invisible.
   Forces plain black-on-white output and hides chrome/controls that are
   meaningless on paper; the compliance disclaimer in the footer is
   deliberately kept, not hidden with the rest of the nav/footer chrome. */
@media print {
  * {
    background: #fff !important;
    color: #000 !important;
    box-shadow: none !important;
    text-shadow: none !important;
  }

  header, .skip-link, .empty-state__art, .status-ring, #complianceRing {
    display: none !important;
  }

  /* "Swipe/scroll the table..." only makes sense on a screen a reader can
     scroll — on paper every column is simply already there, so the stale
     instruction is just noise. Shared class rather than hunting down each
     page's own hint text/selector. */
  .scroll-hint {
    display: none !important;
  }

  main {
    padding: 0 !important;
    margin: 0 !important;
    max-width: none !important;
  }

  .glass-panel {
    border: 1px solid #999 !important;
    border-radius: 0 !important;
    backdrop-filter: none !important;
  }

  /* Real form controls and their action buttons are meaningless on paper —
     hides the manual-sale entry form, the Add-pickup-date form, and every
     per-row Confirm/Cancel/Remove/Delete action, leaving just the data. */
  button, input, select, textarea, form {
    display: none !important;
  }

  /* Exception: Products/Index's In-stock/Sold-out control is a <button>
     inside a <form> (it toggles on click), but unlike Confirm/Cancel/etc.
     it's also the ONLY place that status is shown at all — hiding it like
     every other action control would silently drop real data from the
     printout instead of just removing a meaningless control. Shown as
     plain text, not a pill, matching the plain "Inactive" span it sits
     next to in the same column. */
  form.print-status {
    display: inline !important;
  }
  form.print-status button {
    display: inline !important;
    background: none !important;
    border: none !important;
    padding: 0 !important;
    font: inherit !important;
  }

  table {
    border-collapse: collapse !important;
  }

  th, td {
    border-bottom: 1px solid #ccc !important;
  }
}
.authenticator-qr {
  display: block;
  width: min(240px, 100%);
  height: auto;
  aspect-ratio: 1;
}

.insights-heading {
  display: flex;
  align-items: end;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.5rem;
}

.insights-filters {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr)) auto;
  align-items: end;
  gap: 0.75rem;
}

.insight-cards {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1rem;
}

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

.insight-chart {
  width: 100%;
  height: clamp(18rem, 36vw, 23rem);
}

.insight-chart:not(.is-ready) {
  display: none;
}

.chart-empty {
  display: grid;
  min-height: 12rem;
  place-items: center;
  border: 1px dashed rgb(255 255 255 / 18%);
  border-radius: 0.75rem;
  color: rgb(255 255 255 / 55%);
  font-size: 0.875rem;
  text-align: center;
  padding: 1rem;
}

.chart-download__menu {
  position: absolute;
  z-index: 20;
  right: 0;
  display: grid;
  width: 9rem;
  margin-top: 0.35rem;
  padding: 0.35rem;
  border: 1px solid rgb(255 255 255 / 14%);
  border-radius: 0.65rem;
  background: #171a20;
  box-shadow: 0 12px 30px rgb(0 0 0 / 35%);
}

.chart-download__menu a,
.chart-download__menu button {
  width: 100%;
  border: 0;
  border-radius: 0.4rem;
  background: transparent;
  color: white;
  cursor: pointer;
  font: inherit;
  font-size: 0.75rem;
  padding: 0.55rem 0.65rem;
  text-align: left;
}

.chart-download__menu a:hover,
.chart-download__menu button:hover,
.chart-download__menu a:focus-visible,
.chart-download__menu button:focus-visible {
  background: rgb(255 255 255 / 10%);
}

.insight-table th,
.insight-table td {
  border-top: 1px solid rgb(255 255 255 / 10%);
  padding: 0.65rem 0.75rem;
  text-align: right;
  white-space: nowrap;
}

.insight-table th:first-child {
  position: sticky;
  left: 0;
  background: #16181e;
  text-align: left;
}

.insight-table thead th {
  color: rgb(255 255 255 / 55%);
  font-size: 0.75rem;
  font-weight: 600;
}

.chart-export-host {
  position: fixed;
  left: -10000px;
  top: 0;
  width: 1200px;
  height: 675px;
  pointer-events: none;
}

@media (max-width: 900px) {
  .insights-filters {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .insight-cards {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

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

@media (max-width: 420px) {
  .insights-filters,
  .insight-cards {
    grid-template-columns: minmax(0, 1fr);
  }

  .insight-chart {
    height: 18rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .insight-panel *,
  .insight-panel *::before,
  .insight-panel *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}
