/* classic: filled in by Tasks 0.10-0.13 */

/* ── _base/reset.css, inlined ──────────────────────────────────────────────
   ServeThemeCSS (internal/storefront/handler.go) serves ONLY this file per
   theme — there is no per-theme hashed route for _base/*.css, so a theme
   that needs the shared reset/contract scaffolding inlines it here rather
   than adding a second unguarded <link>. See task-0.11-report.md.
   Remove this block if _base ever gets linked directly. */
.sf :where(a, button) { color: inherit; }
.sf :where(a) { text-decoration: none; }
.sf :where(button) { font: inherit; }
.sf :where(img, svg) { display: block; max-width: 100%; }
.sf :where(*, *::before, *::after) { box-sizing: border-box; }
.sf { margin-inline: 0; padding-inline: 0; }
@media (prefers-reduced-motion: reduce) {
  .sf *, .sf *::before, .sf *::after {
    transition: none !important;
    animation: none !important;
  }
}

/* ── home.html: page shell + banner (Task 0.11) ──────────────────────────── */
.sf-home {
  min-height: 100vh; /* min-h-screen */
  transition: background-color 150ms ease;
  background-image: linear-gradient(to bottom right, #f8fafc, #ffffff, #f1f5f9); /* from-slate-50 via-white to-slate-100 */
}
.sf-home--dark {
  background-image: linear-gradient(to bottom right, #020617, #0f172a, #020617); /* from-slate-950 via-slate-900 to-slate-950 */
}

.sf-banner { width: 100%; }
.sf-banner__img {
  display: block;
  width: 100%;
  height: auto;
  /* Round 21 (ab, live on test): only an UPPER BOUND, never a
     template-chosen aspect box — the gh-1295 rule stands (the real ratio is
     unknowable server-side). A short/wide banner renders exactly as before;
     a tall one, or a small one stretched full-width (store10's 568x320 came
     out ~790px tall and upscaled on a 1400px viewport), is capped and
     center-cropped instead of eating the whole first screen. object-fit
     only ever engages once the cap bites, so nothing is squashed. */
  max-height: min(38vh, 360px);
  object-fit: cover;
  object-position: center;
}

/* ── collection.html: header (gh-1389 Task C.6) ─────────────────────────────
   No forced aspect box / object-cover on .sf-collection__banner-img — same
   gh-1295 rule as .sf-banner__img above: only the URL is stored, so the
   real dimensions are unknowable server-side. */
.sf-collection__banner { width: 100%; }
.sf-collection__banner-img { display: block; width: 100%; height: auto; }

.sf-collection__header {
  max-width: 80rem; /* max-w-7xl, matches .sf-main below */
  margin-inline: auto;
  padding-inline: 1rem;
  padding-block-start: 2rem; /* pt-8 */
  padding-block-end: 1rem; /* pb-4 */
}
.sf-collection__title {
  font-size: 1.5rem; /* text-2xl, same scale as .sf-lookup__heading/.sf-track__heading */
  line-height: 2rem;
  font-weight: 700;
  color: #0f172a; /* text-slate-900 */
}
@media (min-width: 640px) {
  .sf-collection__title { font-size: 1.875rem; line-height: 2.25rem; } /* sm:text-3xl */
}
.sf-collection__header--dark .sf-collection__title { color: #ffffff; }
.sf-collection__desc {
  margin-top: 0.5rem; /* mt-2 */
  font-size: 0.875rem; /* text-sm */
  line-height: 1.25rem;
  color: #475569; /* text-slate-600 */
  max-width: 42rem; /* max-w-2xl — keep long descriptions readable */
}
.sf-collection__header--dark .sf-collection__desc { color: #94a3b8; } /* text-slate-400 */
.sf-collection__desc--rtl,
.sf-collection__title--rtl { text-align: right; }

/* Branded-404 state only (Collection == nil): same link styling family as
   .sf-cart__continue's ghost variant. */
.sf-collection__back-link {
  display: inline-block;
  margin-top: 1rem; /* mt-4 */
  padding-inline: 1.25rem; /* px-5 */
  padding-block: 0.625rem; /* py-2.5 */
  border-radius: 0.75rem; /* rounded-xl */
  border: 1px solid #cbd5e1; /* border-slate-300 */
  color: #334155; /* text-slate-700 */
  font-weight: 700;
}
.sf-collection__header--dark .sf-collection__back-link {
  border-color: #334155; /* border-slate-700 */
  color: #e2e8f0; /* text-slate-200 */
}

/* gh-1389 Task 4.3: the "collection-strip" catalog section — reuses .sf-grid/
   .sf-card* above for its member-product grid (same reuse collection.html's
   own grid already makes); these are the only new classes it adds. */
.sf-collection-strip { margin-block: 2rem; } /* my-8 */
.sf-collection-strip__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1rem; /* mb-4 */
}
.sf-collection-strip__title {
  font-size: 1.25rem; /* text-xl */
  line-height: 1.75rem;
  font-weight: 700;
  color: #0f172a; /* text-slate-900 */
}
.sf-collection-strip__view-all {
  font-size: 0.875rem; /* text-sm */
  font-weight: 700;
  color: var(--brand);
  white-space: nowrap;
}
.sf-collection-strip__view-all:hover { text-decoration: underline; }
/* section_collection_strip.html's own optional "heading" setting, sized to
   match .sf-product__description-heading's own weight (gh-1389 Task 4.2). */
.sf-collection-strip__heading {
  font-size: 1.5rem; line-height: 2rem; font-weight: 700; margin-bottom: 1rem; color: #0f172a; /* text-2xl font-bold mb-4 text-slate-900 */
}

/* gh-1389 Task 4.4: the "featured-product" catalog section — ONE
   seller-chosen product in a two-column showcase. .sf-featured-product__badge/
   __was reuse .sf-card__badge/__was's own visual language (new class names:
   this is a showcase layout, not a grid cell, so nothing here is a literal
   shared partial — same "reuse the card's building blocks" posture
   collection-strip's own doc comment already established). "layout"
   (image-left/image-right) is CSS-only, driven entirely by the
   --image-right modifier class the L3 dispatch template
   (section_featured_product.html) picks off .Settings.layout — no JS. */
.sf-featured-product { margin-block: 2rem; }
.sf-featured-product__heading {
  font-size: 1.5rem; line-height: 2rem; font-weight: 700; margin-bottom: 1rem; color: #0f172a;
}
.sf-featured-product__row {
  display: flex;
  flex-direction: column;
  gap: 0;
  border-radius: 1rem;
  border: 1px solid #e2e8f0; /* border-slate-200 */
  overflow: hidden;
  background-color: rgba(255, 255, 255, 0.5); /* bg-white/50, mirrors .sf-card */
}
.sf-featured-product--dark .sf-featured-product__row {
  background-color: rgba(30, 41, 59, 0.5); /* bg-slate-800/50 */
  border-color: #334155; /* border-slate-700 */
}
@media (min-width: 768px) {
  .sf-featured-product__row { flex-direction: row; }
  .sf-featured-product--image-right .sf-featured-product__row { flex-direction: row-reverse; }
}
.sf-featured-product__media {
  position: relative;
  flex: 1 1 50%;
  min-height: 16rem;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background-color: #f3f4f6; /* bg-gray-100, mirrors .sf-card__pad */
}
.sf-featured-product__img { width: 100%; height: 100%; object-fit: cover; }
.sf-featured-product__placeholder { font-size: 3rem; }
.sf-featured-product__badge {
  position: absolute;
  top: 0.75rem;
  inset-inline-start: 0.75rem;
  background-color: #e11d48;
  color: #fff;
  font-size: 0.75rem;
  font-weight: 700;
  padding: 0.25rem 0.625rem;
  border-radius: 9999px;
}
.sf-featured-product__body {
  flex: 1 1 50%;
  padding: 1.5rem;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.75rem;
}
.sf-featured-product__name { font-size: 1.25rem; line-height: 1.75rem; font-weight: 700; color: #0f172a; }
.sf-featured-product__desc { font-size: 0.875rem; line-height: 1.375rem; color: #64748b; }
.sf-featured-product__price-row { display: flex; align-items: baseline; gap: 0.5rem; }
.sf-featured-product__price { font-size: 1.5rem; line-height: 2rem; font-weight: 700; }
.sf-featured-product__was { font-size: 0.875rem; line-height: 1.25rem; color: #64748b; text-decoration: line-through; }
.sf-featured-product__cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: fit-content;
  border-radius: 0.75rem;
  padding: 0.75rem 1.5rem;
  font-weight: 700;
}

/* ── home.html: filter bar (search count + sort dropdown) (Task 0.11) ──────
   .storefront-sticky-below-nav (kept as-is, an existing non-Tailwind class
   driven by storefront-nav-offset.js) still supplies `position:sticky` and
   the measured `top` offset — see internal/storefront/CLAUDE.md's sticky-bar
   rule; this stylesheet only adds the remaining utility-equivalent styling. */
.sf-filterbar {
  position: sticky; /* the "sticky" Tailwind utility, previously alongside storefront-sticky-below-nav —
                        that class supplies only `top`, not `position`; dropping this one would still
                        pass the pixel harness (it never scrolls) but breaks the actual sticky behavior
                        gh-1295 depends on. See internal/storefront/home_banner_layout_test.go. */
  z-index: 40;
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  border-bottom: 1px solid #e2e8f0; /* border-slate-200 */
  padding-block: 1rem;
  transition: background-color 150ms ease, border-color 150ms ease;
  background-color: rgba(255, 255, 255, 0.8); /* bg-white/80 */
}
.sf-filterbar--dark {
  background-color: rgba(15, 23, 42, 0.8); /* bg-slate-900/80 */
  border-bottom-color: #1e293b; /* border-slate-800 */
}

/* gh-1389 Task K fix round 1: sections/product-grid.html renders the filter
   bar as a PRECEDING SIBLING of its own <section class="sf-section
   sf-section--product-grid">, not nested inside it (nesting would put the
   bar under whatever padding that section carries — see that file's own
   header comment). classic ships no .sf-section/.sf-section--product-grid
   rule at all today, so without this the grid sat flush (0px) against the
   bar with no deliberate spacing. classic's composed home never reaches
   production today (its `defaults.home` names section types the catalog
   doesn't resolve, so it always falls back to legacy home.html — see
   home_composition_test.go's TestShippedThemesComposeExactlyWhenTheyCan),
   but a theme rule is standalone (internal/storefront/CLAUDE.md) and this
   theme WILL reach this path the day its defaults resolve. Verified with a
   Playwright measurement against this theme's own real CSS (task-K review
   fix round 1 report): before this rule, bar-to-grid gap was 0px. */
.sf-filterbar + .sf-section--product-grid { padding-block-start: 24px; }

.sf-filterbar__inner {
  max-width: 80rem;
  margin-inline: auto;
  padding-inline: 1rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1rem;
}

.sf-filterbar__count {
  font-size: 0.875rem;
  line-height: 1.25rem;
  font-weight: 500;
  color: #475569; /* text-slate-600 — dark:text-slate-400 was dead, see nav note */
}

.sf-filterbar__sort { position: relative; }

.sf-filterbar__sort-toggle {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding-inline: 1rem;
  padding-block: 0.5rem;
  border-radius: 0.5rem;
  border: 1px solid #e2e8f0; /* border-slate-200 */
  background-color: #ffffff;
  color: #334155; /* text-slate-700 */
  transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;
}
.sf-filterbar__sort-toggle--dark {
  border-color: #334155; /* border-slate-700 */
  background-color: #1e293b; /* bg-slate-800 */
  color: #cbd5e1; /* text-slate-300 */
}

.sf-filterbar__sort-icon {
  width: 1rem;
  height: 1rem;
  transition: transform 150ms ease;
}

.sf-filterbar__sort-menu {
  position: absolute;
  left: 0;
  margin-top: 0.5rem;
  width: 12rem; /* w-48 */
  border-radius: 0.5rem;
  border: 1px solid #e2e8f0; /* border-slate-200 */
  box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); /* shadow-lg */
  z-index: 50;
  background-color: #ffffff;
}
.sf-filterbar__sort-menu--rtl { left: auto; right: 0; }
.sf-filterbar__sort-menu--dark {
  background-color: #1e293b; /* bg-slate-800 */
  border-color: #334155; /* border-slate-700 */
}

.sf-filterbar__sort-link {
  display: block;
  width: 100%;
  text-align: left;
  padding-inline: 1rem;
  padding-block: 0.5rem;
  font-size: 0.875rem;
  line-height: 1.25rem;
}
.sf-filterbar__sort-link:hover { background-color: #f1f5f9; } /* hover:bg-slate-100 — dark:hover:bg-slate-700 was dead */
.sf-filterbar__sort-link--rtl { text-align: right; }
.sf-filterbar__sort-link--active { font-weight: 700; color: var(--brand); }

/* ── Category chips (gh-1389 Task 12b) ──────────────────────────────────────
   Pill chips — classic's own active-state language, matching
   .sf-of__dtype-btn--active (border/bg/text all in var(--brand)) rather than
   inventing a fourth "selected" treatment. */
.sf-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding-block-end: 1.25rem;
}
.sf-chip {
  display: inline-flex;
  align-items: center;
  border-radius: 9999px; /* rounded-full */
  padding-inline: 1rem;
  padding-block: 0.375rem;
  font-size: 0.8125rem;
  font-weight: 600;
  border: 1px solid #e2e8f0; /* border-slate-200 */
  background-color: #ffffff;
  color: #475569; /* text-slate-600 */
  text-decoration: none;
  transition: border-color 150ms ease, background-color 150ms ease, color 150ms ease;
}
.sf-chip:hover { border-color: var(--brand); color: var(--brand); }
/* .sf-chip--active is redundant with [aria-pressed="true"] — the template
   always sets both together — but the class ships in the markup (review fix
   round 2, gh-1389 Task 12b) so it must carry its own declaration or a chip
   restyled via the class alone (no ARIA state) would render bare. */
.sf-chip[aria-pressed="true"],
.sf-chip--active {
  border-color: var(--brand);
  background-color: color-mix(in srgb, var(--brand) 8%, white); /* bg-brand-50 equivalent */
  color: var(--brand);
}

/* ── home.html: main content wrapper + product grid (Task 0.11) ────────────
   .sf-grid CSS reproduces the previous "grid grid-cols-2 md:grid-cols-3
   lg:grid-cols-4 gap-4 md:gap-6" utility set exactly. */
.sf-main {
  max-width: 80rem; /* max-w-7xl */
  margin-inline: auto;
  padding-inline: 1rem;
  padding-block: 3rem; /* py-12 */
}

.sf-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1rem;
}
@media (min-width: 768px) {
  .sf-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1.5rem; }
}
@media (min-width: 1024px) {
  .sf-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* gh-1389 Phase 4 fix wave I4: collection-strip's own "columns" setting
   (sections.ColumnsCSSVars injects --sf-cols/--sf-cols-mobile onto the
   section's root element — see section_collection_strip.html's own doc
   comment). Scoped to .sf-collection-strip so the SITE-WIDE .sf-grid above
   (home/collection page's own product grid, never seller-configurable) is
   untouched. Container query, not the @media rules above — matches
   noor/theme.css's own identical rule and this codebase's own convention
   for a builder device-toggle setting (spec §6.3, the SAME 760px cutoff
   [data-sf-hidden-mobile] uses just above in this file). */
.sf-collection-strip .sf-grid { grid-template-columns: repeat(var(--sf-cols, 4), minmax(0, 1fr)); }
@container (max-width: 760px) {
  .sf-collection-strip .sf-grid { grid-template-columns: repeat(var(--sf-cols-mobile, 2), minmax(0, 1fr)); }
}

.sf-grid__empty { grid-column: 1 / -1; text-align: center; padding-block: 5rem; } /* col-span-full py-20 */
.sf-grid__empty-text {
  color: #475569; /* text-slate-600 — dark:text-slate-400 was dead, see nav note */
  font-size: 1.125rem;
  line-height: 1.75rem;
}

.sf-grid__more { grid-column: 1 / -1; padding-block: 2rem; text-align: center; color: #9ca3af; } /* text-gray-400 */
.sf-grid__load-trigger { grid-column: 1 / -1; padding-block: 2rem; text-align: center; color: #9ca3af; } /* converted from: col-span-full py-8 text-center text-gray-400 */

/* ── product card (Task 0.11) — shared by home.html's initial render AND
   partials/product_list.html's HTMX swap. The two markups are NOT identical:
   product_list.html's card carries an inline `border-color: transparent`
   plus an Alpine @mouseenter/@mouseleave pair that paints the border/shadow
   from the store's MainColor at runtime (untouched here, not a Tailwind
   utility); home.html's card instead gets a static focus-visible ring. Both
   keep working unmodified — this stylesheet only replaces what were plain
   utility classes in both files. object-fit:cover (not contract.css's
   suggested contain) is deliberate: it is what today's markup renders and
   Task 0.11 must not change a pixel. */
.sf-card-wrap { height: 100%; border-radius: 0.75rem; }
.sf-card-link { height: 100%; display: block; }

.sf-card {
  height: 100%;
  border-radius: 0.75rem;
  border: 1px solid #e2e8f0; /* border-slate-200 */
  overflow: hidden;
  display: flex;
  flex-direction: column;
  background-color: rgba(255, 255, 255, 0.5); /* bg-white/50 */
  transition: box-shadow 150ms ease, background-color 150ms ease, border-color 150ms ease;
}
.sf-card--dark {
  background-color: rgba(30, 41, 59, 0.5); /* bg-slate-800/50 */
  border-color: #334155; /* border-slate-700 */
}
.sf-card:hover { box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); } /* hover:shadow-lg */
.sf-card:focus-visible { box-shadow: 0 0 0 2px var(--brand); } /* focus-visible:ring-2 ring-brand-600 (home.html only) — ring-brand-600 is remapped to var(--brand) per-store by base.html, so this must not hardcode the default hex */

.sf-card__pad {
  position: relative;
  height: 8rem; /* h-32 */
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background-color: #f3f4f6; /* bg-gray-100 — dark:bg-slate-700 was dead, see nav note */
}
@media (min-width: 768px) {
  .sf-card__pad { height: 10rem; } /* md:h-40 */
}

.sf-card__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 500ms ease; /* duration-500 */
}
.sf-card-wrap:hover .sf-card__img { transform: scale(1.1); } /* group-hover:scale-110 */

.sf-card__placeholder { font-size: 2.25rem; line-height: 2.5rem; } /* text-4xl */

.sf-card__badge {
  position: absolute;
  top: 0.5rem;
  left: 0.5rem;
  background-color: #ef4444; /* bg-red-500 */
  color: #ffffff;
  padding-inline: 0.5rem;
  padding-block: 0.25rem;
  border-radius: 0.5rem;
  font-size: 0.75rem;
  line-height: 1rem;
  font-weight: 700;
}

/* gh-1648 — the sold-out chip. `.sf-card__soldout` reached the base contract
   with gh-1532 but no card surface emitted it until now, so classic had
   nothing to style it against.

   LOGICAL insets, not the physical `top`/`left` .sf-card__badge above still
   carries: Arabic is the platform default and its cards are RTL, so the chip
   pins to the reading-start corner. The two never paint together — a sold-out
   card's discount badge stands down — so the axis difference cannot show.

   Not muted. Every other theme drew this as a quiet grey pill, which is the
   opposite of what a card needs: the state it announces is the one that stops
   a sale. slate-700 on white is 10.35:1. */
.sf-card__soldout {
  position: absolute;
  inset-block-start: 0.5rem;
  inset-inline-start: 0.5rem;
  z-index: 2;
  padding-inline: 0.5rem;
  padding-block: 0.25rem;
  border: 1px solid rgba(15, 23, 42, 0.08);
  border-radius: 0.5rem;
  background-color: rgba(255, 255, 255, 0.94);
  color: #334155; /* slate-700 */
  font-size: 0.75rem;
  line-height: 1rem;
  font-weight: 700;
}
/* 14.48:1 — slate-200 on slate-900. */
.sf-card--dark .sf-card__soldout {
  border-color: rgba(255, 255, 255, 0.14);
  background-color: rgba(15, 23, 42, 0.94);
  color: #e2e8f0;
}

.sf-card__like {
  position: absolute;
  top: 0.5rem;
  right: 0.5rem;
  padding: 0.375rem;
  border-radius: 9999px;
  background-color: rgba(255, 255, 255, 0.8);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  transition: background-color 150ms ease;
}
.sf-card__like:hover { background-color: #ffffff; }

.sf-card__like-icon { width: 1.25rem; height: 1.25rem; }

/* gh-1532 quick-add. A pinned disc on the OPPOSITE corner from .sf-card__like,
   in the same visual language (translucent white, blurred, circular) so the
   two read as one family, one size up because this one is the purchase
   affordance.

   Logical insets, not the physical top/right .sf-card__like still uses: this
   control is new, so nothing is pinned to its Arabic position, and
   inset-inline-end mirrors for free.

   Every colour here is a literal, never var(--brand): the brand is the
   seller's MainColor and can be any hue including a pale one, so a white icon
   on a brand disc is a contrast lottery. slate-900 on white MEASURES 17.7:1
   (every ratio in this block is measured against the painted result, not
   computed from the two literals — the disc is translucent), and the hover
   inverts to the same pair. */
.sf-card__add {
  position: absolute;
  inset-block-end: 0.5rem;
  inset-inline-end: 0.5rem;
  z-index: 2;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 9999px;
  border: 1px solid rgba(15, 23, 42, 0.08);
  background-color: rgba(255, 255, 255, 0.9);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  color: #0f172a; /* slate-900 on white — 17.7:1 measured, well past the 3:1 the icon needs */
  box-shadow: 0 1px 2px rgb(0 0 0 / 0.08);
  cursor: pointer;
  transition: background-color 150ms ease, color 150ms ease, transform 150ms ease;
}
.sf-card__add:hover { background-color: #0f172a; color: #ffffff; }
.sf-card__add:active { transform: scale(0.94); }
.sf-card__add:focus-visible { outline: 2px solid #0f172a; outline-offset: 2px; }

/* Sold out. Not opacity: dimming the whole control dims the icon against a
   dimmed disc, so the pair keeps its ratio and nothing reads as unavailable.
   Grey both, explicitly. */
.sf-card__add[aria-disabled="true"] {
  background-color: #e2e8f0; /* slate-200 */
  border-color: transparent;
  color: #64748b;            /* slate-500 on slate-200 — 3.86:1 measured */
  box-shadow: none;
  cursor: not-allowed;
}
.sf-card__add[aria-disabled="true"]:hover { background-color: #e2e8f0; color: #64748b; }
.sf-card__add[aria-disabled="true"]:active { transform: none; }

/* The 1800ms confirmation cart.js sets after an add. green-700, so white on it
   measures 5.02:1 — and it is a state the buyer sees, not decoration. */
.sf-card__add[data-sf-added="true"],
.sf-card__add[data-sf-added="true"]:hover {
  background-color: #15803d;
  border-color: transparent;
  color: #ffffff;
}

/* --pick sends the buyer to the picker instead of adding. Same disc: the
   difference is where the tap goes, and the card's own link already says
   "product page". */
.sf-card__add--pick { background-color: rgba(255, 255, 255, 0.9); }

.sf-card__add-icon { width: 1.125rem; height: 1.125rem; }
/* The done glyph ships with the `hidden` attribute and cart.js flips the
   pair. This rule is explicit rather than left to the UA: an author
   `display` on .sf-card__add-icon outranks the UA sheet, and the failure
   mode is both glyphs painted on top of each other. */
.sf-card__add-icon[hidden] { display: none; }


.sf-card__body { padding: 0.75rem; flex: 1 1 0%; display: flex; flex-direction: column; }

.sf-card__name {
  font-size: 0.75rem; /* text-xs */
  line-height: 1rem;
  font-weight: 700;
  color: #0f172a; /* text-slate-900 — dark:text-white was dead, see nav note */
  margin-bottom: 0.25rem;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-align: left;
}
@media (min-width: 768px) {
  .sf-card__name { font-size: 0.875rem; line-height: 1.25rem; } /* md:text-sm */
}
.sf-card__name--rtl { text-align: right; }

.sf-card__desc {
  font-size: 0.75rem;
  line-height: 1rem;
  color: #475569; /* text-slate-600 — dark:text-slate-400 was dead, see nav note */
  margin-bottom: 0.75rem;
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
  text-align: left;
}
.sf-card__desc--rtl { text-align: right; }

.sf-card__price-row-wrap {
  margin-top: auto;
  padding-top: 0.75rem;
  border-top: 1px solid #e2e8f0; /* border-slate-200 — dark:border-slate-700 was dead, see nav note */
}

.sf-card__price-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}

.sf-card__price { font-size: 1.125rem; line-height: 1.75rem; font-weight: 700; } /* text-lg */
@media (min-width: 768px) {
  .sf-card__price { font-size: 1.25rem; line-height: 1.75rem; } /* md:text-xl */
}

.sf-card__was { font-size: 0.75rem; line-height: 1rem; color: #64748b; text-decoration: line-through; } /* text-xs text-slate-500 */
@media (min-width: 768px) {
  .sf-card__was { font-size: 0.875rem; line-height: 1.25rem; } /* md:text-sm */
}

.sf-card__cta {
  display: block;
  width: 100%;
  margin-top: 0.75rem;
  padding-block: 0.5rem;
  border-radius: 0.5rem;
  color: #ffffff;
  font-weight: 600;
  font-size: 0.75rem;
  line-height: 1rem;
  text-align: center;
  transition: box-shadow 150ms ease;
  /* No border rule here: neither the home.html <span> nor product_list.html's
     <button> ever carried a border utility class — leave the browser's own
     default (none, for a <button> under this repo's Tailwind preflight)
     alone rather than assert a value the pre-conversion markup never set. */
}
@media (min-width: 768px) {
  .sf-card__cta { font-size: 0.875rem; line-height: 1.25rem; } /* md:text-sm */
}
.sf-card__cta--hoverable:hover { box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); } /* hover:shadow-lg, product_list.html only */

/* ── home.html: classic nav (Task 0.11) ─────────────────────────────────────
   Reproduces the previous "sticky top-0 z-50 backdrop-blur-xl border-b
   transition-colors {IsDark? bg-slate-900/80 border-slate-800 : bg-white/80
   border-slate-200}" utility set exactly. Several `{{ if .IsDark }}
   dark:...{{ end }}` fragments in the pre-conversion markup were DEAD CODE —
   Tailwind's `dark:` variant needs a `.dark` ancestor class
   (darkMode:"class" in tailwind.config.js) and nothing in this codebase ever
   adds one, so those fragments never painted a pixel; they are dropped here,
   not ported. The if/else literal-class-swap sites (nav/search-box
   background, search input text/placeholder) ARE real (plain conditional
   classes, no `dark:` prefix) and keep their --dark modifier so this stays
   correct if IsDark is ever wired to true. */
.sf-nav {
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  border-bottom: 1px solid #e2e8f0; /* border-slate-200 */
  background-color: rgba(255, 255, 255, 0.8); /* bg-white/80 */
  transition: background-color 150ms ease, border-color 150ms ease;
  /* gh-1389 Task I: anchors .sf-nav__menu-panel (position:absolute) even on
     the product page, whose nav carries no --sticky modifier and so had no
     positioned ancestor at all. --sticky's `position:sticky` below wins the
     cascade when present (same specificity, later in the file). */
  position: relative;
}
.sf-nav--dark {
  background-color: rgba(15, 23, 42, 0.8); /* bg-slate-900/80 */
  border-bottom-color: #1e293b; /* border-slate-800 */
}
/* home.html's nav is "sticky top-0 z-50"; product_v2.html's (Task 0.12) never
   was — its header wrapper scrolls away. Split out so .sf-nav stays shared
   without silently making the product nav sticky (invisible to a static
   pixel screenshot, per the Task 0.11/0.12 sticky-bar lesson). */
.sf-nav--sticky {
  position: sticky;
  top: 0;
  z-index: 50;
}

.sf-nav__inner {
  max-width: 80rem; /* max-w-7xl */
  margin-inline: auto;
  padding-inline: 1rem;
  padding-block: 1rem;
}

.sf-nav__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  /* gh-1389 Task I: the row must never wrap to a second line — the inline
     links container below is the one that scrolls instead. flex's initial
     value is already nowrap; explicit per the task brief. */
  flex-wrap: nowrap;
}

.sf-nav__logo {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
}

.sf-nav__logo-img {
  height: 3.5rem; /* h-14 */
  width: auto;
  object-fit: contain;
}
@media (min-width: 640px) {
  .sf-nav__logo-img { height: 3rem; } /* sm:h-12 */
}

.sf-nav__logo-badge {
  width: 3rem;
  height: 3rem;
  border-radius: 0.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #ffffff;
  font-weight: 700;
}

.sf-nav__logo-name {
  font-size: 1.25rem;
  line-height: 1.75rem;
  font-weight: 700;
  color: #0f172a; /* text-slate-900 — dark:text-white was dead, see note above */
}

.sf-nav__search {
  display: none;
  flex: 1 1 0%;
  max-width: 28rem; /* max-w-md */
  margin-inline: 1rem;
}
@media (min-width: 768px) {
  .sf-nav__search { display: block; }
}

.sf-nav__search-box {
  position: relative;
  border-radius: 0.5rem;
  border: 1px solid #e2e8f0; /* border-slate-200 */
  background-color: #ffffff;
  transition: background-color 150ms ease, border-color 150ms ease;
}
.sf-nav__search-box--dark {
  border-color: #334155; /* border-slate-700 */
  background-color: #1e293b; /* bg-slate-800 */
}

.sf-nav__search-input {
  width: 100%;
  padding-left: 2.5rem; /* pl-10 */
  padding-right: 1rem; /* pr-4 */
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
  border-radius: 0.5rem;
  outline: none;
  font-size: 0.875rem;
  line-height: 1.25rem;
  text-align: left;
  background-color: transparent;
  color: #0f172a; /* text-slate-900 */
}
.sf-nav__search-input::placeholder { color: #64748b; } /* placeholder-slate-500 */
.sf-nav__search-input--rtl { text-align: right; }
.sf-nav__search-input--dark { color: #ffffff; }
.sf-nav__search-input--dark::placeholder { color: #94a3b8; } /* placeholder-slate-400 */

.sf-nav__search-icon {
  position: absolute;
  left: 0.75rem;
  top: 50%;
  transform: translateY(-50%);
  width: 1rem;
  height: 1rem;
  color: #94a3b8; /* text-slate-400 */
  pointer-events: none;
}

.sf-nav__search-toggle-wrap {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
@media (min-width: 768px) {
  .sf-nav__search-toggle-wrap { display: none; }
}

.sf-nav__search-toggle {
  padding: 0.5rem;
}

.sf-nav__search-toggle-icon {
  width: 1.25rem;
  height: 1.25rem;
  color: #0f172a; /* text-slate-900 — dark:text-white was dead, see note above */
}

.sf-nav__search-mobile {
  margin-top: 1rem;
}
@media (min-width: 768px) {
  .sf-nav__search-mobile { display: none; }
}

.sf-nav__search-mobile-input {
  width: 100%;
  padding-inline: 1rem;
  padding-block: 0.5rem;
  border-radius: 0.5rem;
  outline: none;
  font-size: 0.875rem;
  line-height: 1.25rem;
  background-color: #f9fafb; /* bg-gray-50 — dark:bg-slate-800/text-white was dead, see note above */
}

/* gh-1389 Task D → Task I: the storefront nav's custom-page + track-order
   link row. Task D shipped this as a SECOND LINE below sf-nav__row on every
   width (ab, round 21: "it should be one [line]"). Task I makes it ONE row:
   this container now renders INLINE inside sf-nav__row on desktop — hidden
   on mobile, where .sf-nav__burger (below) opens the same links in
   .sf-nav__menu-panel instead. min-width:0 is load-bearing: a flex child
   needs it before overflow-x:auto can ever shrink it below its content's
   natural width (docs/gotchas-ui.md's fieldset-width-floors lesson,
   generalized to flex). */
.sf-nav__links {
  display: none;
  align-items: center;
  gap: 1.25rem;
  min-width: 0;
  flex: 1 1 auto;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
}
@media (min-width: 768px) {
  .sf-nav__links { display: flex; }
}

.sf-nav__link {
  flex-shrink: 0;
  font-size: 0.875rem;
  line-height: 1.25rem;
  font-weight: 600;
  color: #334155; /* text-slate-700 */
  white-space: nowrap;
  padding-block: 0.125rem;
  border-bottom: 2px solid transparent;
  transition: color 150ms ease, border-color 150ms ease;
}
.sf-nav__link:hover { color: #0f172a; } /* text-slate-900 */
.sf-nav__link--active {
  color: #0f172a; /* text-slate-900 */
  border-bottom-color: currentColor;
}
.sf-nav--dark .sf-nav__link { color: #cbd5e1; } /* text-slate-300 */
.sf-nav--dark .sf-nav__link:hover,
.sf-nav--dark .sf-nav__link--active { color: #ffffff; }

/* gh-1389 Task I: the mobile burger + its dropdown panel.

   .sf-nav__mobile-controls wraps the (optional) search toggle and the
   burger together. `display: contents` makes the wrapper invisible to
   layout — its children behave as if they were direct children of
   sf-nav__row/sf-pnav__row, which is exactly what classic's flex row
   already expected before this wrapper existed. noor's grid needs the
   wrapper to be a real box (its own rule overrides this), which is the
   whole reason it exists rather than leaving the two buttons as loose
   siblings. */
.sf-nav__mobile-controls {
  display: contents;
}

.sf-nav__burger {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.5rem;
}
@media (min-width: 768px) {
  .sf-nav__burger { display: none; }
}
.sf-nav__burger-icon {
  width: 1.25rem;
  height: 1.25rem;
  color: #0f172a; /* text-slate-900 */
}
.sf-nav--dark .sf-nav__burger-icon { color: #ffffff; }

/* Absolutely positioned below the WHOLE nav (.sf-nav is position:relative
   or --sticky, both establish the containing block) — opening it never
   grows the bar itself, satisfying the "row height must never grow" rule
   the inline list above follows too. Alpine's x-show clears the inline
   `display:none` it sets when closed, letting this rule's own `display:
   none` (desktop) or default block (mobile, open) govern — no !important
   needed. */
.sf-nav__menu-panel {
  position: absolute;
  inset-inline: 0;
  top: 100%;
  z-index: 45;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  padding: 0.75rem 1rem 1rem;
  background-color: #ffffff;
  border-top: 1px solid #e2e8f0; /* border-slate-200 */
  border-bottom: 1px solid #e2e8f0;
  box-shadow: 0 12px 24px -12px rgba(15, 23, 42, 0.25);
  max-height: calc(100vh - 100%);
  overflow-y: auto;
}
@media (min-width: 768px) {
  .sf-nav__menu-panel { display: none; }
}
.sf-nav--dark .sf-nav__menu-panel {
  background-color: #0f172a; /* bg-slate-900 */
  border-color: #1e293b; /* border-slate-800 */
}
.sf-nav__menu-panel .sf-nav__link {
  display: block;
  padding-block: 0.625rem;
  padding-inline: 0.5rem;
  border-bottom: none;
  border-radius: 0.375rem;
}
.sf-nav__menu-panel .sf-nav__link--active {
  background-color: #f1f5f9; /* bg-slate-100 */
}
.sf-nav--dark .sf-nav__menu-panel .sf-nav__link--active {
  background-color: #1e293b; /* bg-slate-800 */
}

/* ── product_v2.html: classic nav (Task 0.12) ───────────────────────────────
   Shares .sf-nav (minus --sticky) and the logo-badge/logo-img/logo-name and
   search-box/search-input/search-icon rules above — those utility sets were
   byte-identical between home.html's pre-conversion nav and this one. Only
   the container/row metrics and the logo's wrapper nesting genuinely differ
   (no mobile search toggle exists on the product nav at all — never did),
   so only those get their own classes, prefixed sf-pnav to make clear they
   are NOT a second definition of sf-nav__inner/sf-nav__row. */
.sf-pnav__container {
  max-width: 80rem; /* max-w-7xl */
  margin-inline: auto;
  padding-inline: 1rem; /* px-4 */
}
@media (min-width: 640px) {
  .sf-pnav__container { padding-inline: 1.5rem; } /* sm:px-6 */
}
@media (min-width: 1024px) {
  .sf-pnav__container { padding-inline: 2rem; } /* lg:px-8 */
}

.sf-pnav__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: 4rem; /* h-16 */
  gap: 1rem;
  flex-wrap: nowrap; /* gh-1389 Task I — see sf-nav__row's own comment */
}

.sf-pnav__logo-wrap {
  flex-shrink: 0;
  display: flex;
  align-items: center;
}

.sf-pnav__logo-fallback {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* ── product_v2.html: page shell + main layout (Task 0.12) ──────────────────
   NOT reusing .sf-home — same "min-h-screen transition-colors" base but a
   different gradient (bg-gradient-to-b, 2 stops here vs bg-gradient-to-br,
   3 stops on home.html), so a shared class would either change one page's
   gradient or need a modifier per axis. Kept separate on purpose. */
.sf-product {
  min-height: 100vh; /* min-h-screen */
  transition: background-color 150ms ease; /* transition-colors */
  background-image: linear-gradient(to bottom, #f8fafc, #ffffff); /* from-slate-50 to-white */
}
.sf-product--dark {
  background-image: linear-gradient(to bottom, #020617, #0f172a); /* from-slate-950 to-slate-900 */
}

.sf-product__header {
  position: relative;
  z-index: 50;
  width: 100%;
  display: flex;
  flex-direction: column;
}

.sf-product__main {
  max-width: 64rem; /* max-w-5xl */
  margin-inline: auto;
  padding-inline: 1rem; /* px-4 */
  padding-block: 1rem; /* py-4 */
}
@media (min-width: 640px) {
  .sf-product__main { padding-inline: 1.5rem; padding-block: 2rem; } /* sm:px-6 sm:py-8 */
}

.sf-product__layout {
  display: grid;
  grid-template-columns: repeat(1, minmax(0, 1fr));
  gap: 1rem; /* gap-4 */
  margin-bottom: 2.5rem; /* mb-10 */
}
@media (min-width: 640px) {
  .sf-product__layout { gap: 2rem; margin-bottom: 4rem; } /* sm:gap-8 sm:mb-16 */
}
@media (min-width: 1024px) {
  .sf-product__layout { grid-template-columns: repeat(2, minmax(0, 1fr)); } /* lg:grid-cols-2 */
}

/* ── product_v2.html: image gallery (Task 0.12) ── */
.sf-product__gallery { display: flex; flex-direction: column; }
.sf-product__gallery > * + * { margin-top: 1rem; } /* space-y-4 */

.sf-product__gallery-frame {
  position: relative;
  overflow: hidden;
  border-radius: 1rem; /* rounded-2xl */
  border: 1px solid #e2e8f0; /* border-slate-200 */
  aspect-ratio: 1 / 1; /* aspect-square */
}

.sf-product__gallery-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity 300ms ease; /* transition-opacity duration-300 */
}

.sf-product__gallery-placeholder {
  display: flex;
  height: 100%;
  width: 100%;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  background-color: #f1f5f9; /* bg-slate-100 — dark:bg-slate-900 was dead, see nav note */
  color: #94a3b8; /* text-slate-400 — dark:text-slate-500 was dead, see nav note */
}
.sf-product__gallery-placeholder-icon { width: 5rem; height: 5rem; } /* h-20 w-20 */
.sf-product__gallery-placeholder-text { font-size: 0.875rem; line-height: 1.25rem; font-weight: 600; } /* text-sm font-semibold */

.sf-product__gallery-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 2rem; /* w-8 */
  height: 2rem; /* h-8 */
  border-radius: 9999px;
  background-color: rgba(255, 255, 255, 0.8); /* bg-white/80 */
  backdrop-filter: blur(4px); /* backdrop-blur-sm */
  -webkit-backdrop-filter: blur(4px);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #334155; /* text-slate-700 */
  transition: background-color 150ms ease;
  box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); /* shadow-sm */
}
.sf-product__gallery-nav:hover { background-color: #ffffff; }
.sf-product__gallery-nav--prev { left: 0.5rem; } /* left-2 */
.sf-product__gallery-nav--next { right: 0.5rem; } /* right-2 */
.sf-product__gallery-nav-icon { width: 1rem; height: 1rem; } /* w-4 h-4 */

/* Physical (not logical) left/right below: the pre-conversion markup used
   Tailwind's physical `right-3`/`top-3` utilities and forces dir="ltr" on
   the counter regardless of page direction — an inset-inline-end here would
   flip under rtl and change behavior this conversion must not touch. */
.sf-product__gallery-counter {
  position: absolute;
  bottom: 0.75rem;
  right: 0.75rem;
  padding-inline: 0.625rem; /* px-2.5 */
  padding-block: 0.25rem; /* py-1 */
  border-radius: 9999px;
  background-color: rgba(0, 0, 0, 0.5); /* bg-black/50 */
  color: #ffffff;
  font-size: 0.75rem;
  line-height: 1rem;
  font-weight: 500;
}

.sf-product__gallery-autoplay { position: absolute; top: 0.75rem; right: 0.75rem; }
.sf-product__gallery-autoplay-dot {
  width: 0.5rem; /* w-2 */
  height: 0.5rem; /* h-2 */
  border-radius: 9999px;
  animation: sf-product-pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; /* animate-pulse */
}
@keyframes sf-product-pulse { 50% { opacity: 0.5; } }

.sf-product__thumbs {
  display: flex;
  gap: 0.5rem; /* gap-2 */
  overflow-x: auto;
  padding-bottom: 0.5rem; /* pb-2 */
}

.sf-product__thumb {
  width: 3.5rem; /* w-14 */
  height: 3.5rem; /* h-14 */
  flex-shrink: 0;
  border-radius: 0.5rem; /* rounded-lg */
  border-width: 2px;
  border-style: solid;
  overflow: hidden;
  transition: all 150ms ease; /* transition-all */
}
.sf-product__thumb-img { width: 100%; height: 100%; object-fit: cover; }

/* ── product_v2.html: buy box (Task 0.12) — contract, LOCKED, single instance ── */
.sf-buybox {
  border-radius: 1rem; /* rounded-2xl */
  border: 1px solid #e2e8f0; /* border-slate-200 */
  padding: 0.75rem; /* p-3 */
  background-color: rgba(255, 255, 255, 0.5); /* bg-white/50 */
}
@media (min-width: 640px) {
  .sf-buybox { padding: 1.25rem; } /* sm:p-5 */
}
.sf-buybox > * + * { margin-top: 0.75rem; } /* space-y-3 */
@media (min-width: 640px) {
  .sf-buybox > * + * { margin-top: 1.25rem; } /* sm:space-y-5 */
}

.sf-buybox__title { font-size: 1.25rem; line-height: 1.75rem; font-weight: 700; color: #0f172a; margin-bottom: 0.5rem; } /* text-xl font-bold text-slate-900 mb-2 */
@media (min-width: 640px) {
  .sf-buybox__title { font-size: 1.5rem; line-height: 2rem; } /* sm:text-2xl */
}
.sf-buybox__desc { color: #64748b; margin-bottom: 0.75rem; } /* text-slate-500 mb-3 */

.sf-buybox__price-row { display: flex; align-items: flex-end; gap: 0.75rem; } /* flex items-end gap-3 */
.sf-buybox__price { font-size: 1.5rem; line-height: 2rem; font-weight: 900; } /* text-2xl font-black */
@media (min-width: 640px) {
  .sf-buybox__price { font-size: 1.875rem; line-height: 2.25rem; } /* sm:text-3xl */
}
.sf-buybox__compare { font-size: 0.875rem; line-height: 1.25rem; color: #94a3b8; text-decoration: line-through; font-weight: 500; } /* text-sm text-slate-400 line-through font-medium */
@media (min-width: 640px) {
  .sf-buybox__compare { font-size: 1rem; line-height: 1.5rem; } /* sm:text-base */
}
.sf-buybox__discount {
  padding-inline: 0.5rem; /* px-2 */
  padding-block: 0.125rem; /* py-0.5 */
  background-color: #fee2e2; /* bg-red-100 */
  color: #dc2626; /* text-red-600 */
  font-size: 10px;
  font-weight: 700;
  border-radius: 9999px;
}
@media (min-width: 640px) {
  .sf-buybox__discount { font-size: 0.75rem; line-height: 1rem; } /* sm:text-xs */
}

/* gh-1389 Task 4.1: the buy-box SECTION's own empty state — a store with no
   active product yet (the builder preview's "most-recently-created active
   product" rule finds none). Not part of _base/contract.css's cross-theme
   promise (it is this ONE section type's own fallback, not shared L1
   markup), but styled here so it never renders bare/unstyled text. */
.sf-buybox-empty {
  border-radius: 1rem;
  border: 1px dashed #cbd5e1; /* border-slate-300 */
  padding: 1.5rem;
  text-align: center;
  color: #64748b; /* text-slate-500 */
}

/* gh-1389 Task 4.2: the product-gallery/product-description/similar-products
   SECTIONS' own empty states — no active subject product yet, or a subject
   with nothing of that particular kind to show (no description text, no
   OTHER active products). One shared class rather than three copies of
   .sf-buybox-empty's own rule (same visual language, deliberately). */
.sf-catalog-section-empty {
  border-radius: 1rem;
  border: 1px dashed #cbd5e1; /* border-slate-300 */
  padding: 1.5rem;
  text-align: center;
  color: #64748b; /* text-slate-500 */
}

/* gh-1389 round 20 Task H: builder-preview-only hint for a block section
   (faq/testimonials/order-steps/features/gallery/slideshow) whose items
   are all skip-empty (round-19's live-skip guard). RenderInput.IsPreview
   gates it server-side — this rule only ever gets a chance to paint inside
   the builder iframe, never on a live storefront page. */
.sf-preview-empty-hint {
  border-radius: 1rem;
  border: 1px dashed #cbd5e1; /* border-slate-300 */
  padding: 2rem;
  text-align: center;
  color: #64748b; /* text-slate-500 */
}

/* gh-1389 Task 4.2: the product-description SECTION's own optional
   "heading" setting (product-description/schema.json) — the legacy inline
   description block never rendered a heading, so this class has no
   pre-existing analogue; sized to match .sf-product__similar-title's own
   weight for visual consistency within the same page. */
.sf-product__description-heading {
  font-size: 1.5rem; line-height: 2rem; font-weight: 700; margin-bottom: 1rem; color: #0f172a; /* text-2xl font-bold mb-4 text-slate-900 */
}

/* ── product_v2.html: trust badges (Task 0.12) ── */
.sf-product__trust {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr)); /* grid-cols-2 */
  grid-auto-rows: 1fr; /* auto-rows-fr */
  align-items: stretch;
  gap: 1rem;
  padding-block: 0.5rem;
  border-top: 1px solid #e2e8f0; /* border-slate-200 */
}
@media (min-width: 640px) {
  .sf-product__trust { grid-template-columns: repeat(4, minmax(0, 1fr)); } /* sm:grid-cols-4 */
}

.sf-product__trust-item {
  display: flex;
  height: 100%;
  min-height: 9rem; /* min-h-36 */
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  text-align: center;
  padding: 1rem;
  border-radius: 0.75rem; /* rounded-xl */
  border: 1px solid #f1f5f9; /* border-slate-100 */
  box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); /* shadow-sm */
  background-color: #ffffff;
}

.sf-product__trust-icon-wrap {
  margin-bottom: 0.75rem;
  display: flex;
  height: 3rem; /* h-12 */
  width: 3rem; /* w-12 */
  align-items: center;
  justify-content: center;
  border-radius: 9999px;
  box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); /* shadow-sm */
  background-color: #ffffff;
}
.sf-product__trust-icon { width: 1.5rem; height: 1.5rem; } /* w-6 h-6 */

/* text-sm sets its own line-height (1.25rem), but leading-tight (1.25,
   unitless) is also on this element and sits AFTER .text-sm in output.css,
   so it wins the cascade at equal specificity — confirmed against the
   compiled stylesheet (same trap as .sf-product__trust-desc above). Using
   the flat 1.25rem here shifted this text by ~2.5px and cascaded a 1px
   layout diff through everything below it in the pixel harness. */
.sf-product__trust-title {
  width: 100%;
  min-height: 2.5rem; /* min-h-10 */
  font-weight: 700;
  font-size: 0.875rem; /* text-sm */
  line-height: 1.25; /* leading-tight wins the cascade over text-sm's 1.25rem */
  color: #0f172a; /* text-slate-900 */
  margin-bottom: 0.25rem;
}
/* text-[10px] carries no line-height of its own; leading-tight (1.25, a
   unitless ratio) applies unopposed at this width. At sm:text-xs (>=640px)
   leading-tight loses the line-height cascade — output.css puts .sm\:text-xs
   after .leading-tight, so line-height becomes the flat 1rem text-xs sets.
   Confirmed against the compiled stylesheet rather than assumed. */
.sf-product__trust-desc {
  width: 100%;
  font-size: 10px; /* text-[10px] */
  line-height: 1.25; /* leading-tight */
  color: #64748b; /* text-slate-500 */
}
@media (min-width: 640px) {
  .sf-product__trust-desc { font-size: 0.75rem; line-height: 1rem; } /* sm:text-xs wins the line-height cascade */
}

/* ── partials/variants.html: variant picker key groups (Task 0.12) ──────────
   See variants.html's comment on this class: reproduces the 12px spacing a
   pre-conversion `<template x-for>` sibling accidentally gave every key
   group (including the first) via space-y-3's `:not([hidden]) ~
   :not([hidden])` selector. Unconditional on purpose — matches the old
   behavior exactly rather than "fixing" a first-child exception nothing
   relied on visually. */
.sf-variant-group { margin-top: 0.75rem; }

/* Task 0.13: the label/select/chevron/swatch-dot leftovers only — the
   dynamic :class array on .sf-variant is untouched, see variants.html's
   comment at this call site. */
.sf-variant-group__label { display: block; font-size: 0.875rem; line-height: 1.25rem; font-weight: 700; margin-bottom: 0.5rem; color: #334155; } /* block text-sm font-bold mb-2 text-slate-700 */
.sf-variant-select-wrap { position: relative; } /* relative */
.sf-variant-select {
  width: 100%;
  min-height: 2.75rem; /* min-h-11 */
  appearance: none;
  border-radius: 0.5rem; /* rounded-lg */
  border: 1px solid #e2e8f0; /* border-slate-200 */
  background-color: #ffffff;
  padding-inline: 0.75rem; /* px-3 */
  padding-block: 0.625rem; /* py-2.5 */
  padding-inline-end: 2.5rem; /* pl-10/pr-10 per dir — the chevron sits at the END, opposite text-align:start */
  text-align: start; /* text-right/text-left per dir */
  font-size: 0.875rem; /* text-sm */
  line-height: 1.25rem;
  font-weight: 700;
  color: #334155; /* text-slate-700 */
  outline: none;
  transition: border-color 150ms ease, box-shadow 150ms ease;
}
.sf-variant-select:focus { border-color: var(--brand); box-shadow: 0 0 0 2px var(--brand); } /* focus:border-brand-500 focus:ring-2 focus:ring-brand-500 */
.sf-variant-select-chevron { position: absolute; inset-inline-end: 0.75rem; top: 50%; transform: translateY(-50%); color: #94a3b8; pointer-events: none; } /* absolute (right-3/left-3 per dir → inset-inline-end) top-1/2 -translate-y-1/2 text-slate-400 pointer-events-none */
.sf-variant-select-chevron-icon { width: 1rem; height: 1rem; } /* h-4 w-4 */
.sf-variant-buttons-row { display: flex; flex-wrap: wrap; gap: 0.5rem; } /* flex flex-wrap gap-2 */
.sf-variant-swatch-dot { width: 1.25rem; height: 1.25rem; border-radius: 9999px; border: 1px solid rgb(0 0 0 / 0.1); flex-shrink: 0; } /* w-5 h-5 rounded-full border border-black/10 shrink-0 */

/* ── partials/order_form.html: COD order form (Task 0.12) ────────────────────
   Only the contract-required sections converted (.sf-of wrapper, .sf-of__qty,
   .sf-of__summary) plus the delivery-fee-unknown modifier. Everything else in
   this partial — the sold-out badge, upsell cards, submit/add-to-cart buttons
   — stays on its pre-conversion Tailwind classes: none of it is part of the
   sf-of__* contract, and its Alpine :class bindings are dense enough (colors
   keyed off FormTheme/MainColor at runtime) that converting it carries far
   more regression risk than the "highest-risk conversion" budget for this
   task affords. checkout_fields.html/custom_fields.html similarly got
   ADDITIVE sf-of__field/sf-of__dtype markers only — see those files' own
   comments — not a full class rewrite. */
.sf-of {
  background-color: #ffffff; /* bg-white */
  padding: 0.75rem; /* p-3 */
  /* border-color/width/radius are NO LONGER on the inline style attribute
     (gh-1389 L1): FormTheme is still per-store/per-LP runtime configuration,
     but it now arrives as --sf-legacy-form-* on :root and is consumed in the
     legacy-appearance block at the end of this file. */
}

.sf-of__qty { display: flex; align-items: center; justify-content: space-between; }
.sf-of__qty-label { font-weight: 700; font-size: 0.875rem; line-height: 1.25rem; color: #334155; } /* font-bold text-sm text-slate-700 */
.sf-of__qty-control { display: flex; align-items: center; gap: 0.75rem; }
.sf-of__qty-btn {
  width: 2rem; /* w-8 */
  height: 2rem; /* h-8 */
  border-radius: 0.5rem; /* rounded-lg */
  border: 1px solid #e2e8f0; /* border-slate-200 */
  transition: background-color 150ms ease; /* transition */
  display: flex;
  align-items: center;
  justify-content: center;
  color: #475569; /* text-slate-600 */
  font-size: 0.875rem; /* text-sm */
  line-height: 1.25rem; /* text-sm bundles this — omitting it left the glyph inheriting
                            html's line-height:1.5 (21px) instead of 20px, shifting the
                            +/- glyph's flex-centered vertical position by a sub-pixel
                            amount; caught by the pixel harness, not by any Go/JS test. */
  font-weight: 700;
}
.sf-of__qty-btn:hover { background-color: #f1f5f9; } /* hover:bg-slate-100 */
.sf-of__qty-value { font-size: 0.875rem; line-height: 1.25rem; font-weight: 700; width: 1.5rem; text-align: center; } /* text-sm font-bold w-6 text-center */

.sf-of__summary {
  border-radius: 0.75rem; /* rounded-xl */
  border: 1px solid #e2e8f0; /* border-slate-200 */
  padding: 0.75rem; /* p-3 */
  background-color: #f8fafc; /* bg-slate-50 */
}
.sf-of__summary > * + * { margin-top: 0.375rem; } /* space-y-1.5 — real persistent DOM rows, no phantom-sibling risk (contrast variants.html's fix above) */
.sf-of__summary--pending {} /* contract modifier — classic gives it no styling; see order_form.html's comment */

/* ── product_v2.html: similar products strip (Task 0.12) ─────────────────── */
.sf-product__similar { margin-top: 3rem; padding-top: 3rem; border-top: 1px solid #e2e8f0; } /* mt-12 pt-12 border-slate-200 */
.sf-product__similar-title { font-size: 1.5rem; line-height: 2rem; font-weight: 700; margin-bottom: 1.5rem; color: #0f172a; } /* text-2xl font-bold mb-6 text-slate-900 */
.sf-product__similar-scroller {
  display: flex;
  overflow-x: auto;
  gap: 1rem; /* gap-4 */
  scroll-snap-type: x mandatory; /* snap-x snap-mandatory */
  padding-bottom: 0.5rem; /* pb-2 */
}
.sf-product__similar-card {
  display: block;
  scroll-snap-align: center; /* snap-center */
  flex-shrink: 0;
  width: 200px; /* w-[200px] */
}
@media (min-width: 640px) {
  .sf-product__similar-card { width: 16rem; } /* sm:w-64 */
}

/* gh-1389 Phase 4 fix wave I4: similar-products' own "columns" setting —
   read it as "how many cards are visible before the strip scrolls".
   Scoped to .sf-section-similar-products (section_similar_products.html's
   own doc comment) so product_v2.html's legacy render — same class, same
   markup, no such ancestor — keeps its fixed 200px/16rem card width
   byte-for-byte; only the COMPOSED section route ever carries --sf-cols.
   1rem (16px) matches .sf-product__similar-scroller's own gap above. */
.sf-section-similar-products .sf-product__similar-card {
  width: calc((100% - (var(--sf-cols, 4) - 1) * 1rem) / var(--sf-cols, 4));
}
@container (max-width: 760px) {
  .sf-section-similar-products .sf-product__similar-card {
    width: calc((100% - (var(--sf-cols-mobile, 2) - 1) * 1rem) / var(--sf-cols-mobile, 2));
  }
}

.sf-product__similar-frame {
  border-radius: 0.75rem; /* rounded-xl */
  overflow: hidden;
  border: 1px solid #e2e8f0; /* border-slate-200 */
  transition: border-color 150ms ease, box-shadow 150ms ease; /* transition-all, scoped to what brand-hover-border (base.html) actually animates */
}
.sf-product__similar-thumb {
  height: 8rem; /* h-32 */
  background-color: #f3f4f6; /* bg-gray-100 */
  position: relative;
  overflow: hidden;
}
@media (min-width: 768px) {
  .sf-product__similar-thumb { height: 10rem; } /* md:h-40 */
}
.sf-product__similar-img { width: 100%; height: 100%; object-fit: cover; transition: transform 150ms ease; } /* transition-transform */
.sf-product__similar-card:hover .sf-product__similar-img { transform: scale(1.1); } /* group-hover:scale-110 */
.sf-product__similar-placeholder { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 1.5rem; line-height: 2rem; }
.sf-product__similar-badge {
  position: absolute;
  top: 0.5rem;
  left: 0.5rem;
  background-color: #ef4444; /* bg-red-500 */
  color: #ffffff;
  padding-inline: 0.5rem; /* px-2 */
  padding-block: 0.25rem; /* py-1 */
  border-radius: 0.5rem; /* rounded-lg */
  font-size: 0.75rem;
  line-height: 1rem;
  font-weight: 700;
}
.sf-product__similar-body { padding: 0.75rem; } /* p-3 */
.sf-product__similar-name {
  font-weight: 700;
  font-size: 0.875rem;
  line-height: 1.25rem; /* text-sm */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap; /* truncate */
  color: #0f172a; /* text-slate-900 — dark:text-white was dead, see nav note */
}
.sf-product__similar-price-row { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-top: 0.25rem; } /* mt-1 */
.sf-product__similar-price { font-weight: 700; font-size: 0.875rem; line-height: 1.25rem; } /* font-bold text-sm */
.sf-product__similar-compare { font-size: 0.75rem; line-height: 1rem; color: #94a3b8; text-decoration: line-through; } /* text-xs text-slate-400 line-through */
.sf-product__similar-cta {
  width: 100%;
  margin-top: 0.5rem; /* mt-2 */
  padding-block: 0.375rem; /* py-1.5 */
  border-radius: 0.5rem; /* rounded-lg */
  color: #ffffff;
  font-weight: 600;
  font-size: 0.75rem;
  line-height: 1rem;
  transition: box-shadow 150ms ease; /* transition-all */
}

/* ── cart_page.html (Task 0.13) ─────────────────────────────────────────── */
.sf-cart { max-width: 48rem; margin-inline: auto; padding-inline: 1rem; padding-block: 2rem; } /* max-w-3xl mx-auto px-4 py-8 */
.sf-cart__back { display: inline-flex; align-items: center; gap: 0.5rem; font-size: 0.875rem; line-height: 1.25rem; color: #64748b; margin-bottom: 1.5rem; } /* text-sm text-slate-500 mb-6 */
.sf-cart__back:hover { color: #334155; } /* hover:text-slate-700 */
.sf-cart__back-icon { width: 1rem; height: 1rem; } /* w-4 h-4 */
[dir="rtl"] .sf-cart__back-icon { transform: rotate(180deg); } /* rtl:rotate-180 */
.sf-cart__title { font-size: 1.5rem; line-height: 2rem; font-weight: 700; margin-bottom: 1.5rem; } /* text-2xl font-bold mb-6 */

.sf-cart__success { text-align: center; padding-block: 4rem; border-radius: 1rem; border: 1px solid #a7f3d0; background-color: #ecfdf5; } /* py-16 rounded-2xl border-emerald-200 bg-emerald-50 */
.sf-cart__success--dark { border-color: rgba(6, 78, 59, 0.5); background-color: rgba(6, 78, 59, 0.2); } /* border-emerald-900/50 bg-emerald-900/20 */
.sf-cart__success-icon { width: 3rem; height: 3rem; margin-inline: auto; margin-bottom: 1rem; color: #10b981; } /* w-12 h-12 mx-auto mb-4 text-emerald-500 */
.sf-cart__success-title { font-weight: 700; font-size: 1.125rem; line-height: 1.75rem; margin-bottom: 0.5rem; } /* font-bold text-lg mb-2 */
.sf-cart__success-hint { font-size: 0.875rem; line-height: 1.25rem; color: #64748b; margin-bottom: 1.5rem; } /* text-sm text-slate-500 mb-6 */
.sf-cart__success-actions { display: flex; flex-direction: column; align-items: center; gap: 0.75rem; } /* flex flex-col items-center gap-3 */

.sf-cart__track-btn { display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem; padding-inline: 1.5rem; padding-block: 0.75rem; border-radius: 0.75rem; font-weight: 700; color: #ffffff; } /* inline-flex items-center justify-center gap-2 px-6 py-3 rounded-xl font-bold text-white */
.sf-cart__track-icon { width: 1rem; height: 1rem; } /* w-4 h-4 */
.sf-cart__track-hint { font-size: 0.75rem; line-height: 1rem; color: #64748b; } /* text-xs text-slate-500 */

.sf-cart__continue { display: inline-block; padding-inline: 1.5rem; padding-block: 0.75rem; border-radius: 0.75rem; font-weight: 700; } /* inline-block px-6 py-3 rounded-xl font-bold */
.sf-cart__continue--filled { color: #ffffff; } /* text-white — background comes from the store's inline brand-color style */
.sf-cart__continue--ghost { color: #334155; border: 1px solid #cbd5e1; } /* text-slate-700 border border-slate-300 */
.sf-cart__continue--ghost-dark { color: #e2e8f0; border-color: #334155; } /* text-slate-200 border-slate-700 */

.sf-cart__empty { text-align: center; padding-block: 4rem; border-radius: 1rem; border: 1px solid #e2e8f0; background-color: #ffffff; } /* py-16 rounded-2xl border-slate-200 bg-white */
.sf-cart__empty--dark { border-color: #1e293b; background-color: rgba(15, 23, 42, 0.5); } /* border-slate-800 bg-slate-900/50 */
.sf-cart__empty-text { color: #64748b; margin-bottom: 1rem; } /* text-slate-500 mb-4 */

.sf-cart__lines { border-radius: 1rem; border: 1px solid #e2e8f0; overflow: hidden; margin-bottom: 1.5rem; background-color: #ffffff; } /* rounded-2xl border overflow-hidden mb-6 border-slate-200 bg-white */
.sf-cart__lines--dark { border-color: #1e293b; background-color: rgba(15, 23, 42, 0.5); } /* border-slate-800 bg-slate-900/50 */

.sf-cart__line { display: flex; gap: 0.75rem; padding: 1rem; border-bottom: 1px solid #f1f5f9; } /* flex gap-3 p-4 border-b border-slate-100 */
.sf-cart__line:last-child { border-bottom: none; } /* last:border-b-0 */
.sf-cart__line--dark { border-bottom-color: #1e293b; } /* border-slate-800 */
.sf-cart__line-img { width: 4rem; height: 4rem; border-radius: 0.5rem; overflow: hidden; flex-shrink: 0; background-color: #f1f5f9; } /* w-16 h-16 rounded-lg overflow-hidden flex-shrink-0 bg-slate-100 — dark:bg-slate-800 was dead */
.sf-cart__line-img-el { width: 100%; height: 100%; object-fit: cover; } /* w-full h-full object-cover */
.sf-cart__line-body { flex: 1 1 0%; min-width: 0; } /* flex-1 min-w-0 */
.sf-cart__line-name { font-weight: 600; font-size: 0.875rem; line-height: 1.25rem; } /* font-semibold text-sm */
.sf-cart__line-variant { font-size: 0.75rem; line-height: 1rem; color: #64748b; } /* text-xs text-slate-500 */
.sf-cart__line-controls { display: flex; align-items: center; gap: 0.5rem; margin-top: 0.5rem; } /* flex items-center gap-2 mt-2 */
.sf-cart__qty-btn { width: 1.75rem; height: 1.75rem; border-radius: 0.25rem; border: 1px solid currentColor; display: flex; align-items: center; justify-content: center; } /* w-7 h-7 rounded border (bare, no color utility — Tailwind v3 default border-color is currentColor) flex items-center justify-center */
.sf-cart__qty-value { font-size: 0.875rem; line-height: 1.25rem; width: 1.5rem; text-align: center; } /* text-sm w-6 text-center */
.sf-cart__remove { margin-inline-start: auto; font-size: 0.75rem; line-height: 1rem; color: #ef4444; } /* ms-auto text-xs text-red-500 */
.sf-cart__remove:hover { text-decoration: underline; } /* hover:underline */
.sf-cart__line-price { font-size: 0.875rem; line-height: 1.25rem; font-weight: 700; white-space: nowrap; } /* text-sm font-bold whitespace-nowrap */

.sf-cart__unavailable { margin-bottom: 1.5rem; padding: 1rem; border-radius: 0.75rem; border: 1px solid #fcd34d; background-color: #fffbeb; color: #78350f; font-size: 0.875rem; line-height: 1.25rem; } /* mb-6 p-4 rounded-xl border-amber-300 bg-amber-50 text-amber-900 text-sm */
.sf-cart__unavailable-title { font-weight: 600; margin-bottom: 0.25rem; } /* font-semibold mb-1 */

.sf-cart__form { display: flex; flex-direction: column; gap: 0.75rem; border-radius: 1rem; border: 1px solid #e2e8f0; padding: 1rem; background-color: #ffffff; } /* space-y-3 rounded-2xl border p-4 border-slate-200 bg-white — no phantom <template> siblings in this form, gap is a safe 1:1 swap for space-y-3 */
.sf-cart__form--dark { border-color: #1e293b; background-color: rgba(15, 23, 42, 0.5); } /* border-slate-800 bg-slate-900/50 */

.sf-cart__totals { padding-top: 0.75rem; border-top: 1px solid #f1f5f9; font-size: 0.875rem; line-height: 1.25rem; display: flex; flex-direction: column; gap: 0.25rem; } /* pt-3 border-t border-slate-100 space-y-1 text-sm */
.sf-cart__totals--dark { border-top-color: #1e293b; } /* border-slate-800 */
.sf-cart__totals-row { display: flex; justify-content: space-between; } /* flex justify-between */
.sf-cart__totals-row--total { font-weight: 700; font-size: 1rem; line-height: 1.5rem; padding-top: 0.25rem; } /* font-bold text-base pt-1 */
.sf-cart__delivery-pending { color: #94a3b8; } /* text-slate-400 */

.sf-cart__error { font-size: 0.875rem; line-height: 1.25rem; color: #dc2626; } /* text-sm text-red-600 */

.sf-cart__submit { width: 100%; padding-block: 0.75rem; border-radius: 0.75rem; font-weight: 700; color: #ffffff; } /* w-full py-3 rounded-xl font-bold text-white */
.sf-cart__submit:disabled { opacity: 0.6; cursor: not-allowed; } /* disabled:opacity-60 disabled:cursor-not-allowed */

/* ── partials/cart.html: navbar toggle + slide-over drawer (Task 0.13) ────
   Renders on every page except /cart itself (base.html: {{ if not .OnCartPage }}).
   x-transition:enter/leave attribute VALUES on the <aside> below are left on
   raw Tailwind transform utilities (translate-x-full, rtl:-translate-x-full,
   transition/ease/duration) — those are Alpine directive values, not a
   class="..." attribute, so the zero-Tailwind grep never sees them, but they
   still resolve through compiled output.css. Flagged in task-0.13-report.md
   as a residual dependency for a future task. */
.sf-cart-root { display: contents; } /* contents */
.sf-cart-toggle {
  position: fixed; /* fixed */
  inset-block-start: 1rem; /* top-4 */
  inset-inline-end: 1rem; /* end-4 */
  z-index: 60; /* z-[60] */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3rem; /* w-12 */
  height: 3rem; /* h-12 */
  border-radius: 9999px; /* rounded-full */
  box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); /* shadow-lg */
  border: 1px solid #e2e8f0; /* border-slate-200 */
  background-color: #ffffff; /* bg-white */
  color: #0f172a; /* text-slate-900 */
  transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease; /* transition-colors */
}
.sf-cart-toggle--dark { background-color: #1e293b; border-color: #334155; color: #ffffff; } /* bg-slate-800 border-slate-700 text-white */
.sf-cart-toggle__icon { width: 1.5rem; height: 1.5rem; } /* w-6 h-6 */
.sf-cart-badge {
  position: absolute;
  inset-block-start: -0.25rem; /* -top-1 */
  inset-inline-end: -0.25rem; /* -end-1 */
  min-width: 1.25rem; /* min-w-[1.25rem] */
  height: 1.25rem; /* h-5 */
  padding-inline: 0.25rem; /* px-1 */
  border-radius: 9999px; /* rounded-full */
  background-color: #ef4444; /* bg-red-500 */
  color: #ffffff;
  font-size: 0.75rem; /* text-xs */
  line-height: 1rem;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}
.sf-cart-overlay { position: fixed; inset: 0; z-index: 70; } /* fixed inset-0 z-[70] */
.sf-cart-backdrop { position: absolute; inset: 0; background-color: rgb(0 0 0 / 0.4); } /* absolute inset-0 bg-black/40 */
.sf-cart-drawer {
  position: absolute;
  inset-block-start: 0; /* top-0 */
  inset-inline-end: 0; /* end-0 */
  height: 100%; /* h-full */
  width: 100%; /* w-full */
  max-width: 28rem; /* max-w-md */
  display: flex;
  flex-direction: column;
  box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25); /* shadow-2xl */
  background-color: #ffffff; /* bg-white */
  color: #0f172a; /* text-slate-900 */
}
.sf-cart-drawer--dark { background-color: #0f172a; color: #ffffff; } /* bg-slate-900 text-white */
.sf-cart-drawer__header { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; padding-inline: 1rem; padding-block: 1rem; border-bottom: 1px solid #e2e8f0; } /* flex items-center justify-between gap-3 px-4 py-4 border-b border-slate-200 */
.sf-cart-drawer__header--dark { border-bottom-color: #1e293b; } /* border-slate-800 */
.sf-cart-drawer__title { font-weight: 700; font-size: 1.125rem; line-height: 1.75rem; } /* font-bold text-lg */
.sf-cart-drawer__close { padding: 0.5rem; border-radius: 0.5rem; } /* p-2 rounded-lg */
.sf-cart-drawer__close:hover { background-color: rgb(0 0 0 / 0.05); } /* hover:bg-black/5 — dark:hover:bg-white/10 was dead */
.sf-cart-drawer__close-icon { width: 1.25rem; height: 1.25rem; } /* w-5 h-5 */
.sf-cart-drawer__body { flex: 1 1 0%; overflow-y: auto; padding-inline: 1rem; padding-block: 0.75rem; } /* flex-1 overflow-y-auto px-4 py-3 */
.sf-cart-drawer__empty { text-align: center; font-size: 0.875rem; line-height: 1.25rem; color: #64748b; padding-block: 2.5rem; } /* text-center text-sm text-slate-500 py-10 */
.sf-cart-drawer__line { display: flex; gap: 0.75rem; padding-block: 0.75rem; border-bottom: 1px solid #f1f5f9; } /* flex gap-3 py-3 border-b border-slate-100 */
.sf-cart-drawer__line--dark { border-bottom-color: #1e293b; } /* border-slate-800 */
.sf-cart-drawer__line-img { width: 4rem; height: 4rem; border-radius: 0.5rem; overflow: hidden; flex-shrink: 0; background-color: #f1f5f9; } /* w-16 h-16 rounded-lg overflow-hidden flex-shrink-0 bg-slate-100 — dark:bg-slate-800 was dead */
.sf-cart-drawer__line-img-el { width: 100%; height: 100%; object-fit: cover; } /* w-full h-full object-cover */
.sf-cart-drawer__line-body { flex: 1 1 0%; min-width: 0; } /* flex-1 min-w-0 */
.sf-cart-drawer__line-name { font-weight: 600; font-size: 0.875rem; line-height: 1.25rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } /* font-semibold text-sm truncate */
.sf-cart-drawer__line-variant { font-size: 0.75rem; line-height: 1rem; color: #64748b; } /* text-xs text-slate-500 */
.sf-cart-drawer__line-price { font-size: 0.875rem; line-height: 1.25rem; font-weight: 700; margin-top: 0.25rem; } /* text-sm font-bold mt-1 */
.sf-cart-drawer__line-controls { display: flex; align-items: center; gap: 0.5rem; margin-top: 0.5rem; } /* flex items-center gap-2 mt-2 */
.sf-cart-drawer__qty-btn { width: 1.75rem; height: 1.75rem; border-radius: 0.25rem; border: 1px solid #cbd5e1; display: flex; align-items: center; justify-content: center; } /* w-7 h-7 rounded border-slate-300 flex items-center justify-center */
.sf-cart-drawer__qty-btn--dark { border-color: #334155; } /* border-slate-700 */
.sf-cart-drawer__qty-value { font-size: 0.875rem; line-height: 1.25rem; font-weight: 500; width: 1.5rem; text-align: center; } /* text-sm font-medium w-6 text-center */
.sf-cart-drawer__remove { margin-inline-start: auto; font-size: 0.75rem; line-height: 1rem; color: #ef4444; } /* ms-auto text-xs text-red-500 */
.sf-cart-drawer__remove:hover { text-decoration: underline; } /* hover:underline */
.sf-cart-drawer__footer { padding-inline: 1rem; padding-block: 1rem; border-top: 1px solid #e2e8f0; } /* px-4 py-4 border-t border-slate-200 */
.sf-cart-drawer__footer--dark { border-top-color: #1e293b; } /* border-slate-800 */
.sf-cart-drawer__subtotal-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.75rem; } /* flex items-center justify-between mb-3 */
.sf-cart-drawer__subtotal-label { font-size: 0.875rem; line-height: 1.25rem; color: #64748b; } /* text-sm text-slate-500 */
.sf-cart-drawer__subtotal-value { font-weight: 700; } /* font-bold */
.sf-cart-drawer__checkout { display: block; width: 100%; text-align: center; padding-block: 0.75rem; border-radius: 0.75rem; font-weight: 700; color: #ffffff; } /* block w-full text-center py-3 rounded-xl font-bold text-white */

/* ── order_tracking.html (Task 0.13) ────────────────────────────────────────
   No pixel harness coverage exists for /track (0.8's spec never included
   it). Verified instead by: go test ./internal/storefront/... (order_tracking
   Go suite pins the rendered bytes it cares about), a structural diff
   (identical tag multiset + data-testid/data-step/data-state sequence
   pre/post), and a manual curl of the fixture tracking page in both ar (rtl)
   and fr (ltr) — see task-0.13-report.md.
   Two Go conditionals collapsed into a single logical-property rule instead
   of a per-dir ternary — {{if eq .Dir "rtl"}}text-left{{else}}text-right{{end}}
   and the connector's left-3/right-3 swap — since text-align:end and
   inset-inline-start already resolve identically per `dir` with no branch. */
.sf-track { min-height: 100vh; transition: background-color 150ms ease; background-color: #f8fafc; } /* min-h-screen transition-colors bg-slate-50 */
.sf-track--dark { background-color: #020617; } /* bg-slate-950 */

.sf-track__header { border-bottom: 1px solid #e2e8f0; transition: background-color 150ms ease, border-color 150ms ease; background-color: #ffffff; } /* border-b transition-colors bg-white border-slate-200 */
.sf-track__header--dark { background-color: #0f172a; border-bottom-color: #1e293b; } /* bg-slate-900 border-slate-800 */
.sf-track__header-inner { max-width: 42rem; margin-inline: auto; padding-inline: 1rem; padding-block: 1rem; } /* max-w-2xl mx-auto px-4 py-4 */
.sf-track__logo { display: flex; align-items: center; gap: 0.5rem; } /* flex items-center gap-2 */
.sf-track__logo-img { height: 2.5rem; width: auto; object-fit: contain; } /* h-10 w-auto object-contain */
.sf-track__logo-fallback { width: 2.5rem; height: 2.5rem; border-radius: 0.5rem; display: flex; align-items: center; justify-content: center; color: #ffffff; font-weight: 700; } /* w-10 h-10 rounded-lg flex items-center justify-center text-white font-bold */
.sf-track__logo-name { font-size: 1.125rem; line-height: 1.75rem; font-weight: 700; color: #0f172a; } /* text-lg font-bold text-slate-900 */
.sf-track__logo-name--dark { color: #ffffff; } /* text-white */

.sf-track__main { max-width: 42rem; margin-inline: auto; padding-inline: 1rem; padding-block: 1.5rem; } /* max-w-2xl mx-auto px-4 py-6 */
@media (min-width: 640px) { .sf-track__main { padding-block: 2.5rem; } } /* sm:py-10 */
.sf-track__heading { font-size: 1.5rem; line-height: 2rem; font-weight: 700; margin-bottom: 1.5rem; color: #0f172a; } /* text-2xl font-bold mb-6 text-slate-900 */
@media (min-width: 640px) { .sf-track__heading { font-size: 1.875rem; line-height: 2.25rem; } } /* sm:text-3xl */
.sf-track__heading--dark { color: #ffffff; } /* text-white */

.sf-track__section { border-radius: 1rem; border: 1px solid #e2e8f0; padding: 1rem; margin-bottom: 1rem; transition: background-color 150ms ease, border-color 150ms ease; background-color: #ffffff; } /* rounded-2xl border p-4 mb-4 transition-colors bg-white border-slate-200 */
@media (min-width: 640px) { .sf-track__section { padding: 1.25rem; } } /* sm:p-5 */
.sf-track__section--dark { background-color: #0f172a; border-color: #1e293b; } /* bg-slate-900 border-slate-800 */

.sf-track__summary-row { display: flex; flex-wrap: wrap; align-items: flex-start; justify-content: space-between; gap: 0.75rem; } /* flex flex-wrap items-start justify-between gap-3 */
.sf-track__label { font-size: 0.75rem; line-height: 1rem; text-transform: uppercase; letter-spacing: 0.025em; color: #64748b; } /* text-xs uppercase tracking-wide text-slate-500 */
.sf-track__label--dark { color: #94a3b8; } /* text-slate-400 */
.sf-track__order-number { font-size: 1.125rem; line-height: 1.75rem; font-weight: 700; color: #0f172a; } /* text-lg font-bold text-slate-900 */
.sf-track__order-number--dark { color: #ffffff; } /* text-white */
.sf-track__summary-right { text-align: end; } /* {{if rtl}}text-left{{else}}text-right{{end}} — text-align:end is the dir-aware equivalent */
.sf-track__placed-value { font-size: 0.875rem; line-height: 1.25rem; font-weight: 500; color: #334155; } /* text-sm font-medium text-slate-700 */
.sf-track__placed-value--dark { color: #e2e8f0; } /* text-slate-200 */

.sf-track__status { margin-top: 1rem; display: inline-flex; align-items: center; gap: 0.5rem; border-radius: 9999px; padding-inline: 0.75rem; padding-block: 0.375rem; font-size: 0.875rem; line-height: 1.25rem; font-weight: 600; } /* mt-4 inline-flex items-center gap-2 rounded-full px-3 py-1.5 text-sm font-semibold */
.sf-track__status--bad { background-color: #ffe4e6; color: #be123c; } /* bg-rose-100 text-rose-700 */
.sf-track__status--delivered { background-color: #d1fae5; color: #047857; } /* bg-emerald-100 text-emerald-700 */
.sf-track__status--pending { background-color: #f1f5f9; color: #334155; } /* bg-slate-100 text-slate-700 */
.sf-track__status-dot { width: 0.5rem; height: 0.5rem; border-radius: 9999px; display: inline-block; } /* w-2 h-2 rounded-full */
.sf-track__status-dot--bad { background-color: #f43f5e; } /* bg-rose-500 */
.sf-track__status-dot--delivered { background-color: #10b981; } /* bg-emerald-500 */
.sf-track__status-dot--pending { background-color: #64748b; } /* bg-slate-500 */

.sf-track__timeline { position: relative; } /* relative */
.sf-track__step { display: flex; gap: 0.75rem; padding-bottom: 1.25rem; position: relative; } /* flex gap-3 pb-5 relative */
.sf-track__step:last-child { padding-bottom: 0; } /* last:pb-0 */
.sf-track__connector { position: absolute; top: 1.5rem; bottom: 0; width: 1px; inset-inline-start: 0.75rem; background-color: #e2e8f0; } /* absolute top-6 bottom-0 w-px (left-3/right-3 per dir → inset-inline-start) bg-slate-200 */
.sf-track__connector--dark { background-color: #334155; } /* bg-slate-700 */
.sf-track__connector--done { background-color: #34d399; } /* bg-emerald-400 */

.sf-track__step-marker { position: relative; z-index: 10; flex-shrink: 0; width: 1.5rem; height: 1.5rem; border-radius: 9999px; display: flex; align-items: center; justify-content: center; } /* relative z-10 flex-shrink-0 w-6 h-6 rounded-full flex items-center justify-center */
.sf-track__step-marker--done { background-color: #10b981; } /* bg-emerald-500 */
.sf-track__step-marker--current { background-color: var(--brand); } /* bg-brand-600 — dynamic per-store color, see base.html's remap table */
.sf-track__step-marker--upcoming { background-color: #e2e8f0; } /* bg-slate-200 */
.sf-track__step-marker--upcoming-dark { background-color: #334155; } /* bg-slate-700 */
.sf-track__step-marker-check { width: 0.875rem; height: 0.875rem; color: #ffffff; } /* w-3.5 h-3.5 text-white */
.sf-track__step-marker-dot { width: 0.5rem; height: 0.5rem; border-radius: 9999px; background-color: #ffffff; } /* w-2 h-2 rounded-full bg-white */

.sf-track__step-body { min-width: 0; padding-top: 0.125rem; } /* min-w-0 pt-0.5 */
.sf-track__step-title { font-size: 0.875rem; line-height: 1.25rem; font-weight: 600; } /* text-sm font-semibold */
.sf-track__step-title--upcoming { color: #94a3b8; } /* text-slate-400 */
.sf-track__step-title--upcoming-dark { color: #64748b; } /* text-slate-500 */
.sf-track__step-title--active { color: #0f172a; } /* text-slate-900 */
.sf-track__step-title--active-dark { color: #ffffff; } /* text-white */
.sf-track__step-date { font-size: 0.75rem; line-height: 1rem; margin-top: 0.125rem; color: #64748b; } /* text-xs mt-0.5 text-slate-500 */
.sf-track__step-date--dark { color: #94a3b8; } /* text-slate-400 */

.sf-track__items-heading { font-size: 0.875rem; line-height: 1.25rem; font-weight: 700; margin-bottom: 0.75rem; color: #0f172a; } /* text-sm font-bold mb-3 text-slate-900 */
.sf-track__items-heading--dark { color: #ffffff; } /* text-white */
.sf-track__items-list > * + * { border-top: 1px solid #f1f5f9; } /* divide-y divide-slate-100 */
.sf-track__items-list--dark > * + * { border-top-color: #1e293b; } /* divide-slate-800 */
.sf-track__item { display: flex; align-items: center; gap: 0.75rem; padding-block: 0.75rem; } /* flex items-center gap-3 py-3 */
.sf-track__item-img { width: 3rem; height: 3rem; border-radius: 0.5rem; object-fit: cover; flex-shrink: 0; background-color: #f1f5f9; } /* w-12 h-12 rounded-lg object-cover flex-shrink-0 bg-slate-100 */
.sf-track__item-img--dark { background-color: #1e293b; } /* bg-slate-800 */
.sf-track__item-body { min-width: 0; flex: 1 1 0%; } /* min-w-0 flex-1 */
.sf-track__item-name { font-size: 0.875rem; line-height: 1.25rem; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #0f172a; } /* text-sm font-medium truncate text-slate-900 */
.sf-track__item-name--dark { color: #ffffff; } /* text-white */
.sf-track__item-meta { font-size: 0.75rem; line-height: 1rem; color: #64748b; } /* text-xs text-slate-500 */
.sf-track__item-meta--dark { color: #94a3b8; } /* text-slate-400 */
.sf-track__item-price { font-size: 0.875rem; line-height: 1.25rem; font-weight: 600; white-space: nowrap; color: #0f172a; } /* text-sm font-semibold whitespace-nowrap text-slate-900 */
.sf-track__item-price--dark { color: #ffffff; } /* text-white */

.sf-track__totals { margin-top: 0.75rem; padding-top: 0.75rem; border-top: 1px solid #f1f5f9; font-size: 0.875rem; line-height: 1.25rem; display: flex; flex-direction: column; gap: 0.375rem; } /* mt-3 pt-3 border-t border-slate-100 space-y-1.5 text-sm */
.sf-track__totals--dark { border-top-color: #1e293b; } /* border-slate-800 */
.sf-track__totals-row { display: flex; justify-content: space-between; } /* flex justify-between */
.sf-track__totals-label { color: #64748b; } /* text-slate-500 */
.sf-track__totals-label--dark { color: #94a3b8; } /* text-slate-400 */
.sf-track__totals-value { color: #334155; } /* text-slate-700 */
.sf-track__totals-value--dark { color: #e2e8f0; } /* text-slate-200 */
.sf-track__totals-row--grand { padding-top: 0.375rem; border-top: 1px solid #f1f5f9; } /* pt-1.5 border-t border-slate-100 */
.sf-track__totals-row--grand-dark { border-top-color: #1e293b; } /* border-slate-800 */
.sf-track__totals-label--bold { font-weight: 700; color: #0f172a; } /* font-bold text-slate-900 */
.sf-track__totals-label--bold-dark { color: #ffffff; } /* text-white */
.sf-track__totals-value--bold { font-weight: 700; color: #0f172a; } /* font-bold text-slate-900 */
.sf-track__totals-value--bold-dark { color: #ffffff; } /* text-white */

.sf-track__delivery-list { display: flex; flex-direction: column; gap: 0.5rem; font-size: 0.875rem; line-height: 1.25rem; } /* space-y-2 text-sm */
.sf-track__delivery-row { display: flex; justify-content: space-between; gap: 1rem; } /* flex justify-between gap-4 */
.sf-track__delivery-value { font-weight: 500; text-align: end; color: #334155; } /* font-medium text-end text-slate-700 */
.sf-track__delivery-value--dark { color: #e2e8f0; } /* text-slate-200 */

.sf-track__contact { display: flex; align-items: center; justify-content: center; gap: 0.5rem; width: 100%; border-radius: 0.75rem; padding-inline: 1rem; padding-block: 0.875rem; color: #ffffff; font-weight: 600; transition: background-color 150ms ease; } /* flex items-center justify-center gap-2 w-full rounded-xl px-4 py-3.5 text-white font-semibold transition-colors */
.sf-track__contact--whatsapp { background-color: #25D366; }
.sf-track__contact--whatsapp:hover { background-color: #1da851; }
.sf-track__contact--phone { background-color: var(--brand); } /* bg-brand-600 — dynamic per-store color */
.sf-track__contact--phone:hover { background-color: color-mix(in srgb, var(--brand) 82%, black); } /* hover:bg-brand-700 — matches base.html's .hover\:bg-brand-700 remap */
.sf-track__contact-icon { width: 1.25rem; height: 1.25rem; } /* w-5 h-5 */

.sf-track__hint { margin-top: 1rem; text-align: center; font-size: 0.75rem; line-height: 1rem; color: #94a3b8; } /* mt-4 text-center text-xs text-slate-400 */
.sf-track__hint--dark { color: #64748b; } /* text-slate-500 */

/* ── product_v2.html: description, sticky mobile CTA, success modal
   leftovers from Task 0.12 (Task 0.13) ─────────────────────────────────────
   dark:* utilities throughout this section were confirmed DEAD: every one
   was wrapped in {{ if .IsDark }} in the pre-conversion markup, AND
   individually gated by Tailwind's darkMode:"class" on a literal
   class="dark" ancestor that nothing in this codebase ever sets — so
   dropped rather than ported, per the house dark:-is-dead rule. The one
   exception is the success modal panel's bare `border` (see
   .sf-product__modal-panel--dark-border below): that one was NOT dark:
   -prefixed, so it was live only when the Go .IsDark branch was taken. */
.sf-product__description { display: flex; flex-direction: column; gap: 2rem; padding-top: 1.5rem; border-top: 1px solid #e2e8f0; } /* space-y-8 pt-6 border-t border-slate-200 */
.sf-product__description-body { color: #475569; line-height: 1.625; } /* text-slate-600 leading-relaxed */
.sf-product__description-body--rtl { text-align: right; font-family: Inter, Poppins, "Noto Sans Arabic", sans-serif; } /* text-right font-arabic */
.sf-product__description-images { display: flex; flex-direction: column; } /* flex flex-col gap-0 (gap-0 is a no-op) */
.sf-product__description-image-wrap { overflow: hidden; } /* overflow-hidden */
.sf-product__description-image { width: 100%; height: auto; object-fit: cover; } /* w-full h-auto object-cover */

/* Task 0.13 follow-up #3 from task-0.12-report.md: x-show="showStickyButton"
   is scroll-gated JS state, false in every pixel-harness capture — a static
   screenshot cannot see position:fixed being dropped here. Pinned directly
   by TestProductStickyMobileCTAIsFixedPositioned (0.11's
   .sf-filterbar/position:sticky pattern). */
.sf-product__sticky-cta {
  position: fixed; /* fixed */
  inset-block-end: 0; /* bottom-0 */
  inset-inline: 0; /* inset-x-0 */
  padding: 1rem; /* p-4 */
  background-color: rgb(255 255 255 / 0.8); /* bg-white/80 */
  backdrop-filter: blur(16px); /* backdrop-blur-lg */
  border-top: 1px solid currentColor; /* border-t (bare, no color utility — currentColor) */
  z-index: 40; /* z-40 */
  /* gh-1389 Task 12's two-column grid, merged into this rule by gh-1713 and
     NOT left in its own block at the foot of the file. The total spans both
     tracks, the primary CTA takes the 1fr one and the add-to-bag button the
     auto one; with the cart app off the add button is absent and the auto
     track collapses to zero, so the primary CTA is exactly as wide as it was.

     It must sit ABOVE the lg:hidden line below. Written at the foot of the
     file it was dead (unreachable behind the unclosed .sf-of__submit brace),
     and simply reviving it there put `display: grid` AFTER
     `@media (min-width: 1024px) { display: none }` at equal specificity —
     source order then wins and the MOBILE bar reappears on desktop the moment
     showStickyButton flips. Measured at 1280: display none -> grid. */
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 0.5rem 0.625rem;
}
@media (min-width: 1024px) { .sf-product__sticky-cta { display: none; } } /* lg:hidden */
.sf-product__sticky-cta-btn { width: 100%; padding-block: 1rem; border-radius: 1rem; color: #ffffff; font-weight: 700; box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); display: flex; align-items: center; justify-content: center; gap: 0.5rem; } /* w-full py-4 rounded-2xl text-white font-bold shadow-lg flex items-center justify-center gap-2 */
.sf-product__sticky-cta-icon { width: 1.25rem; height: 1.25rem; } /* w-5 h-5 */

.sf-product__modal-overlay { position: fixed; inset: 0; z-index: 50; display: flex; align-items: center; justify-content: center; padding: 1rem; } /* fixed inset-0 z-50 flex items-center justify-center p-4 */
.sf-product__modal-backdrop { position: fixed; inset: 0; background-color: rgb(0 0 0 / 0.5); backdrop-filter: blur(4px); } /* fixed inset-0 bg-black/50 backdrop-blur-sm */
.sf-product__modal-panel { position: relative; width: 100%; max-width: 28rem; border-radius: 1rem; box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25); padding: 2rem; background-color: #ffffff; } /* relative w-full max-w-md rounded-2xl shadow-2xl p-8 bg-white */
.sf-product__modal-panel--dark-border { border: 1px solid currentColor; } /* the one LIVE dark-only class: bare `border`, added only when .IsDark */
.sf-product__modal-panel--rtl { font-family: Inter, Poppins, "Noto Sans Arabic", sans-serif; } /* font-arabic */
.sf-product__modal-icon-wrap { display: flex; justify-content: center; margin-bottom: 1.5rem; } /* flex justify-center mb-6 */
.sf-product__modal-icon-badge { position: relative; margin-inline: auto; display: flex; height: 6rem; width: 6rem; align-items: center; justify-content: center; border-radius: 9999px; background-color: rgb(236 253 245 / 0.95); box-shadow: 0 18px 45px -24px rgba(16,185,129,0.85); } /* relative mx-auto flex h-24 w-24 items-center justify-center rounded-full bg-emerald-50/95 shadow-[0_18px_45px_-24px_rgba(16,185,129,0.85)] */
.sf-product__modal-icon-ring { position: absolute; inset: 0; border-radius: 9999px; border: 1px solid rgb(167 243 208 / 0.8); } /* absolute inset-0 rounded-full border border-emerald-200/80 */
.sf-product__modal-icon-core { position: relative; display: flex; height: 4rem; width: 4rem; align-items: center; justify-content: center; border-radius: 9999px; background-color: #10b981; color: #ffffff; box-shadow: 0 16px 32px -20px rgba(16,185,129,0.9); outline: 1px solid rgb(255 255 255 / 0.7); } /* relative flex h-16 w-16 items-center justify-center rounded-full bg-emerald-500 text-white shadow-[0_16px_32px_-20px_rgba(16,185,129,0.9)] ring-1 ring-white/70 */
.sf-product__modal-icon-check { height: 2.25rem; width: 2.25rem; } /* h-9 w-9 */
.sf-product__modal-body { text-align: center; display: flex; flex-direction: column; gap: 0.75rem; } /* text-center space-y-3 */
.sf-product__modal-title { font-size: 1.5rem; line-height: 2rem; font-weight: 700; color: #0f172a; } /* text-2xl font-bold text-slate-900 */
.sf-product__modal-subtitle { color: #475569; font-size: 0.875rem; line-height: 1.25rem; } /* text-slate-600 text-sm */
.sf-product__modal-actions { margin-top: 2rem; display: flex; flex-direction: column; gap: 0.75rem; } /* mt-8 space-y-3 */
.sf-product__modal-track-btn { width: 100%; display: flex; align-items: center; justify-content: center; gap: 0.5rem; padding-inline: 1.5rem; padding-block: 0.75rem; border-radius: 0.5rem; font-weight: 700; font-size: 0.875rem; line-height: 1.25rem; transition: box-shadow 150ms ease; color: #ffffff; box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); } /* w-full flex items-center justify-center gap-2 px-6 py-3 rounded-lg font-bold text-sm transition-all text-white shadow-lg */
.sf-product__modal-track-icon { width: 1rem; height: 1rem; } /* w-4 h-4 */
.sf-product__modal-hint { font-size: 0.75rem; line-height: 1rem; text-align: center; color: #64748b; } /* text-xs text-center text-slate-500 */
.sf-product__modal-secondary { width: 100%; display: flex; align-items: center; justify-content: center; padding-inline: 1.5rem; padding-block: 0.75rem; border-radius: 0.5rem; font-weight: 700; font-size: 0.875rem; line-height: 1.25rem; transition: background-color 150ms ease; background-color: #f1f5f9; color: #0f172a; } /* w-full flex items-center justify-center px-6 py-3 rounded-lg font-bold text-sm transition-all bg-slate-100 text-slate-900 */
.sf-product__modal-secondary:hover { background-color: #e2e8f0; } /* hover:bg-slate-200 */
.sf-product__modal-secondary--elevated { gap: 0.5rem; box-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); } /* the "continue shopping" link only: gap-2 shadow-sm — the close <button> has neither */

/* ── partials/checkout_fields.html + custom_fields.html: FULL conversion
   (Task 0.13) — 0.12 left these two with ADDITIVE sf-of__field/sf-of__dtype
   markers only, alongside the original Tailwind utilities (see
   task-0.12-report.md's concern #2). Both dir-based ternaries (icon/chevron
   left-3 vs right-3, and the text/select inputs' pl-10/pr-10 pair) collapse
   into inset-inline-start/end and padding-inline-start/end — pure physical
   mirrors of one rule, no branch needed; verified in both languages. */
.sf-of__row { display: grid; grid-template-columns: repeat(1, minmax(0, 1fr)); gap: 0.75rem; } /* grid grid-cols-1 gap-3 */
@media (min-width: 640px) { .sf-of__row { grid-template-columns: repeat(2, minmax(0, 1fr)); } } /* sm:grid-cols-2 */
.sf-of__row--2col { grid-template-columns: repeat(2, minmax(0, 1fr)); } /* grid-cols-2, unconditionally — the location row, unlike the name/phone row above */
/* NOT display:flex — .sf-of__field's first child is an inline <label> (no
   block/flex class of its own). A flex container promotes it to a flex item
   with a content-only box, dropping the extra "strut" height an inline
   element gets from its block-context line box — 8px per field, caught by
   the pixel harness (46207px diff / 24px shorter page), not by any Go test.
   `> * + *` reproduces space-y-1's margin exactly, in the same block
   context, so the label keeps its original (if slightly odd) sizing. */
.sf-of__field > * + * { margin-top: 0.25rem; } /* space-y-1 */
.sf-of__label { font-size: 0.75rem; line-height: 1rem; font-weight: 700; color: #334155; } /* text-xs font-bold text-slate-700 */
.sf-of__required { color: #ef4444; } /* text-red-500 */

.sf-of__input-wrap { position: relative; } /* relative */
.sf-of__input-icon { position: absolute; inset-inline-start: 0.75rem; top: 50%; transform: translateY(-50%); color: #94a3b8; } /* absolute (left-3/right-3 per dir → inset-inline-start) top-1/2 -translate-y-1/2 text-slate-400 */
.sf-of__input-icon--muted { pointer-events: none; } /* pointer-events-none — the state/city select icons only, they'd otherwise block opening the <select> */
.sf-of__input-icon-svg { width: 1rem; height: 1rem; } /* w-4 h-4 */
.sf-of__select-chevron { position: absolute; inset-inline-end: 0.75rem; top: 50%; transform: translateY(-50%); color: #94a3b8; pointer-events: none; } /* absolute (right-3/left-3 per dir → inset-inline-end) top-1/2 -translate-y-1/2 text-slate-400 pointer-events-none */

.sf-of__input {
  width: 100%;
  min-height: 2.75rem; /* min-h-11 (custom_fields' shape) */
  padding-inline: 1rem; /* px-4 */
  padding-block: 0.625rem; /* py-2.5 */
  border-radius: 0.75rem; /* rounded-xl */
  border: 1px solid #e2e8f0; /* border-slate-200 */
  background-color: #f8fafc; /* bg-slate-50 */
  outline: none;
  transition: border-color 150ms ease, box-shadow 150ms ease; /* transition-all */
  text-align: start; /* text-right/text-left per dir — both wanted the START edge */
}
.sf-of__input:focus { border-color: var(--brand); box-shadow: 0 0 0 1px var(--brand); } /* focus:border-brand-500 focus:ring-1 focus:ring-brand-500 */
.sf-of__input:disabled { opacity: 0.5; } /* disabled:opacity-50 */
.sf-of__input--error { border-color: #ef4444; } /* :class="errors.x ? 'border-red-500...' */
.sf-of__input--error:focus { border-color: #ef4444; box-shadow: 0 0 0 1px #ef4444; } /* focus:border-red-500 focus:ring-red-500 */
.sf-of__input--icon { padding-inline-start: 2.5rem; min-height: 0; } /* checkout_fields' name/phone: pl-10/pr-10 pl-4/pr-4, no min-h-11 in the original */
.sf-of__input--select { padding-inline-start: 2.5rem; padding-inline-end: 2.5rem; min-height: 0; appearance: none; } /* checkout_fields' state/city: pl-10 pr-10 appearance-none, no min-h-11 */
.sf-of__error { color: #ef4444; font-size: 0.75rem; line-height: 1rem; margin-top: 0.25rem; } /* text-red-500 text-xs mt-1 */

.sf-of__dtype-label { display: block; font-size: 0.875rem; line-height: 1.25rem; font-weight: 700; margin-bottom: 0.5rem; color: #1e293b; } /* block text-sm font-bold mb-2 text-slate-800 */
.sf-of__dtype-row { display: flex; gap: 0.75rem; } /* flex gap-3 */
.sf-of__dtype-btn {
  flex: 1 1 0%;
  padding-block: 0.625rem; /* py-2.5 */
  padding-inline: 0.75rem; /* px-3 */
  border-radius: 0.75rem; /* rounded-xl */
  font-weight: 600; /* font-semibold */
  font-size: 0.75rem; /* text-xs */
  line-height: 1rem;
  transition: border-color 150ms ease, background-color 150ms ease, color 150ms ease; /* transition */
  border-width: 2px; /* border-2 */
  border-style: solid;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.375rem; /* gap-1.5 */
}
.sf-of__dtype-btn--active { border-color: var(--brand); background-color: color-mix(in srgb, var(--brand) 8%, white); color: var(--brand); } /* border-brand-600 bg-brand-50 text-brand-600 */
.sf-of__dtype-btn--inactive { border-color: #e2e8f0; color: #475569; } /* border-slate-200 text-slate-600 */
.sf-of__dtype-btn--inactive:hover { border-color: #cbd5e1; } /* hover:border-slate-300 */
.sf-of__dtype-icon { width: 1.25rem; height: 1.25rem; } /* w-5 h-5 */

.sf-of__custom-fields { display: flex; flex-direction: column; gap: 0.75rem; } /* space-y-3 */
.sf-of__custom-label { display: block; } /* block */
.sf-of__custom-label > * + * { margin-top: 0.25rem; } /* space-y-1 between the label text and its input/textarea */
.sf-of__char-count { font-size: 0.75rem; line-height: 1rem; color: #94a3b8; text-align: end; } /* text-xs text-slate-400 text-left/text-right per dir — both wanted the END edge (opposite of .sf-of__input's start) */
.sf-of__hint { font-size: 0.75rem; line-height: 1rem; color: #94a3b8; } /* text-xs text-slate-400 */
.sf-of__bounds-hint { font-size: 0.75rem; line-height: 1rem; color: #94a3b8; } /* text-xs text-slate-400 — #1392's numberBoundsHint() text, kept a distinct class from .sf-of__hint despite identical rules today since the two mark unrelated hints (quantity vs. number bounds) */

/* ── partials/footer.html (Task 0.13) — renders on EVERY page via
   layouts/base.html. Confirmed and dropped as dead: every dark:-prefixed
   utility here (Go-if-wrapped OR bare) needs a literal class="dark"
   ancestor Tailwind's darkMode:"class" requires, which nothing in this
   codebase ever sets. Also confirmed: hover:text-brand-600,
   hover:border-brand-500 and hover:text-brand-500 have NO entry in
   base.html's brand remap table (unlike the non-hover/focus variants), so
   they use the STATIC Tailwind brand hex — replicated as literal
   #059669/#10b981 below, not var(--brand). */
.sf-footer { border-top: 1px solid #e2e8f0; transition: background-color 150ms ease, border-color 150ms ease; } /* border-t transition-colors border-slate-200 */
.sf-footer--default { margin-top: 5rem; background-color: rgba(248, 250, 252, 0.5); } /* mt-20 bg-slate-50/50 */
.sf-footer--lp { margin-top: 0; background-color: #ffffff; } /* mt-0 bg-white */
.sf-footer--product-padded { padding-bottom: 6rem; } /* pb-24 */
@media (min-width: 1024px) { .sf-footer--product-padded { padding-bottom: 0; } } /* lg:pb-0 */
.sf-footer__inner { margin-inline: auto; padding-inline: 1rem; } /* mx-auto px-4 */
.sf-footer__inner--default { max-width: 80rem; padding-block: 3rem; } /* max-w-7xl py-12 */
.sf-footer__inner--lp { max-width: 35rem; padding-block: 2.5rem; } /* max-w-[560px] py-10 */
.sf-footer__main { margin-bottom: 2rem; } /* mb-8 */
.sf-footer__brand { display: flex; flex-direction: column; align-items: center; gap: 1rem; text-align: center; } /* flex flex-col items-center gap-4 text-center */
.sf-footer__logo { height: 3.5rem; width: auto; object-fit: contain; } /* h-14 w-auto object-contain */
.sf-footer__name { font-size: 1.125rem; line-height: 1.75rem; font-weight: 700; margin-bottom: 0.5rem; color: #0f172a; } /* text-lg font-bold mb-2 text-slate-900 */
.sf-footer__desc { font-size: 0.875rem; line-height: 1.25rem; color: #475569; margin-bottom: 1rem; } /* text-sm text-slate-600 mb-4 */
.sf-footer__social { display: flex; align-items: center; justify-content: center; gap: 0.75rem; } /* flex items-center justify-center gap-3 */
.sf-footer__social-link { display: inline-flex; height: 2.75rem; width: 2.75rem; align-items: center; justify-content: center; border-radius: 9999px; border: 1px solid #e2e8f0; background-color: #ffffff; color: #64748b; transition: border-color 150ms ease, color 150ms ease; } /* inline-flex h-11 w-11 items-center justify-center rounded-full border-slate-200 bg-white text-slate-500 transition */
.sf-footer__social-link:hover { border-color: #10b981; color: #10b981; } /* hover:border-brand-500 hover:text-brand-500 (static hex, no remap entry) */
.sf-footer__track-wrap { margin-bottom: 1.5rem; text-align: center; } /* mb-6 text-center */
.sf-footer__track-link { font-size: 0.875rem; line-height: 1.25rem; font-weight: 500; color: #475569; transition: color 150ms ease; } /* text-sm font-medium text-slate-600 transition-colors */
.sf-footer__track-link:hover { color: #059669; } /* hover:text-brand-600 (static hex, no remap entry) */
.sf-footer__copyright { padding-top: 2rem; border-top: 1px solid #e2e8f0; text-align: center; font-size: 0.75rem; line-height: 1rem; color: #475569; } /* pt-8 border-t border-slate-200 text-center text-xs text-slate-600 */
@media (min-width: 768px) { .sf-footer__copyright { font-size: 0.875rem; line-height: 1.25rem; } } /* md:text-sm */
.sf-footer__copyright-link { font-weight: 600; transition: color 150ms ease; } /* font-semibold transition-colors */
.sf-footer__copyright-link:hover { color: #059669; } /* hover:text-brand-600 (static hex, no remap entry) */

/* ── partials/announcement_bar.html (Task 0.13) — renders on home.html,
   order_lookup.html, order_tracking.html, product_v2.html.
   announcement-bar-viewport/-track/-scroll-segment/-item are UNTOUCHED —
   already non-Tailwind, styled in static/input.css, selected by literal
   class name in announcement-bar.js. */
.sf-announcement { width: 100%; overflow: hidden; padding-block: 0.625rem; border-bottom: 1px solid rgba(255, 255, 255, 0.1); color: #ffffff; } /* w-full overflow-hidden py-2.5 border-b border-white/10 text-white */
.sf-visually-hidden { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border-width: 0; } /* sr-only */
.sf-announcement__text { font-size: 0.875rem; line-height: 1.25rem; font-weight: 500; } /* text-sm font-medium */
.sf-announcement__static {
  width: 100%; /* container */
  margin-inline: auto; /* mx-auto */
  padding-inline: 1rem; /* px-4 (overrides container's own responsive padding) */
  display: flex;
  justify-content: center;
  align-items: center;
}
@media (min-width: 640px) { .sf-announcement__static { max-width: 640px; } } /* container's sm breakpoint */
@media (min-width: 768px) { .sf-announcement__static { max-width: 768px; } } /* container's md breakpoint */
@media (min-width: 1024px) { .sf-announcement__static { max-width: 1024px; } } /* container's lg breakpoint */
@media (min-width: 1280px) { .sf-announcement__static { max-width: 1280px; } } /* container's xl/2xl breakpoint — this repo's tailwind.config.js pins 2xl to the same 1280px as xl */
.sf-announcement__static-text { font-size: 0.875rem; line-height: 1.25rem; font-weight: 500; text-align: center; } /* text-sm font-medium text-center */

/* ── partials/sticky_contact.html (Task 0.13) — renders on home.html,
   product_v2.html, landing_page.html. */
.sf-sticky-contact { position: fixed; inset-inline-end: 1rem; z-index: 50; display: flex; flex-direction: column; gap: 0.75rem; } /* fixed (left-4/right-4 per dir → inset-inline-end) z-50 flex flex-col gap-3 */
.sf-sticky-contact--lp { inset-block-end: 6rem; } /* bottom-24 lg:bottom-24 — same value at every width */
.sf-sticky-contact--product { inset-block-end: 5rem; } /* bottom-20 */
@media (min-width: 1024px) { .sf-sticky-contact--product { inset-block-end: 1.5rem; } } /* lg:bottom-6 */
.sf-sticky-contact--default { inset-block-end: 1.5rem; } /* bottom-6 */
.sf-sticky-contact__btn { width: 3rem; height: 3rem; border-radius: 9999px; color: #ffffff; display: flex; align-items: center; justify-content: center; box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); transition: background-color 150ms ease, transform 150ms ease; } /* w-12 h-12 rounded-full text-white flex items-center justify-center shadow-lg transition-all */
.sf-sticky-contact__btn:hover { transform: scale(1.1); } /* hover:scale-110 */
.sf-sticky-contact__btn--phone { background-color: #3b82f6; } /* bg-blue-500 */
.sf-sticky-contact__btn--phone:hover { background-color: #2563eb; } /* hover:bg-blue-600 */
.sf-sticky-contact__btn--whatsapp { background-color: #22c55e; } /* bg-green-500 */
.sf-sticky-contact__btn--whatsapp:hover { background-color: #16a34a; } /* hover:bg-green-600 */
.sf-sticky-contact__icon { width: 1.5rem; height: 1.5rem; } /* w-6 h-6 */

/* ── partials/order_form.html (Task 0.13 fix round) — the "40 hits" left
   behind by 0.12's sf-of contract markers. Two :style bindings on the
   upsell card/checkbox (border-color/background-color from .MainColor) are
   UNCHANGED from before this pass — see order_form.html's own comment on
   why re-deriving them as a {{ json ... }} pipeline would reopen the
   variants.html ZgotmplZ regression. dark:-prefixed / Go-if-wrapped-dark
   utilities confirmed dead per the established pattern, dropped. The
   add-to-cart button's IsDark branch is a genuine exception — it picks
   between two literal, non-"dark:"-prefixed class sets directly, so both
   are implemented for real below. */
.sf-of__form { display: flex; flex-direction: column; gap: 0.75rem; } /* space-y-3 */
.sf-of__soldout { display: inline-flex; align-items: center; gap: 0.375rem; border-radius: 9999px; background-color: #dc2626; padding-inline: 0.75rem; padding-block: 0.25rem; font-size: 0.75rem; line-height: 1rem; font-weight: 600; color: #ffffff; } /* inline-flex items-center gap-1.5 rounded-full bg-red-600 px-3 py-1 text-xs font-semibold text-white */
.sf-of__soldout-icon { width: 0.875rem; height: 0.875rem; } /* w-3.5 h-3.5 */

.sf-of__upsells { display: flex; flex-direction: column; } /* space-y-2 wrapper */
.sf-of__upsells > * + * { margin-top: 0.5rem; } /* space-y-2 */
.sf-of__upsells-header { display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.25rem; } /* flex items-center gap-2 mb-1 */
.sf-of__upsells-header-icon { width: 1rem; height: 1rem; color: #f59e0b; } /* w-4 h-4 text-amber-500 */
.sf-of__upsells-title { font-size: 0.875rem; line-height: 1.25rem; font-weight: 700; color: #0f172a; } /* text-sm font-bold text-slate-900 */

.sf-of__upsell { position: relative; overflow: hidden; padding: 0.75rem; border-width: 2px; border-style: solid; border-radius: 0.75rem; cursor: pointer; transition: border-color 200ms ease, background-color 200ms ease; } /* relative overflow-hidden p-3 border-2 rounded-xl cursor-pointer transition-all duration-200 */
.sf-of__upsell--inactive { border-color: #e2e8f0; } /* border-slate-200 (active branch's border/bg comes from the :style binding) */
.sf-of__upsell--inactive:hover { border-color: #cbd5e1; background-color: #f8fafc; } /* hover:border-slate-300 hover:bg-slate-50 */
.sf-of__upsell-tag { position: absolute; top: 0; inset-inline-end: 0; border-end-start-radius: 0.75rem; background-image: linear-gradient(to right, #f59e0b, #f97316); color: #ffffff; font-size: 0.75rem; line-height: 1rem; font-weight: 700; padding-inline: 0.75rem; padding-block: 0.375rem; box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); z-index: 10; } /* absolute top-0 (left-0 rounded-br-xl / right-0 rounded-bl-xl per dir → inset-inline-end + border-end-start-radius) bg-gradient-to-r from-amber-500 to-orange-500 text-white text-xs font-bold px-3 py-1.5 shadow-lg z-10 */
.sf-of__upsell-body { display: flex; align-items: center; gap: 0.75rem; } /* flex items-center gap-3 */
.sf-of__upsell-check { width: 1.25rem; height: 1.25rem; border-radius: 9999px; border-width: 2px; border-style: solid; flex-shrink: 0; display: flex; align-items: center; justify-content: center; transition: border-color 150ms ease, background-color 150ms ease; } /* w-5 h-5 rounded-full border-2 flex-shrink-0 flex items-center justify-center transition-colors */
.sf-of__upsell-check--inactive { border-color: #cbd5e1; } /* border-slate-300 (active branch's border/bg comes from the :style binding) */
.sf-of__upsell-check-icon { width: 0.75rem; height: 0.75rem; color: #ffffff; } /* w-3 h-3 text-white */
.sf-of__upsell-content { flex: 1 1 0%; } /* flex-1 */
.sf-of__upsell-row { display: flex; justify-content: space-between; align-items: flex-start; gap: 0.75rem; } /* flex justify-between items-start gap-3 */
.sf-of__upsell-left { flex: 1 1 0%; } /* flex-1 */
.sf-of__upsell-name { font-weight: 700; font-size: 0.875rem; line-height: 1.25rem; color: #0f172a; } /* font-bold text-sm text-slate-900 */
.sf-of__upsell-badges { display: flex; align-items: center; gap: 0.375rem; margin-top: 0.375rem; flex-wrap: wrap; } /* flex items-center gap-1.5 mt-1.5 flex-wrap */
.sf-of__upsell-badge--shipping { display: inline-flex; align-items: center; gap: 0.25rem; font-size: 0.625rem; line-height: 0.75rem; font-weight: 700; padding-inline: 0.5rem; padding-block: 0.125rem; border-radius: 0.25rem; background-color: #dcfce7; color: #15803d; } /* inline-flex items-center gap-1 text-[10px] font-bold px-2 py-0.5 rounded bg-green-100 text-green-700 */
.sf-of__upsell-badge-icon { width: 0.625rem; height: 0.625rem; } /* w-2.5 h-2.5 */
.sf-of__upsell-badge--saving { font-size: 0.625rem; line-height: 0.75rem; font-weight: 700; padding-inline: 0.5rem; padding-block: 0.125rem; border-radius: 0.25rem; background-color: #fef3c7; color: #b45309; } /* text-[10px] font-bold px-2 py-0.5 rounded bg-amber-100 text-amber-700 */
.sf-of__upsell-price-col { flex-shrink: 0; text-align: end; } /* flex-shrink-0 text-left/text-right per dir — both wanted the END edge */
.sf-of__upsell-price { font-weight: 900; font-size: 1rem; line-height: 1.5rem; } /* font-black text-base */
.sf-of__upsell-price--best { margin-top: 1rem; } /* :class="offer.isBestOffer ? 'mt-4' : ''" */
.sf-of__upsell-compare { font-size: 0.625rem; line-height: 0.75rem; color: #64748b; text-decoration: line-through; } /* text-[10px] text-slate-500 line-through */

.sf-of__summary-row { display: flex; justify-content: space-between; align-items: center; font-size: 0.75rem; line-height: 1rem; color: #475569; } /* flex justify-between items-center text-xs text-slate-600 */
.sf-of__summary-value { font-weight: 500; color: #0f172a; } /* font-medium text-slate-900 */
/* margin-bottom ONLY (not margin-block): .sf-of__summary's pre-existing
   `> * + * { margin-top: 0.375rem }` rule (0.12, product-card section above)
   already gives every row — including this divider — a 6px margin-top via
   the cascade (later-wins at equal specificity beats the divider's own
   0.25rem/4px, since that rule comes first in file order). The original
   Tailwind markup had exactly this asymmetry: `my-1` (4px/4px) lost its
   margin-top half to the same custom rule, keeping only margin-bottom.
   Setting margin-block here (a fix-round first attempt) collapsed both to
   4px and cost the pixel harness 2px on the whole page — measured via a
   controlled git-show A/B against the last-committed blob, same method as
   the earlier .sf-of__field regression. */
.sf-of__summary-divider { border-top: 1px dashed #e2e8f0; margin-bottom: 0.25rem; } /* border-t border-dashed border-slate-200 my-1 (margin-top half overridden by .sf-of__summary's own cascade, see above) */
.sf-of__summary-total-row { display: flex; justify-content: space-between; align-items: center; font-size: 0.875rem; line-height: 1.25rem; font-weight: 700; color: #0f172a; } /* flex justify-between items-center text-sm font-bold text-slate-900 */
.sf-of__summary-total-value { font-size: 1rem; line-height: 1.5rem; } /* text-base */

.sf-of__form-error { font-size: 0.875rem; line-height: 1.25rem; font-weight: 700; text-align: center; color: #b91c1c; background-color: #fee2e2; border-radius: 0.75rem; padding-inline: 1rem; padding-block: 0.75rem; border: 1px solid #fecaca; } /* text-sm font-bold text-center text-red-700 bg-red-100 rounded-xl px-4 py-3 border border-red-200 */

.sf-of__actions { display: flex; gap: 0.5rem; } /* flex gap-2 */
.sf-of__add-to-cart { position: relative; flex-shrink: 0; width: 3.25rem; display: flex; align-items: center; justify-content: center; transition: background-color 150ms ease, color 150ms ease; } /* relative flex-shrink-0 w-[52px] flex items-center justify-center transition-colors */
.sf-of__add-to-cart:disabled { opacity: 0.5; cursor: not-allowed; } /* disabled:opacity-50 disabled:cursor-not-allowed */
.sf-of__add-to-cart--light { background-color: #f1f5f9; color: #475569; } /* bg-slate-100 text-slate-600 — the genuinely-live .IsDark=false branch */
.sf-of__add-to-cart--dark { background-color: #1e293b; color: #cbd5e1; } /* bg-slate-800 text-slate-300 — the genuinely-live .IsDark=true branch */
.sf-of__add-to-cart-icon { width: 1.25rem; height: 1.25rem; } /* w-5 h-5 */
.sf-of__add-to-cart-icon--added { color: #10b981; } /* text-emerald-500 */
.sf-of__add-to-cart-badge { position: absolute; inset-block-start: -0.25rem; inset-inline-end: -0.25rem; min-width: 1.25rem; height: 1.25rem; padding-inline: 0.25rem; border-radius: 9999px; background-color: #ef4444; color: #ffffff; font-size: 0.75rem; line-height: 1rem; font-weight: 700; display: flex; align-items: center; justify-content: center; } /* absolute -top-1 -end-1 min-w-[1.25rem] h-5 px-1 rounded-full bg-red-500 text-white text-xs font-bold flex items-center justify-center */

.sf-of__submit { flex: 1 1 0%; padding-block: 0.75rem; font-weight: 700; font-size: 0.875rem; line-height: 1.25rem; transition: box-shadow 150ms ease, transform 150ms ease; display: flex; align-items: center; justify-content: center; gap: 0.5rem; } /* flex-1 py-3 font-bold text-sm transition-all flex items-center justify-center gap-2 */
.sf-of__submit:disabled { opacity: 0.75; cursor: not-allowed; } /* disabled:opacity-75 disabled:cursor-not-allowed */
.sf-of__submit:hover { transform: translateY(-0.125rem); } /* transform hover:-translate-y-0.5 */
.sf-of__submit-spinner { width: 1.25rem; height: 1.25rem; animation: sf-spin 1s linear infinite; } /* animate-spin w-5 h-5 */
.sf-of__submit-spinner-track { opacity: 0.25; } /* opacity-25 */
.sf-of__submit-spinner-fill { opacity: 0.75; } /* opacity-75 */
@keyframes sf-spin { to { transform: rotate(360deg); } } /* Tailwind's animate-spin keyframe, namespaced to avoid colliding with a future theme's own @keyframes spin */

/* ── order_lookup.html (Task 0.13 fix round, GET/POST /track) ────────────────
   Unlike most files in this task, EVERY {{ if .IsDark }}...{{ else }}...{{
   end }} here (no "dark:" prefix inside) is genuinely LIVE — implemented as
   real --dark modifier classes below, not dropped. No pixel harness exists
   for this route; verified via go test + a structural curl diff instead. */
.sf-lookup { min-height: 100vh; transition: background-color 150ms ease; background-color: #f8fafc; } /* min-h-screen transition-colors bg-slate-50 */
.sf-lookup--dark { background-color: #020617; } /* bg-slate-950 */
.sf-lookup__header { border-bottom: 1px solid #e2e8f0; transition: background-color 150ms ease, border-color 150ms ease; background-color: #ffffff; } /* border-b transition-colors bg-white border-slate-200 */
.sf-lookup__header--dark { background-color: #0f172a; border-bottom-color: #1e293b; } /* bg-slate-900 border-slate-800 */
.sf-lookup__header-inner { max-width: 42rem; margin-inline: auto; padding-inline: 1rem; padding-block: 1rem; } /* max-w-2xl mx-auto px-4 py-4 */
.sf-lookup__logo { display: flex; align-items: center; gap: 0.5rem; } /* flex items-center gap-2 */
.sf-lookup__logo-img { height: 2.5rem; width: auto; object-fit: contain; } /* h-10 w-auto object-contain */
.sf-lookup__logo-fallback { width: 2.5rem; height: 2.5rem; border-radius: 0.5rem; display: flex; align-items: center; justify-content: center; color: #ffffff; font-weight: 700; } /* w-10 h-10 rounded-lg flex items-center justify-center text-white font-bold */
.sf-lookup__logo-name { font-size: 1.125rem; line-height: 1.75rem; font-weight: 700; color: #0f172a; } /* text-lg font-bold text-slate-900 */
.sf-lookup__logo-name--dark { color: #ffffff; } /* text-white */

.sf-lookup__main { max-width: 42rem; margin-inline: auto; padding-inline: 1rem; padding-block: 1.5rem; } /* max-w-2xl mx-auto px-4 py-6 */
@media (min-width: 640px) { .sf-lookup__main { padding-block: 2.5rem; } } /* sm:py-10 */
.sf-lookup__heading { font-size: 1.5rem; line-height: 2rem; font-weight: 700; margin-bottom: 0.5rem; color: #0f172a; } /* text-2xl font-bold mb-2 text-slate-900 */
@media (min-width: 640px) { .sf-lookup__heading { font-size: 1.875rem; line-height: 2.25rem; } } /* sm:text-3xl */
.sf-lookup__heading--dark { color: #ffffff; } /* text-white */
.sf-lookup__intro { margin-bottom: 1.5rem; font-size: 0.875rem; line-height: 1.25rem; color: #64748b; } /* mb-6 text-sm text-slate-500 */
.sf-lookup__intro--dark { color: #94a3b8; } /* text-slate-400 */
.sf-lookup__dead-link { margin-bottom: 1rem; font-size: 0.875rem; line-height: 1.25rem; font-weight: 500; color: #334155; } /* mb-4 text-sm font-medium text-slate-700 */
.sf-lookup__dead-link--dark { color: #cbd5e1; } /* text-slate-300 */

.sf-lookup__error { margin-bottom: 1rem; border-radius: 0.75rem; border: 1px solid #fecdd3; padding-inline: 1rem; padding-block: 0.75rem; font-size: 0.875rem; line-height: 1.25rem; background-color: #fff1f2; color: #be123c; } /* mb-4 rounded-xl border px-4 py-3 text-sm bg-rose-50 border-rose-200 text-rose-700 */
.sf-lookup__error--dark { background-color: rgba(76, 5, 25, 0.4); border-color: #881337; color: #fecdd3; } /* bg-rose-950/40 border-rose-900 text-rose-200 */
.sf-lookup__error-title { font-weight: 600; } /* font-semibold */
.sf-lookup__error-hint { margin-top: 0.25rem; color: #be123c; } /* mt-1 text-rose-700 */
.sf-lookup__error-hint--dark { color: #fda4af; } /* text-rose-300 */

.sf-lookup__form { border-radius: 1rem; border: 1px solid #e2e8f0; padding: 1rem; transition: background-color 150ms ease, border-color 150ms ease; background-color: #ffffff; } /* rounded-2xl border p-4 space-y-4 transition-colors bg-white border-slate-200 */
@media (min-width: 640px) { .sf-lookup__form { padding: 1.25rem; } } /* sm:p-5 */
.sf-lookup__form > * + * { margin-top: 1rem; } /* space-y-4 */
.sf-lookup__form--dark { background-color: #0f172a; border-color: #1e293b; } /* bg-slate-900 border-slate-800 */
.sf-lookup__field > * + * { margin-top: 0.25rem; } /* space-y-1 */
.sf-lookup__label { font-size: 0.75rem; line-height: 1rem; font-weight: 700; color: #334155; } /* text-xs font-bold text-slate-700 */
.sf-lookup__label--dark { color: #cbd5e1; } /* text-slate-300 */
.sf-lookup__input {
  width: 100%;
  text-align: start; /* text-right/text-left per dir — both wanted the START edge */
  padding-inline: 1rem; /* px-4 */
  padding-block: 0.625rem; /* py-2.5 */
  border-radius: 0.75rem; /* rounded-xl */
  border: 1px solid #e2e8f0; /* border-slate-200 */
  background-color: #f8fafc; /* bg-slate-50 */
  outline: none;
  transition: border-color 150ms ease, box-shadow 150ms ease; /* transition-all */
}
.sf-lookup__input:focus { border-color: var(--brand); box-shadow: 0 0 0 1px var(--brand); } /* focus:border-brand-500 focus:ring-1 focus:ring-brand-500 */
.sf-lookup__hint { font-size: 0.75rem; line-height: 1rem; color: #64748b; } /* text-xs text-slate-500 */
.sf-lookup__hint--dark { color: #94a3b8; } /* text-slate-400 */

.sf-lookup__submit { width: 100%; border-radius: 0.75rem; padding-inline: 1rem; padding-block: 0.875rem; color: #ffffff; font-weight: 600; transition: background-color 150ms ease; background-color: var(--brand); } /* w-full rounded-xl px-4 py-3.5 text-white font-semibold bg-brand-600 transition-colors */
.sf-lookup__submit:hover { background-color: color-mix(in srgb, var(--brand) 82%, black); } /* hover:bg-brand-700 */

.sf-lookup__back { margin-top: 1rem; display: block; text-align: center; font-size: 0.875rem; line-height: 1.25rem; font-weight: 500; color: #64748b; transition: color 150ms ease; } /* mt-4 block text-center text-sm font-medium text-slate-500 */
.sf-lookup__back:hover { color: #334155; } /* hover:text-slate-700 */
.sf-lookup__back--dark { color: #94a3b8; } /* text-slate-400 */
.sf-lookup__back--dark:hover { color: #e2e8f0; } /* hover:text-slate-200 */

/* ── layouts/base.html: <body> (Task 0.13 fix round) — reaches EVERY page.
   dark:bg-slate-900 dark:text-white were confirmed dead (see the template's
   own comment). */
.sf-body { background-color: #f9fafb; color: #0f172a; } /* bg-gray-50 text-slate-900 */

/* ══ Legacy store appearance (gh-1389 L1) ══════════════════════════════════
   Until L1, every declaration below was a literal style="" attribute written
   by the SHARED L1 templates — markup every theme renders verbatim — so an
   inline style outranked whatever the active theme's stylesheet said and no
   theme could own its own appearance. The values now arrive once, as
   --sf-legacy-* custom properties on :root (internal/storefront/legacy_style.go
   → layouts/base.html), and classic consumes ALL of them here to reproduce
   the pre-L1 rendering pixel-for-pixel. noor consumes none: that is the point.

   Two rules govern edits to this block:

   1. It is LAST in the file on purpose. An inline style beat every rule above,
      so these must too; equal specificity + last position is how that is
      reproduced. Do not move it, and do not reach for !important instead.
   2. Each var(--x, #literal) fallback is the exact literal the old
      `{{ or .MainColor "#e11d48" }}` markup carried. legacy_style.go OMITS a
      var whose source setting is empty, so the fallback still fires in the
      same case it always did. Keep them byte-identical.

   Alpha suffixes (--sf-legacy-brand-40 etc.) are the store's colour with the
   literal 8-digit-hex alpha the old markup appended. NOT color-mix(): a
   color-mix percentage does not round-trip to the same bytes, and this file
   is guarded by a maxDiffPixelRatio:0 harness. */

/* home.html / collection.html / partials/product_list.html — product card */
.sf-card__price { color: var(--sf-legacy-brand, #e11d48); }
.sf-card__cta {
  background-color: var(--sf-legacy-shopbtn-bg, #e11d48);
  color: var(--sf-legacy-shopbtn-fg, #ffffff);
}

/* ── gh-1712 · the sold-out call to action ────────────────────────────────
   `.sf-card__cta` promised a purchase on a card whose own chip said the
   product was gone, in the seller's own wording (`ShopButton.Text` overrides
   the label). The markup now swaps the label for `product.out_of_stock` and
   adds this modifier; the box STAYS, because the `:has(.sf-card__add)` rule
   above hides it only when a quick-add is present, so on a cart-off store it
   is the card's one body-level affordance.

   Swapping only the TEXT is not the fix: a buyer reads the shape before the
   word, and an inert label still dressed as this theme's primary action is
   still an invitation. So both halves of the box are restated below — and
   the hover treatment stands down, because a label that lifts or fills under
   the pointer is an invitation whatever it says.

   The pair is this theme's own already-reviewed inert pair, the one
   `.sf-card__add[aria-disabled="true"]` takes, so the two sold-out marks on
   one card cannot read as two different states. Where that pair was chosen
   for a GLYPH it is raised here: a disc clears the 3:1 non-text floor, a
   word has to clear 4.5:1.
   Measured: slate-600 #475569 on slate-200 #e2e8f0 = 6.15:1. The disc's own
   pair is slate-500 on slate-200 = 3.86:1 — enough for a glyph, short for a
   word, so the ink steps one stop darker.

   classic is the ONE theme that keeps this box on screen beside a live
   quick-add: it has no `:has(.sf-card__add)` rule, and `.sf-card__cta` is a
   full-width block (`display: block; width: 100%`) the card body is sized
   around at all times. Nothing here may change its box.

   The rule directly above paints `.sf-card__cta` in the SELLER'S own
   shop-button colours from `--sf-legacy-shopbtn-*`. That is exactly the fill
   this state must not keep, and at equal specificity only source order beats
   it — hence the placement, immediately after it.

   NOT at the end of the file, which is where the other ten themes carry
   their copy of this block. When this was written everything below
   `.sf-of__submit` at the foot of this stylesheet was DEAD — that rule was
   missing its closing brace, so 103 rules were parsed as nested rules of a
   selector that matches nothing. gh-1713 closed the brace; the foot of the
   file is live again and TestEveryThemeStylesheetParsesWithBalancedBlocks
   keeps it that way. This block stays here regardless: source order beside
   the rule it overrides is what makes it work, not the file's end. */
.sf-card__cta--soldout {
  background-color: #e2e8f0;
  color: #475569;
  box-shadow: none;
}
.sf-card-wrap:hover .sf-card__cta--soldout,
.sf-card__cta--soldout:hover,
.sf-card__cta--soldout:active {
  background-color: #e2e8f0;
  color: #475569;
  box-shadow: none;
  transform: none;
}
/* product_list.html ONLY (the HTMX-appended pages). home.html/collection.html
   render page 1 of the same grid without this modifier and never had either
   the transparent border or the hover swap — see product_list.html's comment. */
.sf-card--legacy-hover { border-color: transparent; }
.sf-card--legacy-hover:hover {
  border-color: var(--sf-legacy-brand);
  box-shadow: 0 10px 25px -5px var(--sf-legacy-brand-40);
}

/* partials/nav_classic_{home,product,collection}.html — logo initial badge */
.sf-nav__logo-badge { background-color: var(--sf-legacy-brand, #e11d48); }

/* partials/announcement_bar.html — colours only; the marquee's type + spacing
   stay in this file's announcement section above. */
.sf-announcement {
  background-color: var(--sf-legacy-announce-bg);
  color: var(--sf-legacy-announce-fg, #ffffff);
}

/* cart_page.html + partials/cart.html */
.sf-cart__track-btn { background-color: var(--sf-legacy-brand, #e11d48); }
.sf-cart__continue--filled { background-color: var(--sf-legacy-brand, #e11d48); }
.sf-cart__submit { background-color: var(--sf-legacy-brand, #e11d48); }
.sf-cart-drawer__checkout { background-color: var(--sf-legacy-brand, #e11d48); }

/* order_tracking.html / order_lookup.html — logo initial badge */
.sf-track__logo-fallback { background-color: var(--sf-legacy-brand, #e11d48); }
.sf-lookup__logo-fallback { background-color: var(--sf-legacy-brand, #e11d48); }

/* product_v2.html */
.sf-product__gallery-autoplay-dot { background-color: var(--sf-legacy-brand); }
.sf-buybox__price { color: var(--sf-legacy-brand); }
/* Four modifiers, four different original fallbacks — all resolve to the
   store's brand colour whenever MainColor is set, which it always is on a
   storefront request (getCommonPageData defaults it). */
.sf-product__trust-icon--delivery { color: var(--sf-legacy-brand, #3b82f6); }
.sf-product__trust-icon--guarantee { color: var(--sf-legacy-brand, #22c55e); }
.sf-product__trust-icon--return { color: var(--sf-legacy-brand, #a855f7); }
.sf-product__trust-icon--payment { color: var(--sf-legacy-brand, #f59e0b); }
.sf-product__similar-price { color: var(--sf-legacy-brand, #e11d48); }
.sf-product__similar-cta {
  background: linear-gradient(to right, var(--sf-legacy-brand, #e11d48), var(--sf-legacy-brand-dd, #e11d48DD));
}
.sf-product__sticky-cta-btn {
  background: linear-gradient(to right, var(--sf-legacy-brand), var(--sf-legacy-brand-dd));
  box-shadow: 0 10px 25px -5px var(--sf-legacy-brand-66);
}
.sf-product__modal-track-btn { background-color: var(--sf-legacy-brand, #10b981); }

/* partials/order_form.html — the CTO-reported bleed. FormTheme's border and
   the checkout button's palette are per-store on the product page and
   per-page on a landing page (ServeLandingPage resolves the LP editor's
   overrides before render, and LegacyStyleVars is evaluated at render time,
   so both reach these vars). */
.sf-of {
  border-color: var(--sf-legacy-form-border-color);
  border-width: var(--sf-legacy-form-border-width);
  border-style: solid;
  border-radius: var(--sf-legacy-form-radius);
}
/* Was an Alpine :style binding: an inline style at runtime, so still a bleed. */
.sf-of__upsell--selected {
  border-color: var(--sf-legacy-brand);
  background-color: var(--sf-legacy-brand-15);
}
.sf-of__upsell-check--selected {
  border-color: var(--sf-legacy-brand);
  background-color: var(--sf-legacy-brand);
}
.sf-of__upsell-price { color: var(--sf-legacy-brand); }
.sf-of__summary-total-value { color: var(--sf-legacy-brand); }
.sf-of__add-to-cart { border-radius: var(--sf-legacy-checkout-radius); }
.sf-of__submit {
  background-color: var(--sf-legacy-checkout-bg);
  color: var(--sf-legacy-checkout-fg);
  border-radius: var(--sf-legacy-checkout-radius);
  box-shadow: 0 8px 25px -5px var(--sf-legacy-brand-4d);
}
/* gh-1389 Phase 4 fix wave I2: classic had NO @container rule for
   [data-sf-hidden-mobile] at all, so a seller's mobile-only hide was inert
   on this theme (the server-side base-hide skip still worked; only the
   CSS-only mobile hide did nothing) — see
   internal/storefront/themes/_base/contract.css's own contract comment and
   mobile_hidden_conformance_test.go, which this branch's fix widened to
   actually test classic instead of silently skipping it.

   NO container root, deliberately (gh-1713 -> #1730). The rule below needs
   `.sf-home, .sf-product { container-type: inline-size; }` to ever match,
   and without it the rule is inert, as it has been on every classic page
   since it was written. The root stays off until an iPhone check: classic's
   contact rail, sticky order bar and order-success modal are position:fixed
   INSIDE those shells, and on noor container-type anchored all three to the
   page (docs/gotchas-ui.md). Chromium 149 and Firefox 151 measured clean;
   iOS Safari is unmeasured. #1730 carries the checklist and the sticky-rail
   alternative. Classic's two other @container rules (collection strip,
   similar products) wait on the same root. */

/* mobile-hidden CSS hook — required rule, verbatim from
   _base/contract.css. Additive only: the attribute is emitted exclusively
   by composed renders (sections.MobileHiddenAttr / InjectAttribute), and no
   pre-existing legacy render ever carries data-sf-hidden-mobile, so this
   cannot change any byte of a legacy page. */
@container (max-width: 760px) {
  [data-sf-hidden-mobile] { display: none; }
}

/* gh-1389 UX review round 12: the shared rich-text section. Typography
   inherits the theme; this block only bounds the measure and honors the
   align setting via the data attribute the template publishes. */
.sf-section--rich-text { padding: 32px 0; }
.sf-richtext { max-width: 65ch; margin-inline: auto; line-height: 1.8; }
.sf-richtext p { margin: 0 0 1em; }
.sf-section--rich-text[data-align="start"] .sf-richtext { text-align: start; }
.sf-section--rich-text[data-align="center"] .sf-richtext { text-align: center; }
.sf-section--rich-text[data-align="end"] .sf-richtext { text-align: end; }

/* gh-1389 round 19 Task B: image-with-text — a seller image beside a
   heading/rich-text/CTA column. data-image-side (published by the L1
   template off .Settings.image_side) flips the row via a modifier
   selector, same CSS-only-layout posture as featured-product's own
   --image-right handling above. No aspect box / object-cover on the
   image — img is sized by CSS alone (width:100%,height:auto), the gh-1295
   rule this file already follows everywhere else. */
.sf-image-text { margin-block: 2rem; }
.sf-image-text__row { display: flex; flex-direction: column; gap: 1.5rem; align-items: center; }
@media (min-width: 768px) {
  .sf-image-text__row { flex-direction: row; }
  .sf-image-text[data-image-side="end"] .sf-image-text__row { flex-direction: row-reverse; }
}
.sf-image-text__media { flex: 1 1 50%; min-width: 0; }
.sf-image-text__media img { width: 100%; height: auto; border-radius: 1rem; }
.sf-image-text__body { flex: 1 1 50%; min-width: 0; display: flex; flex-direction: column; gap: 0.75rem; }
.sf-image-text__heading { font-size: 1.5rem; line-height: 2rem; font-weight: 700; color: #0f172a; margin: 0; }
.sf-image-text__text { color: #475569; line-height: 1.7; }
.sf-image-text__text p { margin: 0 0 1em; }
.sf-image-text__cta {
  display: inline-flex; align-items: center; justify-content: center; width: fit-content;
  border-radius: 0.75rem; padding: 0.75rem 1.5rem; font-weight: 700;
  background-color: var(--brand); color: #fff;
}
.sf-image-text__cta:hover { background-color: color-mix(in srgb, var(--brand) 82%, black); }

/* gh-1389 round 19 Task B: features — the COD trust row (delivery/cash on
   delivery/returns...), a "blocks" setting rendered as an icon+title+text
   grid. auto-fit/minmax reflows the grid against the section's OWN inline
   size (no fixed breakpoint needed — a narrower builder-preview device
   toggle wraps exactly like a narrow viewport). */
.sf-features { margin-block: 2rem; }
.sf-features__heading { font-size: 1.5rem; line-height: 2rem; font-weight: 700; margin-bottom: 1.5rem; color: #0f172a; }
.sf-features__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1.5rem; }
.sf-features__item { display: flex; flex-direction: column; gap: 0.5rem; text-align: start; }
.sf-features__icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.75rem; height: 2.75rem; border-radius: 9999px;
  background-color: rgba(226, 232, 240, 0.6); color: var(--brand);
}
.sf-features__title { font-size: 1rem; font-weight: 700; color: #0f172a; margin: 0; }
.sf-features__text { font-size: 0.875rem; color: #64748b; margin: 0; }

/* gh-1389 round 19 Task B: faq — a native <details>/<summary> accordion,
   zero JS. answer is richtext, sanitized at render (richtextHTML). */
.sf-faq { margin-block: 2rem; max-width: 65ch; margin-inline: auto; }
.sf-faq__heading { font-size: 1.5rem; line-height: 2rem; font-weight: 700; margin-bottom: 1rem; color: #0f172a; }
.sf-faq__list { display: flex; flex-direction: column; gap: 0.75rem; }
.sf-faq__item { border: 1px solid #e2e8f0; border-radius: 0.75rem; padding: 1rem 1.25rem; background-color: rgba(255, 255, 255, 0.5); }
.sf-faq__question { cursor: pointer; font-weight: 700; color: #0f172a; }
.sf-faq__answer { margin-top: 0.75rem; color: #475569; line-height: 1.7; }
.sf-faq__answer p { margin: 0 0 1em; }

/* gh-1389 round 19 Task B: testimonials — a card grid, rating rendered as
   N filled stars (never an outline/filled pair) via .sf-testimonials__star,
   one <svg> per star the template emits. */
.sf-testimonials { margin-block: 2rem; }
.sf-testimonials__heading { font-size: 1.5rem; line-height: 2rem; font-weight: 700; margin-bottom: 1.5rem; color: #0f172a; }
.sf-testimonials__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1.5rem; }
.sf-testimonials__card {
  border: 1px solid #e2e8f0; border-radius: 1rem; padding: 1.5rem;
  background-color: rgba(255, 255, 255, 0.5); display: flex; flex-direction: column; gap: 0.5rem;
}
.sf-testimonials__rating { display: flex; gap: 0.125rem; color: #f59e0b; }
.sf-testimonials__quote { color: #334155; line-height: 1.6; margin: 0; }
.sf-testimonials__author { font-size: 0.875rem; font-weight: 700; color: #0f172a; margin: 0; }

/* gh-1389 round 19 Task B: video — a single embed, 16:9, or the shared
   .sf-catalog-section-empty hint (see featured-product's own doc comment
   for that class) when youtubeEmbedURL degrades the setting to "". */
.sf-video { margin-block: 2rem; }
.sf-video__heading { font-size: 1.5rem; line-height: 2rem; font-weight: 700; margin-bottom: 1rem; color: #0f172a; }
.sf-video__frame { position: relative; width: 100%; aspect-ratio: 16 / 9; border-radius: 1rem; overflow: hidden; background-color: #000; }
.sf-video__frame iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }

/* gh-1389 round 19 Task B2: slideshow — a zero-JS scroll-snap carousel.
   The media layer is absolutely positioned behind the text overlay so a
   letterboxed (never cropped, gh-1295) image still fills the slide's
   fixed-height frame; sellerImage's own inline style provides the
   object-fit:contain sizing, this stylesheet only centers it. */
.sf-slideshow { margin-block: 2rem; }
.sf-slideshow__track { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; scroll-behavior: smooth; border-radius: 1rem; }
.sf-slideshow__slide { position: relative; flex: 0 0 100%; scroll-snap-align: start; min-height: 22rem; display: flex; align-items: flex-end; overflow: hidden; background-color: #f1f5f9; }
.sf-slideshow__media { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
.sf-slideshow__media--empty { background-color: #f1f5f9; }
.sf-slideshow__overlay { position: relative; z-index: 1; width: 100%; padding: 1.5rem; background: linear-gradient(to top, rgba(15, 23, 42, 0.7), rgba(15, 23, 42, 0)); color: #fff; }
.sf-slideshow__heading { font-size: 1.5rem; line-height: 2rem; font-weight: 700; margin: 0 0 0.25rem; }
.sf-slideshow__subheading { margin: 0 0 0.75rem; font-size: 0.9375rem; }
.sf-slideshow__cta { display: inline-block; padding: 0.5rem 1.25rem; border-radius: 0.5rem; background-color: #fff; color: #0f172a; text-decoration: none; font-weight: 700; }

/* gh-1389 round 19 Task B2: order-steps — the COD trust row's numbered
   sibling. The number is rendered by the template from the loop index
   (addInt), never a seller setting; this stylesheet only draws the badge. */
.sf-order-steps { margin-block: 2rem; }
.sf-order-steps__heading { font-size: 1.5rem; line-height: 2rem; font-weight: 700; margin-bottom: 1.5rem; color: #0f172a; }
.sf-order-steps__list { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1.5rem; list-style: none; margin: 0; padding: 0; }
.sf-order-steps__item { display: flex; flex-direction: column; gap: 0.75rem; text-align: start; }
.sf-order-steps__number { display: flex; align-items: center; justify-content: center; width: 2.5rem; height: 2.5rem; border-radius: 9999px; background-color: var(--brand); color: #fff; font-weight: 700; }
.sf-order-steps__title { font-size: 1rem; font-weight: 700; color: #0f172a; margin: 0; }
.sf-order-steps__text { font-size: 0.875rem; color: #64748b; margin: 0; }

/* gh-1389 round 19 Task B2: gallery — a responsive grid of seller images at
   their NATURAL aspect ratio (sellerImage's own object-fit:contain, no
   aspect-box crop per gh-1295); the grid cell only bounds the max width. */
.sf-gallery { margin-block: 2rem; }
.sf-gallery__grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 1rem; }
.sf-gallery__item { display: flex; flex-direction: column; gap: 0.5rem; margin: 0; }
.sf-gallery__item img { width: 100%; height: auto; border-radius: 0.5rem; display: block; }
.sf-gallery__caption { font-size: 0.875rem; color: #64748b; text-align: center; }

/* gh-1389 round 19 Task B2: contact — heading/text plus up to two CTA
   buttons (WhatsApp/phone). See contact.html's own doc comment for the
   ZgotmplZ-safe href construction this markup relies on. */
.sf-contact { margin-block: 2rem; max-width: 40rem; margin-inline: auto; text-align: center; }
.sf-contact__heading { font-size: 1.5rem; line-height: 2rem; font-weight: 700; margin-bottom: 0.5rem; color: #0f172a; }
.sf-contact__text { color: #475569; line-height: 1.7; margin: 0 0 1.5rem; }
.sf-contact__actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 0.75rem; }
.sf-contact__cta { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.625rem 1.25rem; border-radius: 0.5rem; font-weight: 700; text-decoration: none; }
.sf-contact__cta--whatsapp { background-color: #25d366; color: #fff; }
.sf-contact__cta--phone { background-color: var(--brand); color: #fff; }

/* round-19 skip-empty fix: numbering comes from a CSS counter, not the
   template loop index, so a skipped (empty) item never leaves a gap. */
.sf-order-steps__list { counter-reset: sf-step; }
.sf-order-steps__item { counter-increment: sf-step; }
.sf-order-steps__number::before { content: counter(sf-step); }

/* ── chrome seam (gh-1389 Task 12) ────────────────────────────────────────
   Bottom nav. classic does NOT select `"chrome": {"nav": "bottom"}` — its
   theme.json carries no chrome block at all, so PageData.NavVariant resolves
   to "top" and home.html never reaches partials/nav_bottom.html on this
   theme. The rules below therefore exist to satisfy the contract, not to
   paint anything: a contract class with no rule in a theme.css is a surface
   that renders bare in the one case the branch DOES fire (a preview, or a
   future classic variant), and an unstyled inline <svg> renders at 300x150.
   Hiding it outright is the honest answer for a theme whose design has one
   nav bar. If classic ever ships a tab bar, replace this whole block —
   `display: none` here is a decision, not a placeholder to fill in later. */
.sf-bnav { display: none; }
.sf-bnav__item { display: none; }
.sf-bnav__icon { width: 1.25rem; height: 1.25rem; }
.sf-bnav__label { font-size: 0.625rem; line-height: 1rem; }
.sf-bnav__cart-count { display: none; }
.sf-bnav__raise { display: none; }

/* Bag-in-nav — gh-1389 Task 12 review fix round 2: nav_classic_*.html now
   emits this slot only when the theme's OWN chrome.cart_toggle is "nav"
   (PageData.CartToggleInNav). classic does not set it, so on classic this
   markup never renders and .sf-cart-toggle (the floating button) stays
   classic's one and only cart entry point, unchanged from before Task 12.
   Before this fix the slot rendered whenever the cart app was merely on, so
   every cart-enabled classic store showed this bag AND the floating button
   at once. These rules are dead code on classic today, kept only so a
   future theme that DOES set cart_toggle: "nav" inherits a correctly styled
   bag without having to invent one — deliberately NOT .sf-cart-toggle's box:
   that button floats over the page and needs its own 3rem white pill with a
   border and a shadow to read as a surface, while this one sits INSIDE the
   bar and takes the same treatment as its neighbours .sf-nav__burger and
   .sf-nav__search-toggle — a 2.5rem transparent hit target that tints on
   hover, inheriting the bar's own background. Only the badge is shared with
   .sf-cart-badge's look, because that is the part a buyer reads as a count. */
.sf-nav__bag {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 9999px;
  color: #0f172a;
  background-color: transparent;
  transition: background-color 150ms ease, color 150ms ease;
}
.sf-nav__bag:hover { background-color: #f1f5f9; }
.sf-nav--dark .sf-nav__bag { color: #ffffff; }
.sf-nav--dark .sf-nav__bag:hover { background-color: #1e293b; }
.sf-nav__bag-icon { width: 1.375rem; height: 1.375rem; }
.sf-nav__bag-count {
  position: absolute;
  inset-block-start: 0;
  inset-inline-end: 0;
  min-width: 1.125rem;
  height: 1.125rem;
  padding-inline: 0.25rem;
  border-radius: 9999px;
  background-color: #ef4444;
  color: #ffffff;
  font-size: 0.6875rem;
  line-height: 1rem;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Footer link columns (gh-1389 Task 12). classic's footer is a single centred
   stack — brand, social, /track, copyright — so the block stays ONE column
   and only inherits that alignment; a theme wanting four columns overrides
   .sf-footer__cols alone. Matched to .sf-footer__track-link's own type scale
   and hover, so the two link groups read as one footer. */
.sf-footer__cols {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 1.5rem;
  text-align: center;
}
.sf-footer__col {
  font-size: 0.875rem;
  line-height: 1.25rem;
  font-weight: 500;
  color: #475569;
  text-decoration: none;
  transition: color 150ms ease;
}
.sf-footer__col:hover { color: #059669; }

/* Order-placed WhatsApp action (gh-1389 Task 12). Rendered inside
   .sf-product__modal-actions and .sf-cart__success-actions, both of which are
   `flex-direction: column`, so this matches .sf-product__modal-secondary's box
   and carries WhatsApp's own brand green — the one place in classic where a
   channel's colour outranks the store's, because a buyer recognises the
   button by it. */
.sf-success__whatsapp {
  width: 100%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding-inline: 1.5rem;
  padding-block: 0.75rem;
  border-radius: 0.5rem;
  font-weight: 700;
  font-size: 0.875rem;
  line-height: 1.25rem;
  text-decoration: none;
  background-color: #25d366;
  color: #ffffff;
  transition: background-color 150ms ease;
}
.sf-success__whatsapp:hover { background-color: #1da851; }
.sf-success__whatsapp-icon { width: 1.125rem; height: 1.125rem; }

/* Sticky product bar contents (gh-1389 Task 12). The bar itself is a
   two-column grid — that half lives on .sf-product__sticky-cta's own rule
   above, beside its position:fixed and above the lg:hidden line it would
   otherwise outrank (gh-1713). These are the bar's CHILDREN, which collide
   with nothing and stay here.

   The bar is scroll-gated JS state (x-show="showStickyButton"), false in
   every initial render the pixel harness captures, so no Phase-0 baseline
   sees these rules; TestProductStickyMobileCTAIsFixedPositioned is what pins
   the container's own position. */
.sf-product__sticky-add {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3.25rem;
  height: 3.25rem;
  border-radius: 1rem;
  border: 1px solid #e2e8f0;
  background-color: #ffffff;
  color: #0f172a;
  transition: background-color 150ms ease, border-color 150ms ease;
}
.sf-product__sticky-add:hover { background-color: #f1f5f9; }
.sf-product__sticky-add:disabled { opacity: 0.5; cursor: not-allowed; }
.sf-product__sticky-add-icon { width: 1.25rem; height: 1.25rem; }

/* ── iOS focus-zoom floor ──────────────────────────────────────────────────
   Mobile Safari zooms the viewport in when the control the buyer focuses has
   a computed font-size under 16px, and it never zooms back out: the rest of
   the checkout is then magnified and scrolls horizontally. Every control in
   this stylesheet is designed smaller than that, so the floor is stated once,
   here, instead of on each rule.

   `pointer: coarse`, not a width: the defect is the touch device, so a theme
   keeps its designed desktop scale and only the zooming case is raised. This
   is LAST in the file on purpose — `.sf-body input` is class+element (0,1,1),
   which already outranks `.sf-of__input` and `.sf-lookup__input` (0,1,0), but
   a later rule at equal specificity would still win. Rooted at `.sf-body`
   (base.html's <body> class) rather than a page shell because the cart drawer
   and the product order bar are <body> children outside every shell (#1800).

   themes.TestEveryThemeFloorsTouchFormControlsAtSixteenPixels pins all of it,
   including that nothing below re-lowers a control's size. */
@media (pointer: coarse) {
  .sf-body input,
  .sf-body select,
  .sf-body textarea {
    font-size: 16px;
  }
}
