/* ============================================================================
   AR Storefront — bold modern retail.

   Every brand surface resolves from a single custom property, `--accent`, which
   ui.js sets from the shop's saved colour. Shades are derived with color-mix()
   rather than hardcoded, so one hex in the settings form repaints buttons,
   badges, the hero wash, focus rings, hover borders and price rules together.

   `--accent-ink` (text drawn ON the accent) is computed from luminance in JS,
   never assumed — a pale accent needs dark text or the labels vanish.
   ========================================================================== */

:root {
  /* Cool light retail — not flat pure white. Dark mode overrides below. */
  --bg: #f3f4f8;
  --bg-2: #e8eaf0;
  --panel: #ffffff;
  --ink: #12121a;
  --ink-soft: #5a5a6a;
  --rule: #d4d6df;
  --shadow-sm: 0 1px 2px rgb(16 18 28 / 0.06), 0 1px 3px rgb(16 18 28 / 0.04);
  --shadow-md: 0 8px 24px rgb(16 18 28 / 0.10), 0 2px 6px rgb(16 18 28 / 0.05);
  --shadow-lg: 0 18px 44px rgb(16 18 28 / 0.14), 0 4px 12px rgb(16 18 28 / 0.06);

  --accent: #5b5bd6;
  --accent-ink: #ffffff;

  --danger: #c8253a;
  --ok: #12794f;
  --sale: #c8253a;

  --radius: 16px;
  --radius-sm: 10px;

  --font: ui-sans-serif, -apple-system, "Segoe UI", Roboto, Inter, Helvetica, Arial, sans-serif;
}

/* Derived accent shades. Kept separate so they recompute whenever --accent
   changes, without JS having to set six properties. */
:root {
  --accent-wash: color-mix(in srgb, var(--accent) 12%, var(--bg));
  --accent-wash-2: color-mix(in srgb, var(--accent) 22%, var(--bg));
  --accent-edge: color-mix(in srgb, var(--accent) 38%, transparent);
  --accent-ring: color-mix(in srgb, var(--accent) 26%, transparent);
  --accent-deep: color-mix(in srgb, var(--accent) 78%, #000);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0a0a0c;
    --bg-2: #101014;
    --panel: #161619;
    --ink: #f3f3f6;
    --ink-soft: #9797a4;
    --rule: #26262d;
    --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.5);
    --shadow-md: 0 10px 30px rgb(0 0 0 / 0.5);
    --shadow-lg: 0 20px 50px rgb(0 0 0 / 0.6);
    --danger: #ff7d8d;
    --ok: #4fdba1;
    --sale: #ff8a98;
    --accent-deep: color-mix(in srgb, var(--accent) 80%, #fff);
  }
}

* { box-sizing: border-box; }

/* Respect the OS "reduce motion" setting. Card lifts, image zooms and the
   progress bar are decorative; for users who get motion sickness or vestibular
   symptoms they are not. Kept to near-zero rather than 0s so transitionend
   handlers still fire. */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .tile.clickable:hover { transform: none; }
  .tile.clickable:hover .media img,
  .tile.clickable:hover .media video { transform: none; }
  .btn:hover:not(:disabled) { transform: none; }
}

/* Visually hidden but announced by screen readers. */
.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;
}

/* The UA stylesheet's `[hidden] { display: none }` is a bare attribute selector,
   so any class rule that sets `display` outranks it — `.btn { display:inline-flex }`
   silently defeats `el.hidden = true`. Everything here toggles visibility through
   the `hidden` property, so it has to actually win. */
[hidden] { display: none !important; }

body {
  margin: 0;
  background:
    radial-gradient(120% 80% at 50% -10%, var(--accent-wash), transparent 55%),
    var(--bg);
  color: var(--ink);
  font-family: var(--font);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

h1, h2, h3 { letter-spacing: -0.025em; line-height: 1.12; margin: 0; }

.wrap { max-width: 1180px; margin: 0 auto; padding: 26px 20px 110px; }
.wrap.narrow { max-width: 440px; padding-top: 9vh; }

.muted { color: var(--ink-soft); }

/* ---------------------------------------------------------------- buttons */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 13px 24px;
  font: inherit;
  font-weight: 650;
  font-size: 0.95rem;
  letter-spacing: -0.01em;
  border-radius: 999px;
  border: 1.5px solid transparent;
  background: var(--accent);
  color: var(--accent-ink);
  cursor: pointer;
  white-space: nowrap;
  transition: transform 0.12s ease, filter 0.15s ease, background 0.15s ease;
}
.btn:hover:not(:disabled) { filter: brightness(1.08); transform: translateY(-1px); }
.btn:active:not(:disabled) { transform: translateY(0); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn:focus-visible { outline: 3px solid var(--accent-ring); outline-offset: 2px; }

.btn.ghost {
  background: var(--panel);
  color: var(--ink);
  border-color: color-mix(in srgb, var(--rule) 70%, var(--ink-soft));
  box-shadow: var(--shadow-sm);
}
.btn.ghost:hover:not(:disabled) {
  background: var(--accent-wash);
  border-color: var(--accent-edge);
  filter: none;
}
.btn.danger {
  background: transparent;
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 40%, transparent);
}
.btn.danger:hover:not(:disabled) {
  background: color-mix(in srgb, var(--danger) 10%, transparent);
  filter: none;
}
.btn.small { padding: 8px 16px; font-size: 0.86rem; }
.btn.block { width: 100%; }

/* ------------------------------------------------------------- storefront */

/* Slim persistent bar. Stays out of the way; the hero does the branding. */
.shopbar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 20px;
  background: color-mix(in srgb, var(--panel) 86%, transparent);
  backdrop-filter: saturate(1.6) blur(14px);
  border-bottom: 1px solid var(--rule);
  box-shadow: 0 1px 0 rgb(16 18 28 / 0.03);
}
.shopbar .spacer { flex: 1; }
.shopbar-logo {
  width: 32px; height: 32px;
  border-radius: 9px;
  object-fit: cover;
  background: var(--accent-wash);
}
.shopbar-name { font-weight: 750; font-size: 0.98rem; letter-spacing: -0.02em; }

/* Deliberately compact. An earlier version ran 382px tall, which on a 720px
   laptop viewport left only a sliver of the product grid visible — a shop has
   to lead with merchandise, not with its own masthead. Keep this under ~290px
   so at least one full row of products sits above the fold. */
.hero {
  position: relative;
  overflow: hidden;
  padding: 40px 20px 36px;
  text-align: center;
  background:
    radial-gradient(110% 140% at 50% -35%, var(--accent-wash-2), transparent 64%),
    var(--bg);
  border-bottom: 1px solid var(--rule);
}
/* Cover photo sits behind the hero. The scrim is not optional decoration: the
   shop name is dark ink in light mode and would be unreadable over a bright
   photo, so the gradient guarantees contrast whatever image is uploaded. */
.hero.has-cover {
  background: var(--bg);
  border-bottom: none;
}
.hero .cover {
  position: absolute;
  inset: 0;
  overflow: hidden;
  z-index: 0;
}
.hero .cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.hero .cover::after {
  content: "";
  position: absolute;
  inset: 0;
  /* Two stacked scrims doing different jobs.
     Top: always dark, never theme-tinted. The floating bar's title is white in
     both themes, so a theme-tinted scrim would be a *white* veil in light mode
     and the title would vanish into it.
     Bottom: theme-tinted, because the shop name below uses --ink and needs the
     page background behind it — heavy enough for a near-white uploaded photo. */
  background:
    linear-gradient(to bottom, rgb(0 0 0 / 0.58) 0%, rgb(0 0 0 / 0) 140px),
    linear-gradient(
      to bottom,
      transparent 0%,
      color-mix(in srgb, var(--bg) 18%, transparent) 28%,
      color-mix(in srgb, var(--bg) 72%, transparent) 62%,
      color-mix(in srgb, var(--bg) 96%, transparent) 100%
    );
}

/* Light mode: a pure white fade over the cover looks milky/washed. Prefer a
   deeper neutral fade so the photo keeps colour and the title stays sharp. */
@media (prefers-color-scheme: light) {
  .hero .cover::after {
    background:
      linear-gradient(to bottom, rgb(0 0 0 / 0.58) 0%, rgb(0 0 0 / 0) 140px),
      linear-gradient(
        to bottom,
        transparent 8%,
        rgb(243 244 248 / 0.35) 40%,
        rgb(243 244 248 / 0.88) 72%,
        rgb(243 244 248 / 0.98) 100%
      );
  }
  .hero.has-cover h1,
  .hero.has-cover .hero-tagline,
  .hero.has-cover .hero-about {
    text-shadow:
      0 1px 1px rgb(255 255 255 / 0.65),
      0 2px 16px rgb(243 244 248 / 0.9);
  }
}
/* ---- Bright cover ("Cover photo style: bright photo") ----
   The default scrims assume the photo may be dark: a black band under the top
   bar so its white title survives, and a heavy fade to the page colour so the
   dark shop name has something to sit on. Over a light, airy photo both read as
   dirt and milk — the top band looks like a smudge and the fade erases the
   lower half of the image. Here the photo is assumed light, so the bar title
   flips to ink and the scrims drop to the minimum that still blends the hero
   into the page below. */
/* Higher specificity than both the base rule and the light-mode override
   below it, so one declaration covers every theme. */
body.cover-soft .hero .cover::after {
  background:
    /* A soft halo under the centred text block instead of a veil over the whole
       image: the name and tagline get something to sit on while the edges of
       the photo — where the product actually is — stay untouched. */
    radial-gradient(
      75% 58% at 50% 58%,
      color-mix(in srgb, var(--bg) 82%, transparent) 0%,
      color-mix(in srgb, var(--bg) 55%, transparent) 45%,
      transparent 72%
    ),
    linear-gradient(
      to bottom,
      transparent 0%,
      transparent 58%,
      color-mix(in srgb, var(--bg) 45%, transparent) 86%,
      color-mix(in srgb, var(--bg) 92%, transparent) 100%
    );
}

/* The muted tagline/about colour is set for a plain background; over a photo,
   even a haloed one, it needs full ink and a tighter halo of its own. */
body.cover-soft .hero.has-cover .hero-tagline,
body.cover-soft .hero.has-cover .hero-about {
  color: var(--ink);
  text-shadow:
    0 1px 2px color-mix(in srgb, var(--bg) 88%, transparent),
    0 0 14px color-mix(in srgb, var(--bg) 95%, transparent);
}

/* White-on-white is invisible: over a bright photo the bar title has to be ink
   with a light halo, the exact inverse of the dark-cover treatment. */
body.cover-mode.cover-soft:not(.scrolled) .shopbar-name {
  color: var(--ink);
  text-shadow: 0 1px 10px color-mix(in srgb, var(--bg) 92%, transparent);
}
body.cover-mode.cover-soft:not(.scrolled) .shopbar-logo {
  box-shadow: 0 0 0 1.5px rgb(0 0 0 / 0.12);
}
body.cover-soft .hero.has-cover .hero-logo {
  box-shadow:
    0 0 0 1.5px rgb(0 0 0 / 0.08),
    0 10px 26px rgb(0 0 0 / 0.18);
}

.hero.has-cover { padding-top: 104px; padding-bottom: 58px; }

/* Cover mode: keep sticky (not fixed). Fixed pulled the bar out of flow and
   product cards scrolled underneath it — on phones you saw orphaned
   "See details" buttons with the images clipped by the header. Sticky + a
   pulled-up hero still lets the bar sit over the cover photo at the top. */
body.cover-mode .shopbar {
  position: sticky;
  background: transparent;
  border-bottom-color: transparent;
  box-shadow: none;
  backdrop-filter: none;
  transition: background 0.25s ease, border-color 0.25s ease;
}
body.cover-mode .hero.has-cover {
  margin-top: -56px;
}
body.cover-mode.scrolled .shopbar {
  background: color-mix(in srgb, var(--panel) 92%, transparent);
  border-bottom-color: var(--rule);
  backdrop-filter: saturate(1.6) blur(14px);
  box-shadow: 0 1px 0 rgb(16 18 28 / 0.04);
}
/* Text sits directly on the photo here, so it needs its own separation
   rather than relying on the bar's background. */
body.cover-mode:not(.scrolled) .shopbar-name {
  color: #fff;
  text-shadow: 0 1px 12px rgb(0 0 0 / 0.75);
}
body.cover-mode:not(.scrolled) .shopbar-logo {
  box-shadow: 0 0 0 1.5px rgb(255 255 255 / 0.25);
}

/* No plate behind the logo. A solid `--panel` backing is near-black in dark
   mode, which put a black box on top of the cover — worse than the problem it
   was meant to solve. Almost every uploaded logo already carries its own
   background, so a hairline ring plus a shadow is enough to lift it off the
   image without introducing a second box. */
.hero.has-cover .hero-logo {
  box-shadow:
    0 0 0 1.5px rgb(255 255 255 / 0.22),
    0 12px 30px rgb(0 0 0 / 0.45);
}

/* Over a cover, the muted grey used for tagline and about text loses far too
   much contrast against a busy photo — it was legible on a plain background and
   barely readable once an image sat behind it. Promote both to full ink and add
   a halo in the page background colour, which works in either theme because the
   halo follows --bg. */
.hero.has-cover .hero-tagline,
.hero.has-cover .hero-about {
  color: var(--ink);
}
.hero.has-cover h1,
.hero.has-cover .hero-tagline,
.hero.has-cover .hero-about {
  text-shadow:
    0 1px 2px color-mix(in srgb, var(--bg) 70%, transparent),
    0 2px 18px color-mix(in srgb, var(--bg) 85%, transparent);
}

.socials {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 16px;
}
.social {
  font-size: 0.84rem;
  font-weight: 620;
  color: var(--ink);
  text-decoration: none;
  padding: 7px 14px;
  border-radius: 999px;
  border: 1.5px solid var(--rule);
  background: color-mix(in srgb, var(--panel) 78%, transparent);
  backdrop-filter: blur(6px);
}
.social:hover {
  text-decoration: none;
  border-color: var(--accent-edge);
  background: var(--accent-wash);
}
.hero-inner { position: relative; z-index: 1; max-width: 700px; margin: 0 auto; }
.hero-logo {
  width: 64px; height: 64px;
  border-radius: 18px;
  object-fit: cover;
  margin-bottom: 14px;
  box-shadow: var(--shadow-md);
  background: var(--panel);
}
.hero h1 {
  font-size: clamp(1.9rem, 5vw, 2.8rem);
  font-weight: 820;
  margin-bottom: 8px;
}
.hero-tagline {
  font-size: clamp(0.98rem, 2vw, 1.1rem);
  color: var(--ink-soft);
  font-weight: 500;
  margin: 0 0 6px;
}
.hero-about {
  color: var(--ink-soft);
  font-size: 0.94rem;
  margin: 0 auto;
  max-width: 56ch;
}
.hero .btn { margin-top: 18px; }

.section-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  margin-bottom: 4px;
}
.section-head h2 { font-size: 1.35rem; font-weight: 780; }
.count {
  font-size: 0.85rem;
  color: var(--ink-soft);
  font-weight: 550;
}

/* --------------------------------------------------------------- search */

.searchbar {
  position: relative;
  margin-top: 18px;
}
.searchbar input[type="search"] {
  padding-left: 42px;
  padding-right: 40px;
  border-radius: 999px;
  background: var(--panel);
  border-color: color-mix(in srgb, var(--rule) 75%, var(--ink-soft));
  box-shadow: var(--shadow-sm);
  -webkit-appearance: none;
  appearance: none;
}
/* Safari's native clear affordance sits where our own button goes. */
.searchbar input[type="search"]::-webkit-search-cancel-button { display: none; }
.search-icon {
  position: absolute;
  left: 15px; top: 50%;
  transform: translateY(-50%);
  width: 17px; height: 17px;
  color: var(--ink-soft);
  pointer-events: none;
}
.search-clear {
  position: absolute;
  right: 8px; top: 50%;
  transform: translateY(-50%);
  width: 28px; height: 28px;
  display: grid;
  place-items: center;
  font-size: 20px;
  line-height: 1;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-soft);
  cursor: pointer;
}
.search-clear:hover { background: var(--bg-2); color: var(--ink); }

/* ---------------------------------------------------------------- filters */

/* Horizontally scrollable rather than wrapping: a shop with a dozen categories
   would otherwise push the products themselves below the fold on a phone. */
.filters {
  display: flex;
  gap: 8px;
  margin-top: 16px;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 2px;
}
.filters::-webkit-scrollbar { display: none; }

.chip {
  flex: 0 0 auto;
  padding: 8px 15px;
  font: inherit;
  font-size: 0.86rem;
  font-weight: 620;
  color: var(--ink);
  background: var(--panel);
  border: 1.5px solid color-mix(in srgb, var(--rule) 70%, var(--ink-soft));
  border-radius: 999px;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: var(--shadow-sm);
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.chip:hover { color: var(--ink); border-color: var(--accent-edge); }
.chip[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.chip:focus-visible { outline: 3px solid var(--accent-ring); outline-offset: 2px; }
.chip .n { opacity: 0.65; font-weight: 550; margin-left: 5px; }

.more-wrap { display: flex; justify-content: center; margin-top: 30px; }

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: 26px;
  margin-top: 22px;
}

/* ----------------------------------------------- product of the week */

/* One promoted product above the shelf. Two columns on desktop, stacked on a
   phone; the media keeps a fixed aspect ratio in both so a portrait photo can't
   push the copy off the screen. */
.featured {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  gap: clamp(18px, 3vw, 34px);
  align-items: center;
  margin: 4px 0 34px;
  padding: clamp(16px, 2.4vw, 26px);
  border-radius: calc(var(--radius) + 6px);
  border: 1px solid var(--accent-edge);
  /* Accent wash rather than a flat panel: this has to read as the one
     promoted slot on the page, without a second brand colour to maintain. */
  background:
    radial-gradient(120% 140% at 100% 0%, var(--accent-wash-2), transparent 62%),
    var(--panel);
  box-shadow: var(--shadow-md);
}

.featured-media {
  position: relative;
  aspect-ratio: 4 / 3;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg-2);
  cursor: pointer;
}
.featured-media img,
.featured-media video {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.5s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.featured-media:hover img,
.featured-media:hover video { transform: scale(1.04); }

.featured-info {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 12px;
  min-width: 0;
}

.featured-badge {
  display: inline-flex;
  align-items: center;
  padding: 6px 13px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-ink);
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.09em;
  text-transform: uppercase;
}

.featured-name {
  font-size: clamp(1.45rem, 3.4vw, 2.1rem);
  font-weight: 800;
  letter-spacing: -0.03em;
  color: var(--ink);
}

/* Same row component as the cards, sized up for the banner. */
.featured-price.price-row {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 12px;
}
.featured-price .price { font-size: clamp(1.4rem, 3vw, 1.85rem); }
.featured-price .was { font-size: 0.98rem; }
.featured-price .off { font-size: 0.78rem; padding: 4px 10px; }
.featured-price .quote-link { font-size: 1.05rem; }

.featured-desc {
  margin: 0;
  color: var(--ink-soft);
  font-size: 0.95rem;
  line-height: 1.55;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.featured-sold {
  margin: 0;
  font-size: 0.9rem;
  font-weight: 650;
  color: var(--danger);
}
.featured-cta { margin-top: 4px; }

@media (max-width: 760px) {
  .featured {
    grid-template-columns: 1fr;
    margin-bottom: 26px;
  }
  .featured-media { aspect-ratio: 16 / 10; }
}

/* ------------------------------------------------------------------ cards */

.tile {
  position: relative;
  background: var(--panel);
  border: 1px solid color-mix(in srgb, var(--rule) 80%, var(--ink-soft));
  border-radius: var(--radius);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow-md);
  transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
.tile.clickable { cursor: pointer; }
.tile.clickable:hover,
.tile.clickable:focus-visible {
  transform: translateY(-5px);
  box-shadow: var(--shadow-lg);
  border-color: var(--accent-edge);
  outline: none;
}
.tile.clickable:focus-visible { box-shadow: var(--shadow-lg), 0 0 0 3px var(--accent-ring); }

.tile .media {
  position: relative;
  aspect-ratio: 1 / 1;
  background: var(--bg-2);
  overflow: hidden;
  width: 100%;
}
.tile .media img,
.tile .media video {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.5s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.tile .media video { pointer-events: none; }
.tile.clickable:hover .media img,
.tile.clickable:hover .media video { transform: scale(1.06); }
.tile model-viewer { width: 100%; height: 100%; --poster-color: transparent; }

.tile .body {
  padding: 16px 17px 18px;
  display: flex;
  flex-direction: column;
  gap: 5px;
  flex: 1;
}
.tile .name {
  font-weight: 700;
  font-size: 1.02rem;
  letter-spacing: -0.015em;
  color: var(--ink);
}
.tile .price {
  font-weight: 780;
  font-size: 1.12rem;
  color: var(--ink);
  letter-spacing: -0.02em;
}

/* ------------------------------------------------------ price & stock */

.price-row {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}
.price-row .price {
  font-weight: 780;
  font-size: 1.12rem;
  color: var(--ink);
  letter-spacing: -0.02em;
  line-height: 1.15;
}
.price-row .deal {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}
.price-row .was {
  color: color-mix(in srgb, var(--ink-soft) 70%, var(--ink));
  font-size: 0.84rem;
  font-weight: 550;
  text-decoration-thickness: 1.5px;
}
.price-row .off {
  font-size: 0.7rem;
  font-weight: 800;
  letter-spacing: 0.03em;
  padding: 3px 8px;
  border-radius: 999px;
  /* Solid enough to stay readable on dark cards — a 16% tint vanished. */
  background: color-mix(in srgb, var(--sale) 22%, var(--panel));
  color: var(--sale);
  white-space: nowrap;
}
/* "DM for Quotation" replaces the price on items quoted per order. Weighted
   like a price, not a button, so a shelf of cards stays scannable — but it is
   a link, so it gets an underline on hover to say so. */
.quote-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-weight: 750;
  font-size: 0.98rem;
  letter-spacing: -0.01em;
  line-height: 1.15;
  color: var(--accent-deep);
  padding: 5px 12px;
  border-radius: 999px;
  background: var(--accent-wash);
  border: 1px solid var(--accent-edge);
  text-decoration: none;
}
a.quote-link:hover { background: var(--accent-wash-2); text-decoration: none; }
a.quote-link:focus-visible { outline: 3px solid var(--accent-ring); outline-offset: 2px; }

.detail-price.price-row,
.detail-price {
  flex-direction: row;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px 10px;
}
.detail-price .price { font-size: 1.5rem; }

.badge.sold {
  left: auto;
  right: 12px;
  background: var(--ink);
  color: var(--bg);
}

/* Dim the image only — the name and price stay fully legible, because a
   customer still needs to read what it is and decide to ask about it. */
.tile.sold-out .media img,
.tile.sold-out .media video,
.tile.sold-out .media model-viewer,
.tile.sold-out .media .ph { opacity: 0.42; }
.tile.sold-out .media { background: var(--bg-2); }
.tile .desc {
  font-size: 0.88rem;
  color: var(--ink-soft);
  margin-top: 2px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.tile .cta { margin-top: 13px; }

.badge {
  position: absolute;
  top: 12px; left: 12px;
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 6px 11px;
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-ink);
  box-shadow: var(--shadow-sm);
}
.badge.draft { background: var(--ink-soft); color: var(--bg); }
/* Bottom-left, so it can sit alongside the "Hidden" / "3D · AR" badge that
   already owns the top-left corner of a dashboard card. */
.badge.featured-flag { top: auto; bottom: 12px; }

/* model-viewer slots this in and hides it where AR is unsupported, so it never
   advertises a button that cannot work. */
.ar-btn {
  position: absolute;
  bottom: 12px; right: 12px;
  z-index: 3;
  padding: 9px 15px;
  font: inherit;
  font-size: 0.8rem;
  font-weight: 700;
  border: none;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-ink);
  cursor: pointer;
  box-shadow: var(--shadow-md);
}
.ar-btn:hover { filter: brightness(1.08); }

/* Empty-image state. Scoped to `.ph` itself, not `.media .ph`: the detail sheet
   uses `.detail-media`, so the descendant selector silently skipped it there —
   leaving the placeholder statically positioned with a default-sized 339px SVG,
   which read as a huge blank container. Both parents are position:relative. */
.ph {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 9px;
  color: var(--ink-soft);
  background: linear-gradient(140deg, var(--accent-wash), transparent 72%);
}
.ph svg { width: 36px; height: 36px; flex: 0 0 auto; opacity: 0.42; }
.ph span { font-size: 0.76rem; letter-spacing: 0.02em; opacity: 0.85; }

/* Skeleton cards while the first Firestore round trips complete.
   The data cannot arrive faster without server rendering — the shop id itself
   comes from a lookup — but a blank page for two seconds feels broken, whereas
   a laid-out shell reads as loading. Same geometry as a real tile so nothing
   jumps when the products replace it. */
.skeleton .media,
.skeleton .line {
  background: linear-gradient(
    100deg,
    var(--bg-2) 20%,
    color-mix(in srgb, var(--ink) 7%, var(--bg-2)) 40%,
    var(--bg-2) 60%
  );
  background-size: 220% 100%;
  animation: shimmer 1.4s linear infinite;
}
.skeleton .line {
  height: 12px;
  border-radius: 6px;
  margin-top: 10px;
}
.skeleton .line.short { width: 45%; }
@keyframes shimmer {
  to { background-position: -220% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .skeleton .media, .skeleton .line { animation: none; }
}

.empty {
  text-align: center;
  padding: 76px 20px;
  color: var(--ink-soft);
  border: 1.5px dashed var(--rule);
  border-radius: var(--radius);
  margin-top: 22px;
  background: var(--panel);
}

/* ------------------------------------------------------------------ forms */

label {
  display: block;
  font-size: 0.8rem;
  font-weight: 650;
  color: var(--ink-soft);
  margin: 16px 0 6px;
  letter-spacing: 0.005em;
}

/* `search` must be in this list. Left out, the search box gets none of the
   shared sizing — no width:100%, no border, no font-size — and falls back to the
   browser's default ~20-character field with its own native border, which then
   collides with the pill radius applied in `.searchbar`. */
input[type="text"], input[type="email"], input[type="password"],
input[type="number"], input[type="url"], input[type="file"],
input[type="search"],
textarea, select {
  width: 100%;
  padding: 12px 14px;
  font: inherit;
  font-size: 0.95rem;
  color: var(--ink);
  background: var(--panel);
  border: 1.5px solid color-mix(in srgb, var(--rule) 70%, var(--ink-soft));
  border-radius: var(--radius-sm);
  outline: none;
  transition: border-color 0.15s, box-shadow 0.15s;
}
input:focus, textarea:focus, select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 4px var(--accent-ring);
}
textarea { min-height: 96px; resize: vertical; }
input[type="file"] { padding: 9px 12px; font-size: 0.88rem; }
input[type="color"] {
  height: 46px;
  padding: 4px;
  cursor: pointer;
  background: var(--bg);
  border: 1.5px solid var(--rule);
  border-radius: var(--radius-sm);
  width: 100%;
}
input[type="checkbox"] { width: auto; accent-color: var(--accent); }
/* The product editor disables the price fields while "DM for Quotation" is on;
   without this they look editable and typing into them appears to do nothing. */
input:disabled, textarea:disabled, select:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  background: var(--bg-2);
}

.row { display: flex; gap: 14px; flex-wrap: wrap; }
.row > * { flex: 1; min-width: 168px; }

.check {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-top: 18px;
  font-size: 0.92rem;
  font-weight: 500;
  color: var(--ink);
  cursor: pointer;
}

.hint { font-size: 0.82rem; color: var(--ink-soft); margin-top: 7px; }
/* A hint that is actually a warning — a setting that will silently not work. */
.hint.warn { color: var(--danger); font-weight: 600; }
.error { color: var(--danger); font-size: 0.88rem; margin-top: 12px; min-height: 1.2em; }
.error.ok { color: var(--ok); }

.card {
  background: var(--panel);
  border: 1px solid color-mix(in srgb, var(--rule) 75%, var(--ink-soft));
  border-radius: var(--radius);
  padding: 24px;
  box-shadow: var(--shadow-md);
}

/* -------------------------------------------------------------- dashboard */

.adminbar {
  display: flex;
  align-items: center;
  gap: 13px;
  padding: 13px 20px;
  background: var(--panel);
  border-bottom: 1px solid var(--rule);
  position: sticky;
  top: 0;
  z-index: 30;
}
.adminbar .spacer { flex: 1; }
.adminbar-logo {
  width: 38px; height: 38px;
  border-radius: 10px;
  object-fit: cover;
  background: var(--accent-wash);
}
.adminbar-name { font-weight: 750; letter-spacing: -0.02em; }
.adminbar-link { font-size: 0.82rem; }

.adminbar-id { min-width: 0; } /* lets the shop name ellipsis instead of shoving the tabs off-screen */
.adminbar-name,
.adminbar-link {
  display: block;
  max-width: 42vw;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

h2.admin-h { font-size: 1.2rem; font-weight: 760; margin: 38px 0 14px; }
#paneProducts > .pane-head > h2.admin-h,
#paneSettings > h2.admin-h:first-child { margin-top: 8px; }

.pane-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

/* ------------------------------------------------------------------- tabs */

/* One markup strip, two presentations. On desktop it sits inline in the header;
   on phones the same element is pinned to the bottom of the screen, where a
   thumb can actually reach it. Duplicating the buttons per breakpoint would
   mean two sets of ids and two sets of listeners to keep in sync. */
.tabs {
  display: flex;
  gap: 4px;
  padding: 4px;
  background: var(--bg-2);
  border-radius: 999px;
}
.tab {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 8px 16px;
  font: inherit;
  font-size: 0.88rem;
  font-weight: 650;
  color: var(--ink-soft);
  background: transparent;
  border: none;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
}
.tab svg { width: 17px; height: 17px; flex-shrink: 0; }
.tab:hover { color: var(--ink); }
.tab[aria-selected="true"] {
  background: var(--panel);
  color: var(--ink);
  box-shadow: var(--shadow-sm);
}
.tab:focus-visible { outline: 3px solid var(--accent-ring); outline-offset: 2px; }

.show-mobile { display: none; }

@media (max-width: 760px) {
  .hide-mobile { display: none !important; }
  .show-mobile { display: inline-flex; }

  .tabs {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 40;
    gap: 0;
    padding: 6px 8px;
    /* Clears the iOS home indicator; resolves to 0 everywhere else. */
    padding-bottom: calc(6px + env(safe-area-inset-bottom));
    border-radius: 0;
    border-top: 1px solid var(--rule);
    background: color-mix(in srgb, var(--panel) 92%, transparent);
    backdrop-filter: saturate(1.6) blur(14px);
  }
  .tab {
    flex: 1;
    flex-direction: column;
    gap: 3px;
    padding: 7px 4px;
    font-size: 0.7rem;
    letter-spacing: 0.01em;
    border-radius: 12px;
  }
  .tab svg { width: 21px; height: 21px; }
  .tab[aria-selected="true"] { background: var(--accent-wash); color: var(--accent-deep); box-shadow: none; }

  /* Keep the last row of content clear of the fixed bar. */
  #adminView .wrap { padding-bottom: calc(96px + env(safe-area-inset-bottom)); }
}

.bar {
  height: 6px;
  background: var(--bg-2);
  border-radius: 999px;
  overflow: hidden;
  margin-top: 9px;
}
.bar > i {
  display: block;
  height: 100%;
  background: var(--accent);
  width: 0;
  transition: width 0.2s;
}

.thumbs { display: flex; flex-wrap: wrap; gap: 9px; margin-top: 10px; }
.thumbs .t {
  position: relative;
  width: 66px; height: 66px;
  border-radius: var(--radius-sm);
  overflow: hidden;
  border: 1.5px solid var(--rule);
}
.thumbs .t img,
.thumbs .t video { width: 100%; height: 100%; object-fit: cover; }
.thumbs .t video { pointer-events: none; }
.thumbs .t button {
  position: absolute;
  top: 3px; right: 3px;
  width: 19px; height: 19px;
  line-height: 1;
  border-radius: 50%;
  border: none;
  cursor: pointer;
  background: rgb(0 0 0 / 0.68);
  color: #fff;
  font-size: 12px;
}

/* ----------------------------------------------------------------- dialog */

dialog {
  border: 1px solid color-mix(in srgb, var(--rule) 80%, var(--ink-soft));
  border-radius: 22px;
  background: var(--panel);
  color: var(--ink);
  padding: 0;
  width: min(600px, calc(100vw - 28px));
  box-shadow: var(--shadow-lg);
}
dialog::backdrop { background: rgb(12 14 22 / 0.48); backdrop-filter: blur(5px); }
dialog .dlg-body { padding: 26px; max-height: 78vh; overflow-y: auto; }
dialog .dlg-foot {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  padding: 16px 26px;
  border-top: 1px solid var(--rule);
  background: color-mix(in srgb, var(--bg) 55%, var(--panel));
  flex-wrap: wrap;
}
dialog h1 { font-size: 1.5rem; font-weight: 780; }

/* Height-capped rather than a 1:1 box. A square detail view blows a landscape
   photo up to the full width and then crops the sides off — the customer sees
   a zoomed fragment of the thing they're trying to look at, and on a laptop it
   swallows the whole screen. `contain` shows the entire product; letterboxing
   on odd aspect ratios is the lesser evil against cropping. Cards keep `cover`,
   because a grid does need uniform tiles. */
.detail-media {
  position: relative;
  height: min(46vh, 380px);
  border-radius: var(--radius);
  overflow: hidden;
  background:
    linear-gradient(180deg, var(--bg-2), color-mix(in srgb, var(--bg-2) 70%, var(--panel)));
  border: 1px solid var(--rule);
}
.detail-media img,
.detail-media video { width: 100%; height: 100%; object-fit: contain; display: block; background: #111218; }
.detail-media img { background: transparent; }
.detail-media model-viewer { width: 100%; height: 100%; --poster-color: transparent; }
.carousel-frame video { width: 100%; height: 100%; object-fit: contain; display: block; background: #111218; }
.detail-price { margin-top: 8px; }
.detail-price .was { font-size: 1.05rem; }

/* Preserve admin line breaks; product copy often has bullets + a short blurb. */
.detail-desc {
  margin-top: 12px;
  white-space: pre-wrap;
  line-height: 1.6;
  word-break: break-word;
  /* `.muted` is too faint for long product copy on white panels. */
  color: color-mix(in srgb, var(--ink) 86%, var(--ink-soft));
  font-size: 0.95rem;
}

/* Detail-only photo carousel — grid tiles still show the cover image. */
.carousel {
  position: relative;
  width: 100%;
  height: 100%;
  touch-action: pan-y;
  user-select: none;
}
.carousel-frame {
  width: 100%;
  height: 100%;
}
.carousel-frame img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
.carousel-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  width: 36px;
  height: 36px;
  border: 0;
  border-radius: 50%;
  background: rgb(12 12 16 / 0.55);
  color: #fff;
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
  display: grid;
  place-items: center;
  padding: 0;
}
.carousel-nav:hover { background: rgb(12 12 16 / 0.75); }
.carousel-nav:focus-visible { outline: 3px solid var(--accent-ring); outline-offset: 2px; }
.carousel-nav.prev { left: 10px; }
.carousel-nav.next { right: 10px; }
.carousel-dots {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 10px;
  z-index: 2;
  display: flex;
  justify-content: center;
  gap: 6px;
  pointer-events: none;
}
.carousel-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  border: 0;
  padding: 0;
  background: rgb(255 255 255 / 0.45);
  pointer-events: auto;
  cursor: pointer;
}
.carousel-dot[aria-current="true"] {
  background: #fff;
  transform: scale(1.15);
}
.carousel-count {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 2;
  font-size: 0.75rem;
  font-weight: 650;
  color: #fff;
  background: rgb(12 12 16 / 0.55);
  padding: 4px 8px;
  border-radius: 999px;
}

/* ---------------------------------------------------------------- footer */

.site-footer {
  border-top: 1px solid var(--rule);
  background: var(--panel);
  padding: 46px 20px calc(46px + env(safe-area-inset-bottom));
  margin-top: 40px;
}
.footer-inner {
  max-width: 700px;
  margin: 0 auto;
  text-align: center;
}
.footer-logo {
  width: 46px; height: 46px;
  border-radius: 12px;
  object-fit: cover;
  margin-bottom: 12px;
}
.footer-name { font-weight: 750; font-size: 1.05rem; letter-spacing: -0.02em; }
.footer-tagline { color: var(--ink-soft); font-size: 0.9rem; margin: 5px 0 0; }
.site-footer .socials { margin-top: 16px; }
.footer-meta {
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--rule);
  color: var(--ink-soft);
  font-size: 0.8rem;
}

.save-row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 16px;
}
.unsaved {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 0.84rem;
  font-weight: 620;
  color: var(--sale);
}
.unsaved::before {
  content: "";
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--sale);
}

.mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.88em;
}

.dns {
  border: 1px solid var(--rule);
  border-radius: var(--radius-sm);
  overflow-x: auto;
}
.dns-row {
  display: grid;
  grid-template-columns: 90px 90px 1fr;
  gap: 10px;
  padding: 9px 12px;
  min-width: 340px;
  align-items: center;
}
.dns-row + .dns-row { border-top: 1px solid var(--rule); }
.dns-row:first-child {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-soft);
  background: var(--bg-2);
}
.dns-row code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.84rem;
  word-break: break-all;
}

.sold-note {
  margin: 10px 0 0;
  font-size: 0.9rem;
  font-weight: 650;
  color: var(--ink-soft);
}

@media (max-width: 760px) {
  /* Full-height sheet rather than a floating box: the product editor is a long
     form with file pickers, and a centred dialog on a phone leaves it scrolling
     inside a scrolling page with the action buttons stranded off-screen.
     Must target [open] — bare `dialog { display:flex }` overrides the UA
     closed-state `display:none` and can blank or break the sheet on mobile. */
  dialog[open] {
    width: 100vw;
    max-width: 100vw;
    height: 100dvh;
    max-height: 100dvh;
    margin: 0;
    border-radius: 0;
    display: flex;
    flex-direction: column;
  }
  dialog[open] .dlg-body {
    flex: 1;
    max-height: none;
    padding: 20px 18px calc(20px + env(safe-area-inset-bottom));
    padding-top: calc(20px + env(safe-area-inset-top));
  }
  /* Actions pinned to the bottom so Save is always one thumb-tap away. */
  dialog .dlg-foot {
    padding: 12px 18px calc(12px + env(safe-area-inset-bottom));
    background: var(--panel);
  }
  dialog .dlg-foot .btn { flex: 1; }

  .adminbar { padding: 10px 14px; gap: 10px; }
  .adminbar-logo { width: 34px; height: 34px; }
  .row { gap: 10px; }
  .row > * { min-width: 100%; } /* one field per line — 3-up is unusable at 375px */
}

@media (max-width: 560px) {
  .grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
  .tile .body { padding: 11px 11px 13px; gap: 4px; }
  .tile .name {
    font-size: 0.9rem;
    line-height: 1.25;
    /* Two lines max — long titles were shoving the price off-pattern. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .price-row .price { font-size: 0.98rem; }
  .price-row .was { font-size: 0.75rem; }
  .price-row .off { font-size: 0.65rem; padding: 2px 6px; }
  /* Two-up cards leave ~140px of body width; at the desktop size the label
     wrapped inside its own pill. */
  .tile .quote-link { font-size: 0.8rem; padding: 4px 9px; }
  /* Bullet soup from long descriptions is unreadable at 2-up. Full text is
     in the detail sheet. */
  .tile .desc { display: none; }
  .tile .cta { width: 100%; margin-top: 8px; font-size: 0.8rem; padding: 8px 10px; }
  /* Tighter than desktop, not looser: phones are where customers actually
     browse, and vertical space is scarcer there. */
  .hero { padding: 26px 18px 24px; }
  .hero-logo { width: 54px; height: 54px; border-radius: 15px; margin-bottom: 11px; }
  .wrap { padding: 22px 16px 90px; }
  .wrap.narrow { padding-top: 6vh; }
  .detail-media { height: min(40vh, 320px); }
}
