:root {
  --color-bg: #faf7f2;
  --color-surface: #ffffff;
  --color-border: #e6e0d6;
  --color-text: #22221f;
  --color-muted: #6b6b66;
  --color-dark: #1b1b22;
  --color-dark-contrast: #ffffff;
  --color-accent: #d9812f;
  --color-accent-dark: #b96a24;
  --color-accent-contrast: #ffffff;
  --color-danger: #b3261e;
  --color-success: #1e6b3c;
  --radius: 4px;
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 16px;
  --space-4: 24px;
  --space-5: 40px;
}
* { box-sizing: border-box; }
body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  color: var(--color-text);
  background: var(--color-bg);
  /* Sticky footer: body is a full-viewport-height column (header, nav,
     page-header, main, footer - see layout.html, all direct children with
     no wrapper), and main grows to fill whatever space the rest don't
     use, so the footer sits at the bottom of the viewport on a short page
     (e.g. an empty cart) instead of floating up right under the content -
     while still being pushed down normally, not fixed, once real content
     is taller than the viewport. */
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
h1, h2, h3 { font-weight: 700; letter-spacing: 0.01em; }
a { color: var(--color-accent-dark); }
main { flex: 1 0 auto; }
.container { max-width: 1200px; margin: 0 auto; padding: var(--space-4); }
/* Used by the presentation home page (see HomeHandler.Show's FullWidth
   flag) so blocks like Hero/Newsletter can go truly edge-to-edge instead
   of being capped at .container's 1200px, unlike every shop page. */
.container-full { max-width: none; margin: 0; padding: 0; }
/* Standard visually-hidden-but-accessible utility: removes an element from
   the visual layout entirely (unlike display:none/visibility:hidden, which
   can also drop it from the accessibility tree in some browser/AT
   combinations) while keeping its text readable by screen readers and by
   JS (getBoundingClientRect() reports a 1x1 box, but .textContent is
   completely unaffected). */
.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;
}

/* ---- Header ---- */
/* Each of the three header bars (site-header, site-nav, page-header) lays its
   content out in three zones - left/center/right - so a backoffice-configured
   per-block alignment (Header page) places each individual content block
   (store name, cart/account, menu, search, page title, ...) in the zone it
   belongs to, not just the bar as a whole. Left/right zones share the
   remaining space equally and push their content to their own edge; the
   center zone sizes to its content and sits between them - the standard
   three-part header layout. An empty zone just collapses to zero width. */
/* min-width: 0 overrides the flex-item default of min-width: auto (which
   resolves to the content's min-content size) - without it, a zone can
   never actually shrink below its own content's width no matter what
   flex-shrink says, so flex-wrap on a child (e.g. .site-nav-menu, when a
   zone holds a long menu) never gets the chance to engage and the row
   overflows horizontally instead. This is what makes the layout actually
   responsive at any viewport width, not just below a specific breakpoint -
   see the max-width: 700px block further down for the separate "stack the
   3 zones into their own full-width rows" polish on top of this. */
.hdr-zone { display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; min-width: 0; }
.hdr-zone-left { flex: 1 1 auto; justify-content: flex-start; }
/* flex-shrink: 1 (not the 0 a plain "size to content" zone would default
   to) is deliberate - see the min-width: 0 comment above for why. */
.hdr-zone-center { flex: 0 1 auto; justify-content: center; }
.hdr-zone-right { flex: 1 1 auto; justify-content: flex-end; }

.site-header { background: var(--site-header-bg, var(--color-dark)); color: var(--site-header-text, var(--color-dark-contrast)); }
.site-header-inner {
  padding: var(--space-3) var(--space-4);
  display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap;
}
.brand {
  font-weight: 700; font-size: 1.4rem; text-transform: uppercase; letter-spacing: 0.08em;
  text-decoration: none; color: var(--site-header-text, var(--color-dark-contrast)); white-space: nowrap;
}
.search-form { display: flex; gap: 0; width: 260px; }
.search-form input {
  flex: 1; min-width: 0; padding: var(--space-2) var(--space-3); border: none; border-radius: var(--radius) 0 0 var(--radius);
  font-size: 0.95rem;
}
.search-form button {
  border-radius: 0 var(--radius) var(--radius) 0; padding: var(--space-2) var(--space-3);
}
.shop-status { display: flex; gap: var(--space-2); flex-wrap: wrap; }
.status-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px var(--space-2); border-radius: 999px;
  font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
  white-space: nowrap;
}
.status-pill::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.status-open { background: rgba(46, 184, 108, 0.18); color: #4ade80; }
.status-closed { background: rgba(255, 90, 90, 0.18); color: #ff8b83; }
.header-nav { display: flex; gap: var(--space-4); align-items: center; flex-wrap: wrap; }
.header-nav a, .header-nav button {
  color: var(--site-header-text, var(--color-dark-contrast)); text-decoration: none; background: none; border: none;
  font-size: 0.9rem; cursor: pointer; padding: 0; font-family: inherit;
}
.header-nav a:hover, .header-nav button:hover { color: var(--color-accent); }
#cart-count {
  display: inline-block; background: var(--color-accent); color: var(--color-accent-contrast);
  border-radius: 999px; padding: 0 6px; font-size: 0.75rem; margin-left: 4px;
}
/* Icon links sit inside .header-nav (see header_social_links in
   header.html), so they already inherit its color/hover rules above -
   fill="currentColor" on each SVG (rather than a hardcoded color) is what
   makes that inheritance actually work. */
.header-social { display: flex; gap: var(--space-3); align-items: center; }
.header-social svg { display: block; }
/* Sits inside .header-nav (see header_language_switcher in header.html), so
   its links already inherit the color/hover rules above - only the active
   locale needs its own treatment. */
.lang-switcher { display: flex; gap: var(--space-2); align-items: center; text-transform: uppercase; font-size: 0.8rem; }
.lang-switcher a.active { color: var(--color-accent); font-weight: 700; text-decoration: none; }

.site-nav {
  background: var(--site-nav-bg, var(--color-accent)); color: var(--site-nav-text, var(--color-accent-contrast));
  font-family: var(--menu-font-family, inherit);
}
.site-nav-inner {
  padding: 0 var(--space-4);
  display: flex; gap: var(--space-4); align-items: center; flex-wrap: wrap;
}
/* --menu-align (left/center/right, set from Menu > Style) positions the menu
   items within their own box, independent of which zone (see .hdr-zone above)
   that box itself has been placed in. */
.site-nav-menu {
  display: flex; gap: var(--space-4); align-items: center; flex-wrap: wrap;
  justify-content: var(--menu-align, flex-start);
}
.site-nav a {
  display: inline-block; padding: var(--space-3) 0; color: var(--menu-text-color, var(--color-accent-contrast));
  text-decoration: none; text-transform: uppercase; font-size: var(--menu-text-size, 0.85rem); font-weight: 600;
  letter-spacing: 0.05em;
}
.site-nav a:hover { color: var(--color-dark); }
/* A menu item flagged "show as button" - a filled call-to-action pill on the
   accent nav bar. */
.site-nav a.site-nav-button {
  align-self: center; padding: 7px 16px; border-radius: var(--radius);
  background: var(--menu-button-bg, var(--color-dark)); color: var(--menu-button-text, var(--color-dark-contrast));
  text-transform: none; letter-spacing: 0;
}
.site-nav a.site-nav-button:hover { opacity: 0.9; }
.site-nav .search-form { padding: var(--space-2) 0; }
.site-nav .search-form input, .site-nav .search-form button { color: inherit; }

/* The header/nav's 3-zone layout (see .hdr-zone above) is fully backoffice-
   configurable - any zone can hold "menu"/"search"/etc. .hdr-zone-center's
   flex: 0 0 auto (sized to its own content, not the shared flex row) is
   deliberate on desktop, but it also means that zone never shrinks, so if
   it holds content wider than the viewport (a menu with several items is
   the common case) the row overflows horizontally instead of the inner
   flex-wrap ever kicking in - wrapping only engages once a flex item's own
   width is actually constrained, and flex-shrink:0 prevents that. Below
   this breakpoint, stack the zones into their own full-width rows instead
   (each one free to wrap its own contents within 100% of the viewport)
   rather than trying to preserve the desktop 3-zone side-by-side layout on
   a phone-width screen. */
@media (max-width: 700px) {
  .site-header-inner, .site-nav-inner { flex-direction: column; align-items: stretch; gap: var(--space-2); }
  .hdr-zone-left, .hdr-zone-center, .hdr-zone-right { flex: 1 1 auto; width: 100%; justify-content: center; }
  .site-nav-menu { justify-content: center; }
  .search-form { width: 100%; }
}

.page-header { background: var(--page-header-bg, #f1ebe0); color: var(--page-header-text, var(--color-text)); border-bottom: 1px solid var(--color-border); }
.page-header-inner {
  max-width: 1200px; margin: 0 auto; padding: var(--space-3) var(--space-4);
  display: flex; align-items: center;
}
.page-header h1 { margin: 0; font-size: 1.15rem; }
.page-header a { font-size: 0.9rem; }

/* ---- Marquee (Header > Marquee) ----
   .marquee-scroll: all lines flow in one row, sliding right-to-left, looping.
   .marquee-fade / .marquee-slide: lines stack in place and cycle one at a
   time. The track's data-lines (2 or 3 - "1" always renders as
   .marquee-static instead, see ResolveHeaderStyle) picks a duration/delay/
   keyframe set sized for that exact line count, so each line's visible
   window tiles the cycle with NO dead gap between handoffs - a duration
   fixed for 3 lines but used with only 2 would leave nothing visible for a
   large fraction of every cycle.
   .marquee-static (only-one-line fallback for fade/slide): no animation. */
.marquee {
  overflow: hidden; background: var(--page-header-bg, #f1ebe0); border-bottom: 1px solid var(--color-border);
  color: var(--marquee-text-color, var(--page-header-text, var(--color-text)));
  font-size: var(--marquee-text-size, inherit);
}
.marquee-inner { max-width: 1200px; margin: 0 auto; padding: var(--space-2) var(--space-4); }

.marquee-scroll .marquee-inner { white-space: nowrap; }
.marquee-scroll .marquee-track { display: inline-flex; gap: var(--space-5); padding-left: 100%; animation: marquee-scroll-move 22s linear infinite; }
.marquee-scroll .marquee-line { white-space: nowrap; }
@keyframes marquee-scroll-move {
  from { transform: translateX(0); }
  to { transform: translateX(-100%); }
}

.marquee-fade .marquee-inner, .marquee-slide .marquee-inner { min-height: 1.4em; }
.marquee-fade .marquee-track, .marquee-slide .marquee-track { position: relative; height: 1.4em; }
.marquee-fade .marquee-line, .marquee-slide .marquee-line {
  position: absolute; left: 0; right: 0; opacity: 0;
  animation-timing-function: ease-in-out; animation-iteration-count: infinite;
}

.marquee-track[data-lines="2"] .marquee-line { animation-duration: 8s; }
.marquee-track[data-lines="2"] .marquee-line:nth-child(1) { animation-delay: 0s; }
.marquee-track[data-lines="2"] .marquee-line:nth-child(2) { animation-delay: 4s; }
.marquee-fade .marquee-track[data-lines="2"] .marquee-line { animation-name: marquee-fade-cycle-2; }
.marquee-slide .marquee-track[data-lines="2"] .marquee-line { animation-name: marquee-slide-cycle-2; }

.marquee-track[data-lines="3"] .marquee-line { animation-duration: 12s; }
.marquee-track[data-lines="3"] .marquee-line:nth-child(1) { animation-delay: 0s; }
.marquee-track[data-lines="3"] .marquee-line:nth-child(2) { animation-delay: 4s; }
.marquee-track[data-lines="3"] .marquee-line:nth-child(3) { animation-delay: 8s; }
.marquee-fade .marquee-track[data-lines="3"] .marquee-line { animation-name: marquee-fade-cycle-3; }
.marquee-slide .marquee-track[data-lines="3"] .marquee-line { animation-name: marquee-slide-cycle-3; }

/* Each line's own animation-duration equals the FULL cycle (not just its
   slot); the keyframe below occupies only the first 1/2 or 1/3 of that
   cycle (matching the animation-delay stagger above), then holds invisible
   for the rest - so N staggered copies exactly tile the cycle. */
@keyframes marquee-fade-cycle-2 {
  0% { opacity: 0; }
  3%, 47% { opacity: 1; }
  50%, 100% { opacity: 0; }
}
@keyframes marquee-fade-cycle-3 {
  0% { opacity: 0; }
  2%, 31.33% { opacity: 1; }
  33.33%, 100% { opacity: 0; }
}
@keyframes marquee-slide-cycle-2 {
  0% { opacity: 0; transform: translateY(100%); }
  3%, 47% { opacity: 1; transform: translateY(0); }
  50%, 100% { opacity: 0; transform: translateY(-100%); }
}
@keyframes marquee-slide-cycle-3 {
  0% { opacity: 0; transform: translateY(100%); }
  2%, 31.33% { opacity: 1; transform: translateY(0); }
  33.33%, 100% { opacity: 0; transform: translateY(-100%); }
}

.marquee-static .marquee-inner { min-height: 0; }
.marquee-static .marquee-track { position: static; height: auto; }
.marquee-static .marquee-line { position: static; opacity: 1; animation: none; }

/* .marquee-half-speed (Marquee > Half speed) doubles every duration/delay
   above - each selector adds the .marquee-half-speed class on top of the
   base rule's own chain, so it always wins on specificity regardless of
   source order, and the delay stagger stays proportional to the doubled
   duration (still tiles the cycle with no dead gap). */
.marquee-half-speed.marquee-scroll .marquee-track { animation-duration: 44s; }
.marquee-half-speed .marquee-track[data-lines="2"] .marquee-line { animation-duration: 16s; }
.marquee-half-speed .marquee-track[data-lines="2"] .marquee-line:nth-child(1) { animation-delay: 0s; }
.marquee-half-speed .marquee-track[data-lines="2"] .marquee-line:nth-child(2) { animation-delay: 8s; }
.marquee-half-speed .marquee-track[data-lines="3"] .marquee-line { animation-duration: 24s; }
.marquee-half-speed .marquee-track[data-lines="3"] .marquee-line:nth-child(1) { animation-delay: 0s; }
.marquee-half-speed .marquee-track[data-lines="3"] .marquee-line:nth-child(2) { animation-delay: 8s; }
.marquee-half-speed .marquee-track[data-lines="3"] .marquee-line:nth-child(3) { animation-delay: 16s; }

/* ---- Flash ---- */
.flash { padding: var(--space-3); border-radius: var(--radius); margin-bottom: var(--space-3); }
.flash-error { background: #fbe9e7; color: var(--color-danger); }
.flash-success { background: #e4f5e9; color: var(--color-success); }

/* ---- Cart error toast (see cart.js's showErrorToast) ---- */
.cart-error-toast {
  position: fixed; left: 50%; bottom: var(--space-4); z-index: 1000;
  display: flex; align-items: center; gap: var(--space-3);
  max-width: calc(100vw - var(--space-4) * 2);
  padding: var(--space-3) var(--space-4); border-radius: var(--radius);
  background: #fbe9e7; color: var(--color-danger); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
  transform: translate(-50%, calc(100% + var(--space-4)));
  opacity: 0; transition: transform 0.2s ease, opacity 0.2s ease; pointer-events: none;
}
.cart-error-toast--visible { transform: translate(-50%, 0); opacity: 1; pointer-events: auto; }
.cart-error-toast-text { font-size: 0.9rem; }
.cart-error-toast-close {
  flex-shrink: 0; border: none; background: none; color: inherit; cursor: pointer;
  font-size: 1.2rem; line-height: 1; padding: 0 var(--space-1);
}

/* ---- Home (presentation-only page, built from home_page_block rows) ---- */
.hero {
  background: linear-gradient(135deg, var(--color-dark) 0%, #2a2a35 100%);
  background-size: cover; background-position: center;
  color: var(--color-dark-contrast); text-align: center;
  min-height: 420px; border-radius: var(--radius); margin: var(--space-4) 0;
  position: relative;
  /* A hero with no explicit HeroBlockData.HeightVh just uses this as a
     floor (content can still push it taller); a background image/video
     always crops to whatever height results, via background-size/
     .hero-video's object-fit:cover below. HeightVh (home_block_hero.html)
     overrides this with an inline height + min-height:0 instead. */
  /* Needed for .hero-video below: a background-image never overflows its
     box regardless of border-radius, but the <video> is a normal
     absolutely-positioned child element, which would otherwise poke past
     rounded corners. Harmless for the no-video case too. */
  overflow: hidden;
}
/* A background-image (set inline per-block) or background video both need
   a dark overlay so text stays legible over busy footage/a photo - the
   linear-gradient base above already covers a block with neither. */
.hero[style*="background-image"]::before, .hero-has-video::before {
  content: ""; position: absolute; inset: 0;
  /* Matches whatever border-radius .hero itself ends up with (rounded when
     boxed, square when full-bleed edge-to-edge - see home_block_hero.html)
     rather than a fixed value, since a pseudo-element can't be targeted by
     the host element's own inline style. */
  border-radius: inherit;
  background: linear-gradient(135deg, rgba(0,0,0,0.55) 0%, rgba(0,0,0,0.35) 100%);
  /* Above the video (z-index:0, see .hero-video) so the dark overlay
     actually shows over it, but still below the heading/tagline/CTA
     overlay elements (z-index:1). */
  z-index: 1;
}
/* Fills .hero edge-to-edge behind the heading/tagline/CTA overlay (and
   behind the dark ::before overlay above it) - autoplay/muted/loop/
   playsinline (set in home_block_hero.html) make it behave as a silent,
   looping background clip rather than something needing playback controls. */
.hero-video {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; z-index: 0;
}
/* Heading/tagline/CTA are each independently positioned (left/top set
   inline, per-block, from the backoffice's drag-to-position widget - see
   home_page_blocks.js) rather than flowing in a centered column, so a hero
   with all three overlapping the same spot is possible but not the default
   (see HeroBlockData.applyPositionDefaults for the spread-out fallback). */
.hero-el { position: absolute; transform: translate(-50%, -50%); max-width: 90%; z-index: 1; }
.hero h1.hero-el { margin: 0; font-size: 2.2rem; text-transform: uppercase; letter-spacing: 0.05em; }
.hero-tagline.hero-el { margin: 0; font-size: 1.1rem; color: #d8d5cf; }
.hero-cta.hero-el {
  display: inline-block; background: var(--color-accent); color: var(--color-accent-contrast);
  padding: var(--space-3) var(--space-5); border-radius: var(--radius); text-decoration: none;
  font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; font-size: 0.9rem;
  white-space: nowrap;
}
.hero-cta.hero-el:hover { background: var(--color-accent-dark); }

/* Mobile: a boxed/narrow hero (ImageWidth < 100, see home_block_hero.html)
   would look broken on a phone regardless of its desktop alignment, so
   force it back to full-bleed - !important is needed here specifically
   because it's counteracting an *inline* width/margin the block sets per-
   instance, which a plain media-query rule can never outrank. */
@media (max-width: 700px) {
  .hero { width: 100% !important; margin-left: 0 !important; margin-right: 0 !important; }
}

/* Slider block (home_block_slider.html) - a rotating carousel of
   image/video slides (see home_slider.js), each simpler than a hero:
   a centered heading/tagline/CTA column instead of freely positioned
   overlay elements. Shares .hero's box treatment (dark gradient floor,
   rounded/cropped) so a slider with no slides yet (or slides with no
   image/video) still looks intentional rather than an empty box. */
.slider {
  background: linear-gradient(135deg, var(--color-dark) 0%, #2a2a35 100%);
  min-height: 420px; border-radius: var(--radius); margin: var(--space-4) 0;
  position: relative; overflow: hidden;
}
.slider-track { position: relative; width: 100%; height: 100%; min-height: inherit; }
/* Each slide stacks on top of the others; only .active is visible/
   interactive - a plain opacity crossfade (no JS-driven layout changes)
   keeps home_slider.js's job to just toggling one class per transition. */
.slider-slide {
  position: absolute; inset: 0; background-size: cover; background-position: center;
  opacity: 0; visibility: hidden; transition: opacity 0.6s ease;
  display: flex; align-items: center; justify-content: center; text-align: center;
  color: var(--color-dark-contrast);
}
.slider-slide.active { opacity: 1; visibility: visible; z-index: 1; }
.slider-video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 0; }
.slider-content { position: relative; z-index: 2; max-width: 90%; padding: var(--space-4); }
.slider-content h2 { margin: 0 0 var(--space-2); font-size: 2rem; text-transform: uppercase; letter-spacing: 0.05em; }
.slider-content p { margin: 0 0 var(--space-3); font-size: 1.1rem; color: #d8d5cf; }
.slider-cta {
  display: inline-block; background: var(--color-accent); color: var(--color-accent-contrast);
  padding: var(--space-3) var(--space-5); border-radius: var(--radius); text-decoration: none;
  font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; font-size: 0.9rem;
}
.slider-cta:hover { background: var(--color-accent-dark); }
.slider-arrow {
  position: absolute; top: 50%; transform: translateY(-50%); z-index: 3;
  background: rgba(0,0,0,0.35); color: #fff; border: 0; border-radius: 50%;
  width: 40px; height: 40px; font-size: 1.1rem; cursor: pointer;
}
.slider-arrow:hover { background: rgba(0,0,0,0.55); }
.slider-prev { left: var(--space-3); }
.slider-next { right: var(--space-3); }
.slider-dots { position: absolute; bottom: var(--space-3); left: 50%; transform: translateX(-50%); z-index: 3; display: flex; gap: var(--space-2); }
.slider-dot {
  width: 10px; height: 10px; border-radius: 50%; border: 0; background: rgba(255,255,255,0.5); cursor: pointer; padding: 0;
}
.slider-dot.active { background: #fff; }
@media (max-width: 700px) {
  .slider { width: 100% !important; margin-left: 0 !important; margin-right: 0 !important; }
}

.home-image-text {
  display: grid; grid-template-columns: var(--iw, 50%) 1fr; gap: var(--space-5); align-items: center;
  margin: var(--space-5) 0;
}
/* Image alignment (backoffice "Image alignment" field): the image is always
   the first element in the markup, in the --iw-wide track. For "right", flip
   which track is --iw-wide AND reorder the two children (grid auto-placement
   honors `order`, same as flexbox) so the image lands in the now-second,
   --iw-wide track while the text takes the first, 1fr track. */
.home-image-text--right { grid-template-columns: 1fr var(--iw, 50%); }
.home-image-text--right .home-image-text-media { order: 2; }
.home-image-text--right .home-image-text-content { order: 1; }
/* .home-image-text-media is shared by the plain <img> and the optional
   <video> (see home_block_image_text.html) - display:block only matters for
   the video (an <img> is already block-level via width:100%, but a <video>
   defaults to inline, which would leave a few px of baseline gap below it).
   object-fit:cover only actually does anything once HeightVh sets an
   explicit height (inline, per-block) that no longer matches the media's
   natural aspect ratio - harmless as a permanent rule otherwise, same as
   .hero-video's own always-on object-fit:cover above. */
.home-image-text-media { display: block; width: 100%; border-radius: var(--radius); object-fit: cover; }
.home-image-text-content h2 { margin: 0 0 var(--space-3); font-size: 1.5rem; }
/* Same !important reasoning as the boxed-hero override above: --iw is set
   inline per-block, which would otherwise always beat this stacking rule
   regardless of media query. */
@media (max-width: 700px) { .home-image-text { grid-template-columns: 1fr !important; } }

.home-newsletter {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-4);
  background: var(--color-accent); color: var(--color-accent-contrast);
  padding: var(--space-4) var(--space-5); border-radius: var(--radius); margin: var(--space-5) 0;
}
/* Grow the heading/subtext side to fill the space the form leaves, so its
   inline text-align (the block's horizontal content alignment control, set
   per-block - see home_block_newsletter.html) is actually visible; a
   shrink-to-fit box would render every alignment identically. */
.home-newsletter-text { flex: 1 1 auto; }
.home-newsletter h2 { margin: 0 0 var(--space-1); font-size: 1.2rem; }
.home-newsletter p { margin: 0; }
/* flex: 1 1 auto + min-width: 0 (here and on the email input below) let
   the whole form shrink to fit any container width - the email input
   previously had a hard min-width: 220px floor that, combined with the
   button beside it, overflowed a narrow-viewport .home-newsletter (see
   .hdr-zone's identical min-width: 0 fix above for why a flex item's
   default min-width: auto floors shrinking at its content's own size
   otherwise). */
.home-newsletter-form { display: flex; gap: var(--space-2); flex: 1 1 auto; min-width: 0; }
.home-newsletter-form input[type="email"] { padding: var(--space-2) var(--space-3); border-radius: var(--radius); border: none; flex: 1 1 auto; min-width: 0; }
.home-newsletter-form button {
  background: var(--color-dark); color: var(--color-dark-contrast); border: none;
  padding: var(--space-2) var(--space-4); border-radius: var(--radius); font-weight: 700; cursor: pointer;
}

.home-text { margin: var(--space-5) 0; }
.home-text h2 { margin: 0 0 var(--space-3); font-size: 1.3rem; }

/* Outer per-block width wrapper for the "block width" control every block
   type exposes (see pages/home.html + home_page_blocks.js). Width is set
   inline per-block only when < 100% (a full-width block renders with no
   wrapper style at all, unchanged from before this control existed). On a
   phone a narrowed block would just waste horizontal space, so force it
   back to full-width - same inline-outranking !important reasoning as the
   boxed hero / image_text mobile overrides above. */
@media (max-width: 700px) { .home-block { width: 100% !important; } }

/* Home page block body text, authored via the backoffice's Quill editor -
   its toolbar covers p/strong/em/u/s/a/ul/ol/h2/h3/blockquote plus inline
   color/background/align (see richtext.SanitizeHTML for what actually
   survives), so this just needs sane spacing/link-color for each, not a
   full rich-text stylesheet. */
.richtext-content img { max-width: 100%; height: auto; }
/* Image alignment set from the About Page editor's align buttons (see
   about_page.js's custom "imageAlign" Quill format) - kept in sync with the
   backoffice's own editor-preview rules in ntos_webshop_core/ui's tables.css. */
.richtext-content .ql-img-left { float: left; margin: 0 var(--space-3) var(--space-2) 0; max-width: 45%; }
.richtext-content .ql-img-right { float: right; margin: 0 0 var(--space-2) var(--space-3); max-width: 45%; }
.richtext-content .ql-img-center { display: block; margin: 0 auto var(--space-2); max-width: 100%; }
.richtext-content p { margin: 0 0 var(--space-3); }
.richtext-content p:last-child { margin-bottom: 0; }
.richtext-content a { color: inherit; text-decoration: underline; }
.richtext-content ul, .richtext-content ol { margin: 0 0 var(--space-3); padding-left: var(--space-4); }
.richtext-content h1, .richtext-content h2, .richtext-content h3 { margin: 0 0 var(--space-2); }
.richtext-content blockquote {
  margin: 0 0 var(--space-3); padding-left: var(--space-3); border-left: 3px solid var(--color-border); color: var(--color-muted);
}
/* Embedded map (About and Contact pages both use this) - a fixed-ratio box
   (16:9) so the iframe never collapses to 0 height (an iframe has no
   intrinsic size of its own). */
.location-map { position: relative; width: 100%; padding-top: 42%; margin-top: var(--space-4); border-radius: var(--radius); overflow: hidden; }
.location-map iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

/* Contact page opts out of the shared width-capped .container (FullWidth,
   same escape hatch the embed/gallery pages use) so its content spans the
   full viewport instead of being boxed into .container's 1200px cap - but
   unlike an edge-to-edge iframe, this is readable text, so it keeps
   .container's own padding value here rather than sitting flush against
   the screen edge. */
.contact-page-content { padding: var(--space-4); }

/* Embedded content (see MenuPageHandler's PDF embed and EmbedPageHandler's
   external-URL embed - same shared markup/styling, generic naming since
   it's not PDF-specific). The page opts out of the shared width-capped
   .container (FullWidth, same escape hatch the gallery page's modern style
   uses) so the iframe gets the full viewport edge-to-edge rather than
   being boxed into .container's ~24px padding and 1200px cap, which on a
   phone left it looking cramped ("mobile size"). A tried-first alternative
   - keeping .container and breaking just this element out with negative
   margins - actually shrank .container itself on this page's flex-column
   body layout (align-items: normal computes to stretch only for
   non-negative cross-axis margins), so FullWidth is both simpler and the
   mechanism this codebase already proves out elsewhere. Only the fallback
   text gets its own padding, so it doesn't sit flush against the screen
   edge - the iframe itself stays genuinely edge-to-edge. */
.embed-container { width: 100%; height: 85vh; min-height: 480px; overflow: hidden; }
.embed-frame { display: block; width: 100%; height: 100%; border: 0; }
.embed-fallback { color: var(--color-muted); font-size: 0.9rem; margin: var(--space-2) var(--space-4) 0; }

/* ---- Listing ---- */

.category-filter { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-4); }
.category-filter-link {
  display: inline-block; padding: var(--space-1) var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-border); border-radius: 999px;
  color: var(--color-text); text-decoration: none; font-size: 0.9rem;
}
.category-filter-link:hover { border-color: var(--color-accent); }
.category-filter-link.active {
  background: var(--color-accent); border-color: var(--color-accent); color: var(--color-accent-contrast);
}

.product-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: var(--space-4); }
.product-card {
  display: flex; flex-direction: column; background: var(--color-surface);
  border: 1px solid var(--color-border); border-radius: var(--radius);
  overflow: hidden; transition: box-shadow 0.15s ease, transform 0.15s ease;
}
.product-card:hover { box-shadow: 0 4px 14px rgba(27, 27, 34, 0.12); transform: translateY(-2px); }
.product-card-link { display: flex; flex-direction: column; flex: 1; text-decoration: none; color: var(--color-text); }
.product-card-image {
  aspect-ratio: 1 / 1; background: #ededea; display: flex; align-items: center; justify-content: center;
  overflow: hidden;
}
.product-card-image img { width: 100%; height: 100%; object-fit: cover; }
.product-card-body { padding: var(--space-3); display: flex; flex-direction: column; gap: var(--space-1); flex: 1; }
.product-card-name { font-weight: 600; font-size: 0.95rem; }
.product-card-price { color: var(--color-accent-dark); font-weight: 700; margin-top: auto; }
.product-card-cta {
  display: block; width: 100%; text-align: center; background: var(--color-accent); color: var(--color-accent-contrast);
  padding: var(--space-2); font-size: 0.85rem; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.03em; margin-top: var(--space-2); border: none; cursor: pointer; font-family: inherit;
  text-decoration: none;
}
.product-card-cta:disabled { opacity: 0.6; cursor: default; }
.product-card .quantity-field { margin: var(--space-2) var(--space-3) 0; }

/* ---- Product list style (Settings > Product list style) - compact
   horizontal rows instead of the grid of cards, independent of the color
   Theme: same .product-card markup, just re-laid-out (image left, name/
   price middle, quick-add circle on the right). */
.product-grid-list { display: flex; flex-direction: column; gap: var(--space-3); }
.product-grid-list .product-card { flex-direction: row; align-items: center; }
.product-grid-list .product-card:hover { transform: none; }
.product-grid-list .product-card-link { flex-direction: row; align-items: center; gap: var(--space-3); padding: var(--space-3); }
.product-grid-list .product-card-image { width: 88px; height: 88px; aspect-ratio: unset; flex-shrink: 0; border-radius: var(--radius-sm); }
.product-grid-list .product-card-body { padding: 0; gap: 0; }
.product-grid-list .quantity-field { flex-direction: row; align-items: center; gap: var(--space-2); margin: 0 var(--space-2) 0 0; }
.product-grid-list .quantity-field label { display: none; }
.product-grid-list .product-card-cta-view {
  flex-shrink: 0; width: auto; margin: 0 var(--space-3) 0 0; padding: var(--space-1) var(--space-3);
  border-radius: var(--radius); font-size: 0.75rem; white-space: nowrap;
}
.product-grid-list .product-card-cta-add {
  flex-shrink: 0; width: 44px; height: 44px; margin: 0 var(--space-3) 0 0; padding: 0;
  border-radius: 50%; font-size: 0; display: flex; align-items: center; justify-content: center;
}
.product-grid-list .product-card-cta-add::before { content: "+"; font-size: 1.5rem; line-height: 1; }
@media (max-width: 480px) {
  .product-grid-list .product-card-link { flex-wrap: wrap; }
  .product-grid-list .product-card-image { width: 64px; height: 64px; }
}

/* ---- Gallery (/gallery) - a flat photo grid, same card visual language as
   product cards but simpler (no link/CTA, just an image + optional
   caption). */
.gallery-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: var(--space-4); }
.gallery-item {
  margin: 0; background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius);
  overflow: hidden; transition: box-shadow 0.15s ease, transform 0.15s ease;
}
.gallery-item:hover { box-shadow: 0 4px 14px rgba(27, 27, 34, 0.12); transform: translateY(-2px); }
.gallery-item img { display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
.gallery-item figcaption { padding: var(--space-2) var(--space-3); font-size: 0.9rem; color: var(--color-muted); }

/* ---- Gallery "Modern" style (Settings > Gallery style) - a full-bleed dark,
   editorial layout, independent of the storefront theme (fixed dark
   background regardless of which theme is active, deliberately - the whole
   point of offering it is a distinct alternative look). Renders inside
   .container-full (GalleryHandler.Show sets FullWidth for this style). Every
   3rd item spans both columns for the alternating "wide photo, then a pair"
   rhythm; images render in full color, with a subtle zoom on hover. */
.gallery-modern { background: #0a0a0a; color: #f5f5f5; padding: var(--space-5) var(--space-4); }
.gallery-modern-empty { color: rgba(255, 255, 255, 0.6); text-align: center; padding: var(--space-5) 0; }
.gallery-modern-grid {
  max-width: 1400px; margin: 0 auto; display: grid;
  grid-template-columns: repeat(2, 1fr); gap: var(--space-4);
}
.gallery-modern-item { margin: 0; }
.gallery-modern-item:nth-child(3n+1) { grid-column: span 2; }
.gallery-modern-image { overflow: hidden; background: #151515; }
.gallery-modern-image img {
  display: block; width: 100%; aspect-ratio: 16 / 10; object-fit: cover;
  transform: scale(1);
  transition: transform 0.4s ease;
}
.gallery-modern-item:hover .gallery-modern-image img { transform: scale(1.03); }
.gallery-modern-item figcaption {
  margin-top: var(--space-2); padding-top: var(--space-2); border-top: 1px solid rgba(255, 255, 255, 0.2);
  font-size: 0.8rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em;
  color: rgba(255, 255, 255, 0.85); text-align: center;
}
@media (max-width: 640px) {
  .gallery-modern-grid { grid-template-columns: 1fr; }
  .gallery-modern-item:nth-child(3n+1) { grid-column: span 1; }
}

/* ---- Quantity stepper (product cards + detail page + mix-and-match option
   items) - .quantity-input is the whole-product quantity field specifically
   (also used by cart.js to tell it apart from a mix-and-match item input,
   which is identified by name instead), but the input styling below applies
   to any number input inside a .quantity-stepper regardless of that class,
   so mix-and-match steppers get the same look without needing the class.
   The stepper itself defaults to filling its container (product cards and
   the vertical option-image-btn layout both want that); a mix-and-match
   qty-row stepper and the product detail page's own field both go compact/
   content-sized instead (see .quantity-field-detail below) - the detail
   page's info column is wide enough that a full-width stepper there looked
   stretched, unlike a narrow product card. ---- */
.quantity-field { display: flex; flex-direction: column; gap: var(--space-1); margin-bottom: var(--space-3); }
.quantity-field label { font-size: 0.85rem; font-weight: 600; color: var(--color-muted); }
.quantity-stepper {
  display: flex; width: 100%;
  border: 1px solid var(--color-border); border-radius: var(--radius); overflow: hidden;
}
.quantity-stepper input[type="number"] {
  flex: 1; min-width: 0; width: 100%; border: none; border-radius: 0; padding: var(--space-2) var(--space-3);
  font-size: 0.95rem; font-family: inherit;
}
.quantity-stepper input[type="number"]::-webkit-inner-spin-button,
.quantity-stepper input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.quantity-stepper input[type="number"] { -moz-appearance: textfield; }
.qty-btn {
  flex-shrink: 0; width: 36px; padding: 0; border: none; border-left: 1px solid var(--color-border);
  border-radius: 0; background: var(--color-surface); color: var(--color-text);
  cursor: pointer; font-size: 1.1rem; font-weight: 400; text-transform: none; line-height: 1;
}
.qty-btn:hover { background: var(--color-bg); }
/* A "+"/"-" stepper button disabled by enforceMixAndMatchButtonStates
   (cart.js) - "+" once this item's own max_quantity is reached or the
   group's shared max_select is fully allocated, "-" once the item is
   already at 0. */
.qty-btn:disabled { opacity: 0.4; cursor: default; }
.qty-btn:disabled:hover { background: var(--color-surface); }
/* A stepper button that's the first child (mix-and-match's and the
   product detail page's whole-product stepper both put "-" before the
   input; product cards still put both buttons after it) needs its
   divider on the right instead of the left. */
.quantity-stepper .qty-btn:first-child { border-left: none; border-right: 1px solid var(--color-border); }
.qty-row .quantity-stepper {
  display: inline-flex; width: auto; vertical-align: middle; margin-right: var(--space-2);
}
.qty-row .quantity-stepper input[type="number"] { flex: none; width: 3em; text-align: center; }
/* .quantity-field's flex column stretches children to full width by
   default (align-items: stretch) - align-self overrides that just for the
   stepper so width: auto actually takes effect instead of being stretched
   right back out. */
.quantity-field-detail .quantity-stepper { width: auto; align-self: flex-start; }
.quantity-field-detail .quantity-stepper input[type="number"] { flex: none; width: 3em; text-align: center; }

/* ---- Product detail ---- */
.product-detail { display: grid; grid-template-columns: minmax(0, 380px) 1fr; gap: var(--space-5); }
/* Below 700px the two 380px/1fr tracks fight over too little space (e.g. a
   375px phone squeezes to ~166px image / ~121px info - unusably narrow for
   the title/price/description/options), same collapse-to-single-column
   breakpoint as .home-image-text/.checkout-layout. */
@media (max-width: 700px) {
  .product-detail { grid-template-columns: 1fr; gap: var(--space-4); }
  .product-detail-image { aspect-ratio: 4 / 3; }
  /* Scoped to this form specifically, not a global button change - a
     full-width primary CTA is the expected mobile pattern, but every other
     button on the site (cart, checkout, forms) should keep its normal
     content-sized width. */
  #add-to-cart-form button[type="submit"] { display: block; width: 100%; text-align: center; }
}
.product-gallery { display: flex; flex-direction: column; gap: var(--space-2); }
.product-detail-image {
  aspect-ratio: 1 / 1; background: #ededea; border-radius: var(--radius); overflow: hidden;
}
.product-detail-image img { width: 100%; height: 100%; object-fit: cover; }
.product-gallery-thumbs { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.product-gallery-thumb {
  width: 64px; height: 64px; object-fit: cover; border-radius: var(--radius);
  border: 2px solid transparent; cursor: pointer; opacity: 0.75;
}
.product-gallery-thumb:hover { opacity: 1; }
.product-gallery-thumb.active { border-color: var(--color-accent); opacity: 1; }
/* .product-details-card boxes the name/price/description in a white card
   against the page's own (cream) background - same visual recipe as the
   shared .card class (background/border/radius/padding), kept as its own
   rule here rather than reusing .card directly so its heading can be
   untouched by .card h2's smaller font-size (this one wraps an <h1>, the
   product's actual name, which should stay prominent). */
.product-details-card {
  background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius);
  padding: var(--space-4); margin-bottom: var(--space-4);
}
.product-details-card h1 { margin-top: 0; }
.product-price { font-size: 1.4rem; color: var(--color-accent-dark); font-weight: 700; }
/* Shown instead of (grid card) or alongside (detail page, via .product-price
   also getting the .price-original class) the plain price whenever a
   logged-in customer has a price-level override (see services.
   ProductSummary/ProductDetail.CustomerPrice) - the list price stays
   visible but struck through, so the customer sees they're getting a deal
   rather than just a different number. Declared after .product-card-price/
   .product-price so these color/font-weight overrides win despite equal
   selector specificity. */
.price-original { text-decoration: line-through; color: var(--color-muted); font-weight: 400; }
.price-customer { color: var(--color-accent-dark); font-weight: 700; }
/* Small "-X%" pill next to a discounted product's price (see
   services.ProductSummary/ProductDetail.DiscountPercent) - margin-left
   keeps it visibly apart from the price rather than butting up against
   it, per the "not too close" spec this was built to. */
.discount-badge {
  display: inline-block; margin-left: var(--space-2);
  background: var(--color-danger); color: #fff;
  font-size: 0.7rem; font-weight: 700; line-height: 1;
  padding: 3px 6px; border-radius: var(--radius);
  vertical-align: middle;
}
.product-combined-total { font-size: 1rem; color: var(--color-muted); font-weight: 400; }
.product-options-subtotal { font-size: 1rem; color: var(--color-muted); margin-top: calc(-1 * var(--space-2)); }
/* Same white-card treatment as .product-details-card, per option group. */
.option-group { background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius); padding: var(--space-3); margin-bottom: var(--space-3); }
/* <legend> is kept (not removed) purely for its native fieldset-labeling
   semantics and because cart.js reads its text for validation error
   messages (see validateOptionGroup) - but browsers render a fieldset's
   legend straddling the *border line itself* (a special layout box
   outside the padding-box, not normal flow), which visually pokes out
   above .option-group's background/border/radius no matter what padding
   or margin is set on it. Visually hiding it and showing
   .option-group-title instead (identical text, normal flow, no special
   rendering) is the standard fix - .sr-only keeps it in the accessibility
   tree (and in the DOM for cart.js to read) without affecting layout at
   all, which is more robust than fighting the legend's layout algorithm
   with float/display tricks (tried first; it desynced the checkbox
   items below it in some option groups). */
.option-group-title { font-weight: 600; margin-bottom: var(--space-2); }
.checkbox-row, .radio-row, .qty-row { display: block; margin-bottom: 6px; }
/* A checkbox disabled by enforceMultipleGroupMax (cart.js) - the group's
   already at max_select, so this unchecked item currently can't be picked
   without unchecking another one first. */
.checkbox-row:has(input:disabled) { opacity: 0.5; }
.option-image-btn:has(input:disabled) { opacity: 0.5; }
/* single/multiple/mix_and_match option groups (plain radio/checkbox/qty-
   stepper list, not the image-button layout below) lay their choices out in
   a responsive grid: 1 column on mobile, 2 on wider screens - selected via
   data-selection-mode on the fieldset rather than the row classes
   themselves, since a "flat fee with memo" row can share a group with
   radio-row/checkbox-row/qty-row siblings and should follow the same grid.
   Excluding via :has(.option-image-btn) rather than the .option-image-group
   wrapper class: a group configured "image_only" in the backoffice still
   gets that class even when none of its items actually have an uploaded
   image (each item falls back to a plain checkbox-row/radio-row/qty-row
   individually) - checking for an actual rendered image button is what
   the layout choice should really hinge on. */
.option-group[data-selection-mode="single"] > .option-group-choices:not(:has(.option-image-btn)),
.option-group[data-selection-mode="multiple"] > .option-group-choices:not(:has(.option-image-btn)),
.option-group[data-selection-mode="mix_and_match"] > .option-group-choices:not(:has(.option-image-btn)) {
  display: grid; grid-template-columns: 1fr; column-gap: var(--space-3);
}
@media (min-width: 700px) {
  .option-group[data-selection-mode="single"] > .option-group-choices:not(:has(.option-image-btn)),
  .option-group[data-selection-mode="multiple"] > .option-group-choices:not(:has(.option-image-btn)),
  .option-group[data-selection-mode="mix_and_match"] > .option-group-choices:not(:has(.option-image-btn)) {
    grid-template-columns: 1fr 1fr;
  }
}
/* A qty-row (stepper + label) is visually denser than a plain checkbox-row/
   radio-row - the same column-gap that reads fine for those leaves a
   mix_and_match row's label butting right up against the next column's
   stepper, so it gets a wider gap of its own, overriding the shared rule
   above (cascade order, not specificity - same selector shape). */
.option-group[data-selection-mode="mix_and_match"] > .option-group-choices:not(:has(.option-image-btn)) {
  column-gap: var(--space-5);
}
.option-image-group { display: flex; flex-wrap: wrap; gap: var(--space-3); }
.option-image-btn {
  display: flex; flex-direction: column; align-items: center; gap: var(--space-1);
  width: 100px; cursor: pointer; text-align: center; font-size: 0.85em;
}
.option-image-btn input[type="radio"], .option-image-btn input[type="checkbox"] {
  position: absolute; opacity: 0; width: 1px; height: 1px;
}
.option-image-btn img {
  width: 100%; aspect-ratio: 1 / 1; object-fit: cover;
  border: 2px solid var(--color-border); border-radius: var(--radius);
}
.option-image-btn input:checked ~ img { border-color: var(--color-primary); }
.option-image-btn .quantity-stepper { margin-top: var(--space-1); }
input[type="number"] {
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  font-size: 0.95rem;
  font-family: inherit;
  width: 80px;
}

/* ---- Cards (account page, and any future page needing boxed sections) ---- */
.card {
  background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius);
  padding: var(--space-4); margin-bottom: var(--space-4);
}
.card h2 { margin-top: 0; font-size: 1.1rem; }
.card .empty-state { margin-top: 0; }
.card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: var(--space-4); }
.card-grid .card { margin-bottom: 0; }
.address-summary { line-height: 1.6; }
/* A card's heading paired with an action on the same row (e.g. "My
   Addresses" + "+ Add Address") - mirrors the backoffice's own .list-header. */
.list-header { display: flex; justify-content: space-between; align-items: center; gap: var(--space-3); flex-wrap: wrap; margin-bottom: var(--space-3); }
.list-header h2 { margin: 0; }

/* The account/orders/addresses pages opt out of the standard .container (see
   AccountHandler's FullWidth flag) so they run genuinely edge-to-edge - no
   max-width cap, unlike .checkout-page-container/.cart-page-container, which
   keep a cap alongside FullWidth. Only padding, so content still has
   breathing room instead of touching the viewport edge. */
.account-page-container { padding: var(--space-4); }

/* Main content (primary activity, e.g. account details/orders) on the left,
   secondary detail cards (address/settings) stacked on the right - evenly
   balanced columns rather than a narrow fixed sidebar. Mirrors the
   backoffice order detail page's own main/sidebar split. */
.account-layout { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); align-items: start; }
@media (max-width: 900px) {
  .account-layout { grid-template-columns: 1fr; }
}
/* .account-sidebar needs no flex/gap of its own - its .card children space
   themselves via .card's own margin-bottom. */

/* ---- Order summary (checkout confirmation + account order detail) - the
   two pages share partials/order_summary.html's "orderSummaryCard" block,
   and the confirmation page additionally shows the hero below (icon,
   heading, intro) above it, so both read as the same design as the
   order-confirmation email (see ntos_webshop_core/orderemail). ---- */
.order-hero { text-align: center; padding: 0 0 var(--space-4); }
.order-icon-circle {
  width: 64px; height: 64px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  margin: 0 auto var(--space-3);
}
.order-icon-circle svg { width: 28px; height: 28px; fill: currentColor; }
.order-icon-circle--accent { background: var(--color-accent); color: var(--color-accent-contrast); }
.order-icon-circle--success { background: var(--color-success); color: #ffffff; }
.order-icon-circle--danger { background: var(--color-danger); color: #ffffff; }
.order-hero h1 { margin: 0 0 var(--space-2); font-size: 1.6rem; }
.order-hero p { margin: 0; color: var(--color-muted); }

.order-info-bar {
  display: flex; justify-content: space-between; align-items: flex-start; gap: var(--space-4); flex-wrap: wrap;
  padding: var(--space-3) var(--space-4); border: 1px solid var(--color-border); border-radius: var(--radius);
  margin-bottom: var(--space-4);
}
.order-info-bar .label { display: block; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--color-muted); margin-bottom: 2px; }
.order-info-bar .value { font-weight: 700; }
.order-info-bar .value.order-id { font-size: 1.1rem; }

.issued-gift-cards {
  padding: var(--space-3) var(--space-4); border: 1px solid var(--color-accent); border-radius: var(--radius);
  background: rgba(217, 129, 47, 0.08); margin-bottom: var(--space-4);
}
.issued-gift-cards-label { margin: 0 0 var(--space-2); font-weight: 700; color: var(--color-accent-dark); }
.issued-gift-card-row { display: flex; justify-content: space-between; align-items: center; padding: var(--space-1) 0; gap: var(--space-3); }
.issued-gift-card-row code { font-size: 1.05rem; font-weight: 700; letter-spacing: 0.03em; }
.issued-gift-card-row span { font-weight: 700; color: var(--color-accent-dark); white-space: nowrap; }

.order-header-actions { display: flex; align-items: center; gap: var(--space-3); }
.order-status-badge { display: inline-flex; align-items: center; gap: 6px; padding: 3px var(--space-2); border-radius: 999px; font-size: 0.7rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; white-space: nowrap; }
.order-status-badge::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.order-status-badge--accent { background: rgba(217, 129, 47, 0.16); color: var(--color-accent); }
.order-status-badge--success { background: rgba(46, 184, 108, 0.18); color: var(--color-success); }
.order-status-badge--danger { background: rgba(220, 38, 38, 0.16); color: var(--color-danger); }

.order-totals { margin-top: var(--space-1); }
.order-totals-row { display: flex; justify-content: space-between; padding: var(--space-1) 0; color: var(--color-muted); }
.order-totals-row.order-total { border-top: 2px solid var(--color-border); margin-top: var(--space-2); padding-top: var(--space-2); font-weight: 700; font-size: 1.1rem; color: var(--color-text); }

.order-fulfillment-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-4); margin-top: var(--space-4); }
@media (max-width: 600px) { .order-fulfillment-grid { grid-template-columns: 1fr; } }
/* .order-section is the single-column counterpart (pickup details, billing
   address) - same heading/paragraph treatment as the grid's columns, just
   without splitting into two. */
.order-section { margin-top: var(--space-4); }
.order-fulfillment-grid h3, .order-section h3 { margin: 0 0 4px; font-size: 0.95rem; }
.order-fulfillment-grid p, .order-section p { margin: 0; color: var(--color-muted); line-height: 1.6; }

/* ---- Cart / checkout ---- */
/* The cart page opts out of the standard .container (see CartHandler.ShowPage's
   FullWidth flag) so its panel can use its own width instead of every other
   page's 1200px cap - same "FullWidth + its own max-width" pattern as
   .checkout-page-container. Matched to .checkout-steps' own rendered width
   (948px) below rather than .checkout-page-container's outer 1440px, since
   that also has to fit the 420px order-summary sidebar cart doesn't have:
   1440 - (2 * 24px padding) - 420px sidebar - 24px grid gap = 948px. */
.cart-page-container { max-width: 948px; margin: 0 auto; padding: var(--space-4); }
.cart-container {
  background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius);
  padding: var(--space-3);
}
.cart-table { width: 100%; border-collapse: collapse; margin-bottom: var(--space-3); }
.cart-table th, .cart-table td { text-align: left; padding: var(--space-2); border-bottom: 1px solid var(--color-border); }
.cart-table th { color: var(--color-muted); text-transform: uppercase; font-size: 0.8rem; letter-spacing: 0.04em; }
.cart-sub-item td { color: var(--color-muted); font-size: 0.9rem; }
.cart-total { font-weight: 700; font-size: 1.2rem; margin: 0; }
.cart-tax-note { color: var(--color-muted); font-size: 0.85rem; margin: 0; }
.cart-actions-row { display: flex; flex-direction: column; align-items: flex-end; gap: var(--space-2); }
.cart-container form { margin-top: var(--space-3); }

/* ---- Buttons & forms ---- */
button, .btn {
  background: var(--color-accent); color: var(--color-accent-contrast); border: none; border-radius: var(--radius);
  padding: var(--space-2) var(--space-4); cursor: pointer; text-decoration: none; display: inline-block;
  font-weight: 600; font-size: 0.9rem; text-transform: uppercase; letter-spacing: 0.03em;
}
button:hover, .btn:hover { background: var(--color-accent-dark); }
.btn.secondary, button.secondary {
  background: transparent; color: var(--color-text); border: 1px solid var(--color-border);
}
.btn.secondary:hover, button.secondary:hover { background: var(--color-border); }
.btn.danger, button.danger, .remove-line-item { background: var(--color-danger); }
.btn.danger:hover, button.danger:hover, .remove-line-item:hover { background: #8c1e18; }
/* Icon-only (a trash can SVG, see cart.html) - overrides the default
   text-button padding/uppercase sizing with a small square touch target. */
.remove-line-item {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; padding: 0; color: var(--color-danger-contrast, #fff);
}

.field-error { color: var(--color-danger); font-size: 0.85rem; }
.field-hint { color: var(--color-muted); font-size: 0.85rem; }
.field-hint.field-hint-error { color: var(--color-danger); }
.empty-state { color: var(--color-muted); }
.auth-form, .checkout-fields { max-width: 400px; }
.auth-form label, .checkout-form label { display: block; margin-bottom: var(--space-2); font-weight: 600; font-size: 0.9rem; }
.auth-form input:not([type="radio"]):not([type="checkbox"]), .auth-form textarea, .auth-form select,
.checkout-form input:not([type="radio"]):not([type="checkbox"]),
.checkout-form select,
.checkout-form textarea {
  width: 100%; padding: var(--space-2); border: 1px solid var(--color-border); border-radius: var(--radius);
  margin-bottom: var(--space-3); font-size: 1rem; font-family: inherit;
}
.checkout-note { margin-top: var(--space-3); }
.checkout-note textarea { resize: vertical; margin-bottom: 0; }
.checkout-form fieldset, .auth-form fieldset { border: 1px solid var(--color-border); border-radius: var(--radius); padding: var(--space-3); margin-bottom: var(--space-3); }
.checkout-form legend, .auth-form legend { font-weight: 600; padding: 0 var(--space-1); }
.checkout-form .radio-row input, .checkout-form .checkbox-row input { width: auto; margin-bottom: 0; margin-right: var(--space-2); }
.voucher-apply-row { display: flex; gap: var(--space-2); }
.voucher-apply-row input { margin-bottom: 0; }
.voucher-apply-row button { flex-shrink: 0; }
.checkout-summary {
  border-top: 1px solid var(--color-border); margin-top: var(--space-3); padding-top: var(--space-3);
  display: flex; flex-direction: column; align-items: flex-end; gap: var(--space-2);
}
.checkout-summary-row { margin: 0; color: var(--color-muted); font-size: 0.9rem; }
.checkout-summary .cart-total { margin: 0; }
/* Place order now lives at the bottom of the order-summary sidebar itself
   (see checkout.html) rather than its own trailing "Review & Place Order"
   step - the summary above it already is the review. */
.place-order-btn { width: 100%; margin-top: var(--space-3); padding: var(--space-3); font-size: 1.05rem; }

/* ---- Checkout: numbered-step layout (modeled on a PrestaShop-style
   checkout) - a left column of step cards plus a persistent right-hand
   order summary sidebar. Every step is always expanded - there's no
   collapse/continue mechanic, so nothing here ever gates access to a
   later field or the submit button. ---- */
/* Checkout renders with FullWidth (opting out of .container's 1200px cap -
   see checkout_handler.go) and supplies its own, wider cap here instead, so
   the two-column step layout below has more breathing room than the rest of
   the shop's narrower content pages need. */
.checkout-page-container { max-width: 1440px; margin: 0 auto; padding: var(--space-4); }

.checkout-layout { display: grid; grid-template-columns: 1fr 420px; gap: var(--space-4); align-items: start; }
@media (max-width: 900px) { .checkout-layout { grid-template-columns: 1fr; } }

.checkout-steps { counter-reset: checkout-step; }
.checkout-step {
  counter-increment: checkout-step;
  background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius);
  padding: var(--space-4); margin-bottom: var(--space-3);
}
.checkout-step-header { display: flex; align-items: center; gap: var(--space-3); }
.checkout-step-badge {
  flex-shrink: 0; width: 1.6em; height: 1.6em; border-radius: 50%;
  background: var(--color-accent); color: var(--color-accent-contrast);
  display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 0.9rem;
}
.checkout-step-badge::before { content: counter(checkout-step); }
.checkout-step-title { margin: 0; font-size: 1.05rem; flex: 1; }

/* Its own fields/fieldsets read as too wide at the checkout-steps column's
   full width - cap them narrower than the Contact Information step above. */
.checkout-step-delivery .checkout-step-body { max-width: 80%; }
@media (max-width: 900px) { .checkout-step-delivery .checkout-step-body { max-width: 100%; } }

/* Step-by-step wizard states (see checkout_steps.js) - a "pending" step
   hasn't been reached yet (dimmed, its header inert - can't be clicked
   straight to), a "done" step was already completed and collapsed (its
   header re-opens it for editing), "active" is the one currently shown. */
.checkout-step--pending .checkout-step-body,
.checkout-step--done .checkout-step-body { display: none; }
.checkout-step--pending { opacity: 0.55; }
.checkout-step--pending .checkout-step-header { cursor: default; }
.checkout-step--done .checkout-step-header,
.checkout-step--active .checkout-step-header { cursor: pointer; }
.checkout-step--done .checkout-step-badge { background: var(--color-accent-dark); }
.checkout-step-actions { margin-top: var(--space-4); }

/* "+ Add billing/invoice details" toggle (see checkout_billing_optional.js)
   - styled like the other plain-text toggle in this flow,
   .checkout-auth-tab, for a consistent "this is a link-like action, not a
   normal button" affordance. */
.billing-optional-toggle {
  background: none; border: none; padding: 0; cursor: pointer; font-size: 0.95rem;
  color: var(--color-accent-dark); text-decoration: underline; margin-bottom: var(--space-3);
}

.saved-address-fieldset { margin-bottom: var(--space-3); }
.saved-address-option { font-weight: 400; }

.checkout-auth-toggle { display: flex; align-items: center; gap: var(--space-2); margin-bottom: var(--space-3); }
.checkout-auth-tab {
  background: none; border: none; padding: 0; cursor: pointer; font-size: 0.95rem;
  color: var(--color-text); font-weight: 600;
}
.checkout-auth-tab.active { color: var(--color-accent-dark); text-decoration: underline; }
.checkout-auth-sep { color: var(--color-border); }
.checkout-auth-login-btn { margin-top: var(--space-1); }

.order-summary-sidebar { position: sticky; top: var(--space-4); }
.voucher-details { margin: var(--space-3) 0; }
.voucher-details summary { cursor: pointer; color: var(--color-accent-dark); font-size: 0.9rem; }
.voucher-details .voucher-apply-row { margin-top: var(--space-2); }

/* Honeypot field (see pages/contact.html) - moved off-screen rather than
   display:none, since some bots specifically skip display:none fields. */
.hp-field { position: absolute; left: -9999px; top: -9999px; }

/* ---- Footer ---- */
.site-footer { background: var(--color-dark); color: #cfcfcf; margin-top: var(--space-5); }
.site-footer-inner {
  max-width: 1200px; margin: 0 auto; padding: var(--space-5) var(--space-4) var(--space-4);
  display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--space-4);
}
.site-footer h3 {
  color: #fff; font-size: 0.95rem; text-transform: uppercase; letter-spacing: 0.05em;
  margin: 0 0 var(--space-3);
}
.site-footer-blurb { font-size: 0.85rem; line-height: 1.5; margin-bottom: var(--space-3); }
.site-footer-blurb p { margin: 0 0 var(--space-2); }
.site-footer ul { list-style: none; margin: 0; padding: 0; }
.site-footer li { margin-bottom: var(--space-2); }
.site-footer a { color: #cfcfcf; text-decoration: none; font-size: 0.9rem; display: inline-flex; align-items: center; gap: var(--space-2); }
.site-footer a:hover { color: var(--color-accent); }
.site-footer-link-icon { flex: none; vertical-align: middle; }
.site-footer-bottom {
  border-top: 1px solid #33333d; padding: var(--space-3) var(--space-4); text-align: center;
  font-size: 0.8rem; color: #8f8f8f;
}
