/* ==========================================================================
   Shared components: placeholders, section heads, product cards, category rows
   ========================================================================== */

/* Image placeholder ------------------------------------------------------- */
/* Every empty image slot renders this, so a missing photo is obvious in review
   rather than silently showing a broken-image icon. IMAGE-SLOTS.md lists them. */
.ph {
  position: relative;
  display: grid;
  place-items: center;
  background:
    repeating-linear-gradient(45deg, #e4e1d9 0 10px, #ded9d0 10px 20px);
  color: var(--brand-soft);
  aspect-ratio: var(--ph-ratio, 3 / 4);
  font-size: 10px;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  text-align: center;
  padding: 12px;
}
.ph::before { content: "Photo needed"; }

/* Section heading --------------------------------------------------------- */
/* MEASURED: the reference title block is ~731px at a 1553 viewport and its
   headings break to two lines. 700px forced a third line here. */
.sec-head { text-align: center; max-width: 820px; margin: 0 auto 54px; }
.sec-head .eyebrow { display: block; margin-bottom: 10px; }
.sec-head p:not(.eyebrow) { margin-top: 14px; }
.sec-head .link-cta { margin-top: 20px; }

/* Curved marquee ribbon ----------------------------------------------------
   MEASURED. The reference does NOT set a height on the SVG. The viewBox
   (0 0 2000 140) fixes the aspect ratio, so height follows width, and the text
   stays a flat 26px at every breakpoint. To keep that text legible as the
   screen narrows it SCALES THE WHOLE BAND: measured scale(1.03) at a 1920
   viewport and scale(2.4) at 430, with the wrapper clipping the overflow.

   This build previously set explicit heights (137/104/78px) and inflated the
   font instead. With preserveAspectRatio="none" a forced height scales x and y
   independently - at 353px that was x0.18 against y0.56, which stretched every
   letter vertically. Hence the distorted look on a phone.

   The 1.03 and 2.4 anchors are measured; the steps between are interpolated. */
/* transform: scale() does NOT change layout size, so the wrapper would reserve
   only the unscaled height (24px at a phone width) while the band renders at
   57px+ and spills over the section below it. The padding reserves the
   overflow: half of (scale - 1) x the natural height, where the natural height
   is 100vw / 14.29 from the viewBox. Overflow still clips to the padding box,
   so the whole band stays visible. */
.ribbon-wrap {
  overflow: hidden;
  line-height: 0;
  --ribbon-scale: 1.03;
  padding-block: calc((var(--ribbon-scale) - 1) * 100vw / 14.29 / 2);
  /* The band is not a .section, so it gets no section rhythm of its own.
     Half the section gap keeps it clear of what sits either side at every
     breakpoint. */
  margin-block: calc(var(--section-gap) * 0.5);
}
.ribbon {
  display: block;
  width: 100%;
  height: auto;              /* let the viewBox set it - do not pin a height */
  transform-origin: center;
  color: var(--brand);       /* the band fills with currentColor */
}
.ribbon__text {
  font-family: var(--font-body);
  font-size: 26px;           /* MEASURED: flat 26px at every width */
  font-weight: 400;
  letter-spacing: normal;
  text-transform: uppercase;
  fill: #f3e9e6;
}

/* Scale and reserved padding must move together, so both read the same var. */
.ribbon { transform: scale(var(--ribbon-scale)); }
@media (max-width: 1199px) { .ribbon-wrap { --ribbon-scale: 1.3; } }
@media (max-width: 991px)  { .ribbon-wrap { --ribbon-scale: 1.6; } }
@media (max-width: 767px)  { .ribbon-wrap { --ribbon-scale: 1.9; } }
@media (max-width: 575px)  { .ribbon-wrap { --ribbon-scale: 2.4; } }

/* Honour a reduced-motion preference: the band stays, the scroll stops. */
@media (prefers-reduced-motion: reduce) {
  .ribbon animate { display: none; }
}

/* Product card ------------------------------------------------------------ */
.prod-card { position: relative; }
/* MEASURED: reference thumbnail is 605 x 787 = 605/787, slightly wider than 3:4. */
.prod-card__media {
  position: relative;
  display: block;
  overflow: hidden;
  /* MEASURED: the reference's .thumbnail-img sits on #f0efea - the same bone as
     the page. --surface-alt (#e8e6df) was a shade too dark, which is why the
     cards read as boxed against the section behind them. */
  background: #f0efea;
  aspect-ratio: 605 / 787;
}
.prod-card__img {
  width: 100%; height: 100%;
  object-fit: cover;
  transition: opacity var(--dur) var(--ease);
}
/* MEASURED: hover crossfades to the second photograph and NOTHING else. The
   reference does not zoom the image - both images report transform: none while
   hovered. The scale(1.04) that used to be here was invented. */
.prod-card__img--hover { position: absolute; inset: 0; opacity: 0; }
.prod-card:hover .prod-card__img--hover,
.prod-card:focus-within .prod-card__img--hover { opacity: 1; }

/* MEASURED: Nunito 14px, weight 400, letter-spacing normal, padding 5px 10px. */
.prod-card__badge {
  position: absolute;
  top: 14px; left: 14px;
  z-index: 2;
  background: var(--brand);
  color: #fff;
  font-size: 14px;
  font-weight: 400;
  letter-spacing: normal;
  padding: 5px 10px;
  line-height: normal;
}
.prod-card__badge--out { background: var(--ink-muted); }

/* RE-MEASURED on the reference's own card: the column sits TOP-right, 10px in
   from both edges, buttons 40x40 with an 8px gap, bone #f0efea ground, square
   corners, #222 icons. (The earlier 15px/45px reading was off.) At rest the
   column is opacity 0 and translated 50px to the RIGHT - it slides in
   horizontally, not up from the bottom. */
.prod-card__acts {
  position: absolute;
  right: 10px; top: 10px;
  z-index: 3;
  display: grid;
  gap: 8px;
  opacity: 0;
  transform: translateX(50px);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.prod-card:hover .prod-card__acts,
.prod-card:focus-within .prod-card__acts { opacity: 1; transform: translateX(0); }

/* MEASURED: on a phone the reference shows these permanently - opacity 1, no
   offset. It has to: hover never fires on a touch screen, so gating them behind
   :hover made wishlist, add-to-cart and view unreachable on mobile entirely. */
@media (hover: none), (max-width: 991px) {
  .prod-card__acts { opacity: 1; transform: none; }
}
.prod-card__act {
  width: 40px; height: 40px;      /* MEASURED */
  display: grid; place-items: center;
  background: #f0efea;
  color: var(--ink);
  /* The maroon comes from the .btn-hv wipe, not from a background swap. */
  transition: color var(--dur) var(--ease);
}
.prod-card__act:hover { color: #f0efea; }
/* A saved item stays filled, so the wipe is held open rather than animated. */
.prod-card__act[aria-pressed="true"] { color: #f0efea; }
.prod-card__act[aria-pressed="true"]::after { transform: scaleX(1); }

/* MEASURED: content-wrap carries 6px of top padding, not 16px. */
.prod-card__body { padding-top: 6px; text-align: center; }
/* RE-MEASURED across all twelve cards on the reference homepage: the title is
   the BODY face - Nunito 14px, weight 400, #222, uppercase, centred - not
   Tenor Sans 16px. No stylesheet rule sets a font on it at any breakpoint; it
   simply inherits from body, so this holds at every width. */
.prod-card__title {
  font-family: var(--font-body);
  font-size: var(--fs-card-title);
  font-weight: 400;
  letter-spacing: normal;
  text-transform: uppercase;
  color: var(--ink);
  line-height: 1.3;
  margin-top: 6px;
}
.prod-card__title:hover { color: var(--brand); }
/* MEASURED: Nunito 15px, maroon, hard against the title - no top margin. */
.prod-card__price { margin-top: 0; font-size: 15px; color: var(--brand); font-weight: 600; }
/* MEASURED: the old price is struck by a 1px #222 rule drawn as del::before,
   not by text-decoration - which would inherit the muted grey and sit thinner. */
.prod-card__was {
  position: relative;
  color: var(--ink-muted);
  text-decoration: none;
  font-weight: 400;
  margin-right: 8px;
  font-size: 15px;
}
.prod-card__was::before {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 1px;
  background: var(--ink);
}
.prod-card__quote { margin-top: 10px; color: var(--brand); font-size: 15px; font-weight: 600; letter-spacing: normal; text-transform: uppercase; }

/* Countdown on sale cards, as the reference shows ------------------------- */
.countdown {
  position: absolute;
  left: 50%; bottom: 0;
  transform: translateX(-50%);
  width: calc(100% - 28px);
  display: grid;
  grid-auto-flow: column;
  background: var(--surface);
  padding: 10px 4px;
  text-align: center;
}
.countdown__n { font-weight: 700; font-size: 15px; display: block; }
.countdown__l { font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-muted); }
.countdown__unit:last-child .countdown__n { color: var(--sale); }

/* Category rows (the pinned homepage list) -------------------------------- */
/* MEASURED: each category row box is 189px tall with a
   1px solid rgba(0,0,0,0.1) bottom rule - not a 34px-padded row on --line. */
.cat-list { border-top: 1px solid rgba(0, 0, 0, 0.1); }
.cat-row {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  min-height: 189px;
  padding-inline: var(--gutter);
  border-bottom: 1px solid rgba(0, 0, 0, 0.1);
  transition: padding-left var(--dur) var(--ease);
}
.cat-row:hover { padding-left: calc(var(--gutter) + 18px); }
/* MEASURED: Tenor Sans 40px, weight 400, letter-spacing normal, maroon,
   line-height 1.2. Row spans the full container width. */
.cat-row__name {
  font-family: var(--font-head);
  font-size: var(--fs-cat-row);
  font-weight: 400;
  letter-spacing: normal;
  text-transform: uppercase;
  color: var(--brand);
  line-height: 1.2;
}
.cat-row__count { font-size: 14px; vertical-align: super; margin-left: 10px; color: var(--brand-soft); }

/* MEASURED: 50x74 inline at the left of each row on a phone, always visible.
   On desktop the floating hover peek does this job, so it is hidden. */
.cat-row__thumb { display: none; }
@media (max-width: 991px) {
  .cat-row { gap: 14px; }
  .cat-row__thumb {
    display: block;
    flex: none;
    width: 50px;
    aspect-ratio: 50 / 74;
    background: var(--surface-alt);
    overflow: hidden;
  }
  .cat-row__thumb img { width: 100%; height: 100%; object-fit: cover; }
  /* The name takes the remaining width and may wrap to two lines. */
  .cat-row__name { flex: 1; }
  .cat-row:hover { padding-left: var(--gutter); }   /* no slide-in on touch */
}
.cat-row__arrow { flex: none; color: var(--ink); transition: transform var(--dur) var(--ease); }
.cat-row:hover .cat-row__arrow { transform: translate(6px, -6px); }

/* Image that follows the cursor across the list. Hidden on touch and for
   readers who asked for reduced motion. */
/* MEASURED: the reference's hover image is 220 x 325 and fades at
   0.4s ease-in-out. */
.cat-peek {
  position: fixed;
  z-index: 5;
  width: 220px;
  aspect-ratio: 220 / 325;
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, -50%) scale(0.94);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
  overflow: hidden;
}
.cat-peek.is-on { opacity: 1; transform: translate(-50%, -50%) scale(1); }
.cat-peek img { width: 100%; height: 100%; object-fit: cover; }
@media (hover: none), (prefers-reduced-motion: reduce) {
  .cat-peek { display: none; }
}

/* Page hero banner (inner pages) ------------------------------------------ */
/* MEASURED: reference page banner is 195px tall on a maroon ground. */
.pagehero {
  position: relative;
  background: var(--brand);
  color: #f0efea;
  min-height: 195px;
  display: flex;
  align-items: center;
  overflow: hidden;
}
.pagehero__bg { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: 0.5; }
.pagehero__inner {
  position: relative;
  width: 100%;
  padding-inline: var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
}
/* MEASURED: Tenor Sans 36px, weight 500, letter-spacing normal, bone. */
.pagehero h1 { color: #f0efea; font-size: 36px; font-weight: 500; letter-spacing: normal; }
.crumbs { display: flex; gap: 10px; align-items: center; font-size: 14px; letter-spacing: normal; text-transform: uppercase; }
.crumbs a:hover { text-decoration: underline; }

/* Toast (add-to-cart feedback) -------------------------------------------- */
.toast {
  position: fixed;
  right: 20px; bottom: 20px;
  z-index: 120;
  background: var(--brand);
  color: #fff;
  padding: 14px 20px;
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  transform: translateY(20px);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.toast.is-on { opacity: 1; transform: translateY(0); }

/* Search overlay ---------------------------------------------------------- */
.searchov {
  position: fixed;
  inset: 0;
  z-index: 110;
  background: rgba(20, 12, 10, 0.5);
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
  overflow-y: auto;
}
.searchov.is-open { opacity: 1; }
.searchov__panel {
  background: var(--bg);
  max-width: 720px;
  margin: 0 auto;
  padding: 40px 28px 34px;
  position: relative;
  transform: translateY(-16px);
  transition: transform var(--dur) var(--ease);
}
.searchov.is-open .searchov__panel { transform: translateY(0); }
.searchov__close { position: absolute; top: 14px; right: 18px; font-size: 26px; color: var(--ink-muted); }
.searchov__close:hover { color: var(--brand); }
.searchov__input {
  width: 100%;
  font-family: var(--font-head);
  font-size: 22px;
  letter-spacing: 0.04em;
  color: var(--brand);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--line);
  padding: 12px 0;
}
.searchov__input:focus { outline: none; border-color: var(--brand); }
.searchov__input::placeholder { color: var(--ink-muted); opacity: 0.7; }
.searchov__status { font-size: 11px; letter-spacing: var(--tracking-wide); text-transform: uppercase; color: var(--ink-muted); margin: 16px 0 0; }
.searchov__results { display: grid; gap: 2px; margin-top: 18px; }
.searchres { display: flex; align-items: center; gap: 14px; padding: 10px; transition: background var(--dur) var(--ease); }
.searchres:hover { background: var(--surface); }
.searchres__img { width: 52px; height: 68px; object-fit: cover; flex: none; background: var(--surface-alt); }
.searchres__img.ph { aspect-ratio: auto; }
.searchres__img.ph::before { content: ""; }
.searchres__body { display: grid; gap: 4px; }
.searchres__title { font-family: var(--font-head); font-size: 13px; letter-spacing: var(--tracking-head); text-transform: uppercase; }
.searchres__meta { font-size: 12px; color: var(--brand); }

/* MEASURED: .cat-box is 189px on desktop and 105px at a 430px viewport. */
@media (max-width: 991px) { .cat-row { min-height: 130px; } }
@media (max-width: 767px) { .cat-row { min-height: 115px; } }
@media (max-width: 575px) { .cat-row { min-height: 105px; } }

/* Page banner, responsive ---------------------------------------------------
   MEASURED off the reference's .blomify-page-title:
     1199: padding 45px 0        991: 35px 0, min-height 60
      767: 25px 0, min-height 50 575: 20px 0        480: 15px 0
   This build held a flat 195px min-height at every width, which ate most of a
   phone screen before any content appeared. */
.pagehero__inner { padding-block: 0; }

@media (max-width: 1199px) {
  .pagehero { min-height: 0; }
  .pagehero__inner { padding-block: 45px; }
  .pagehero h1 { font-size: 30px; }
}
@media (max-width: 991px) {
  .pagehero { min-height: 60px; }
  .pagehero__inner { padding-block: 35px; }
  .pagehero h1 { font-size: 26px; }
}
/* MEASURED at 353px: the banner is 69px tall and BOTH the title and the
   breadcrumb are centred - not left-aligned with the crumb opposite. Title 16px. */
@media (max-width: 767px) {
  .pagehero { min-height: 50px; }
  .pagehero__inner {
    padding-block: 25px;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 8px;
  }
  .pagehero h1 { font-size: 22px; }
  .crumbs { justify-content: center; }
}
@media (max-width: 575px) {
  .pagehero__inner { padding-block: 20px; gap: 6px; }
  .pagehero h1 { font-size: 16px; }
  .crumbs { font-size: 14px; gap: 6px; }
}
@media (max-width: 480px) {
  .pagehero__inner { padding-block: 15px; gap: 4px; }
}

/* MEASURED: the reference's banner is 69px at 353px, and it gets there by
   printing ONLY the current page in the breadcrumb - its .breadcrumbs holds
   the product name and nothing else. The full trail here wrapped to three
   lines and pushed the banner to 156px, more than twice the reference.

   The trail is kept in the markup for desktop and for assistive technology
   (the nav is still announced in full); only the ancestor links are hidden
   from sight on a phone. */
@media (max-width: 767px) {
  .crumbs > *:not(:last-child) { display: none; }
}

/* Card "Add to cart" ---------------------------------------------------------
   MEASURED: 14px weight 500, centred, full card width, shown on a phone only.
   On desktop the hover icon column does this job, so it stays hidden there. */
.prod-card__add { display: none; }

@media (hover: none), (max-width: 991px) {
  .prod-card__add {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    width: 100%;
    /* MEASURED: the reference's .cart-wrap row is 32px tall and sits hard
       against the price. It spans the whole card, so the target stays wide
       even though it is short. */
    min-height: 32px;
    margin-top: 0;
    font-size: 14px;
    font-weight: 500;
    letter-spacing: normal;
    text-transform: uppercase;
    color: var(--ink);
    transition: color var(--dur) var(--ease);
  }
    /* The wipe fills maroon; the label flips to bone, not to maroon. */
  .prod-card__add:hover { color: #f0efea; }
  /* The icon column keeps wishlist and view; the cart icon there would now be
     a duplicate of this button. */
  .prod-card__acts [data-add-to-cart] { display: none; }
}

/* Spinning seal -------------------------------------------------------------
   MEASURED off the reference: a 100 x 100 box between the ribbon and the
   category heading. The svg carries viewBox "0 0 250.5 250.5", so its 24-unit
   ring face renders at 24 x 100 / 250.5 = 9.6px, and the brand initial sits
   over the middle at a flat 44px.

   The svg is the ONLY thing that spins. The initial is a sibling, not a child,
   so it stays upright while the words turn around it - putting it inside the
   svg would have rotated the letter with the ring.
   -------------------------------------------------------------------------- */
.seal-wrap {
  display: flex;
  justify-content: center;
  /* A rotating square sweeps a circle of its own diagonal, so the seal claims
     100 x root2 = 141px while it turns. Reserving the extra 41px here keeps it
     from clipping the ribbon above or the heading below mid-rotation. */
  padding-block: 21px;
  margin-top: calc(var(--section-gap) * 0.5);
}
.seal {
  position: relative;
  width: 100px;
  height: 100px;
  flex: none;
}
.seal__ring {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
  animation: seal-spin 10s linear infinite;
}
.seal__ring text {
  font-family: var(--font-head);
  font-size: 24px;            /* user units, not px on screen - see above */
  font-weight: 400;
  letter-spacing: normal;
  text-transform: uppercase;
  fill: var(--brand);
}
.seal__initial {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-family: var(--font-head);
  font-size: 44px;            /* MEASURED */
  font-weight: 400;
  line-height: 1;
  letter-spacing: normal;
  text-transform: uppercase;
  color: var(--brand);
}

@keyframes seal-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* The seal is decoration. Stop it turning, keep it visible. */
@media (prefers-reduced-motion: reduce) {
  .seal__ring { animation: none; }
}
