/* ==========================================================================
   NeeDoh — theme-authored CSS.

   READ THIS BEFORE ADDING A RULE.

   This file exists because bin/port-css.py OVERWRITES global.css, pages.css and
   every page-*.css on each run. Anything hand-written in those files is destroyed
   the next time the source is re-ported, silently and with no diff to notice.
   So: ported CSS lives there, theme-authored CSS lives here, and the two never mix.

   Only three kinds of rule belong here:

     1. Screens the static build has no equivalent of — the 404 page, the blog
        index, single posts, archives.
     2. WordPress and plugin output the source never had — CF7 form controls,
        wp-block-* classes, admin-bar offsets.
     3. Genuinely new components, added with approval and listed for the client.

   What must NOT go here: a rule that restyles something the port already covers.
   Two defects on a previous build came from exactly that — a small-caps treatment
   applied to a stat label the source renders as plain body text, and a logo height
   of 34px overriding a ported 42px. If the component came across in the port, it
   is already styled. Check global.css and pages.css first.

   Loaded after global.css, so these rules win on equal specificity.
   ========================================================================== */

/* ── 404 ──────────────────────────────────────────────────────────────────
   The static build has no 404 page, so this is theme-authored. It reuses the
   ported primitives (.wrap, .section-pad, .eyebrow, .display, .btn, .btn-row)
   and adds only the layout those primitives do not cover. */

.error-404 {
  text-align: center;
}

.error-404 .eyebrow {
  display: block;
  margin-bottom: 18px;
}

.error-404-title {
  /* Matches the h1.display scale from the ported type system rather than
     restating a size: see .display in pages.css. */
  margin: 0 auto 24px;
  max-width: 18ch;
}

.error-404-lede {
  margin: 0 auto 36px;
  max-width: 52ch;
  font-size: 18px;
  line-height: 1.55;
  color: var(--ink);
}

.error-404-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 22px;
}

/* The source's mobile convention: buttons go full width and pill-plus-underline
   rows stack. Both are already in the ported mobile block, which keys off .btn
   and .btn-row, so this only needs to centre what is left. */
@media (max-width: 640px) {
  .error-404-actions {
    flex-direction: column;
    align-items: stretch;
    gap: 14px;
  }
}

/* ── Skip link ────────────────────────────────────────────────────────────
   Theme-authored. The static build's skip link existed on one page of eight,
   was positioned off-screen with an inline `left:-9999px`, had no :focus rule
   anywhere, and its class appeared in no stylesheet — so it could never be
   brought back on screen and was permanently unreachable. This one is real. */

.skip-nav {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 10000;
  padding: 12px 20px;
  background: var(--ink);
  color: #fff;
  font: 700 13px/1 "Helvetica Neue", Helvetica, Arial, sans-serif;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  border-radius: 0 0 var(--radius-sm) 0;
}

/* The half that was missing: bring it back on screen when focused. */
.skip-nav:focus,
.skip-nav:focus-visible {
  left: 0;
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

/* ── Header logo ──────────────────────────────────────────────────────────
   The source sets these inline on the <img> and its wrapper. Moved into named
   rules so the markup carries no presentational attributes:
     .nav-logo-link  replaces  text-decoration:none; display:inline-flex; align-items:center
     .nav-logo       replaces  height:56px; width:auto; display:block          */

.nav-logo-link {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}

/* SPECIFICITY IS LOAD-BEARING HERE, do not simplify these selectors.
   
   WooCommerce's own stylesheet carries `.woocommerce img, .woocommerce-page img
   { height: auto }` at (0,1,1), which beats a bare `.nav-logo` at (0,1,0). The
   cart, checkout and account pages put `woocommerce-page` on <body> AND load
   woocommerce-layout.css, so the header logo rendered at its natural 1200x1038
   — 700px wide, shoving the whole nav into a corner. The shop and product pages
   were fine because the theme dequeues that stylesheet there, which is why this
   only ever showed on three pages nobody had opened.
   
   `.nav-logo-link .nav-logo` is (0,2,0) and wins cleanly, rather than tying at
   (0,1,1) and depending on load order. */
.nav-logo,
.nav-logo-link .nav-logo {
  height: 56px;
  width: auto;
  display: block;
}

@media (max-width: 640px) {
  .nav-logo,
  .nav-logo-link .nav-logo { height: 36px; }
}

/* ── Interior page hero ───────────────────────────────────────────────────
   .page-hero, .hero-split and the tile stack are all ported. These two are the
   inline values the source puts on the button row and the quieter second
   paragraph, moved into named rules:
     .hero-actions  replaces  margin-top:28px; display:flex; gap:12px; flex-wrap:wrap
     .hero-body     replaces  max-width:44ch; margin-top:14px                   */

.hero-actions {
  margin-top: 28px;
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}

.hero-body {
  max-width: 44ch;
  margin-top: 14px;
}

/* Source mobile convention: buttons go full width and pill rows stack. */
@media (max-width: 640px) {
  .hero-actions {
    flex-direction: column;
    align-items: stretch;
  }
}

/* ── WordPress core output the static build never had ─────────────────────
   Everything from here down exists because the theme renders three screens the
   source has no equivalent of — the blog index, an archive and a single post —
   plus the markup WordPress itself emits inside them. None of it restyles a
   ported component. */

/* .screen-reader-text is emitted by WordPress core, not by a template: every
   the_posts_pagination() call wraps its links in
   <h2 class="screen-reader-text">Posts navigation</h2>. No stylesheet in this
   theme defined the class, so that heading rendered VISIBLY — a stray heading in
   the middle of the page on every paginated index. This is core's own rule. */
.screen-reader-text {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Pagination. Keyed to .navigation.pagination, which core emits regardless of
   the arguments passed, rather than to a class the template hands in. */
.navigation.pagination {
  margin-top: 48px;
}

.navigation.pagination .nav-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px;
}

.navigation.pagination .page-numbers {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 46px;
  height: 46px;
  padding: 0 16px;
  border: 1.5px solid var(--rule);
  border-radius: var(--radius-pill);
  background: var(--paper);
  color: var(--ink);
  font: 700 14px/1 "Helvetica Neue", Helvetica, Arial, sans-serif;
  text-decoration: none;
  transition: border-color 180ms, background 180ms, color 180ms;
}

.navigation.pagination a.page-numbers:hover {
  border-color: var(--ink);
}

.navigation.pagination .page-numbers.current {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--accent);
}

.navigation.pagination .page-numbers.dots {
  border-color: transparent;
  background: transparent;
}

/* ── Images inside ported tiles ───────────────────────────────────────────
   The source's article cards and news hero tile are flat colour — it has no
   photography in either — so neither has a rule for a child image. WordPress
   featured images and the ACF hero tile image need one. Same shape as the
   ported `.tile img.tile-img` in pages.css, applied to the two containers that
   already carry position: relative. */
.article-thumb-img,
.news-hero-tile-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* The card title is a link here; in the source it was plain text inside a card
   that was itself one big anchor. Without this the title carries the browser's
   default underline, which the card design has nowhere else. */
.article-title a {
  color: inherit;
  text-decoration: none;
}

.article-title a:hover {
  /* --accent-deep, not --accent: 5.5:1 on paper, where --accent is 3.2:1. */
  color: var(--accent-deep);
}

/* ── Single post ──────────────────────────────────────────────────────────
   The featured image frame. blog-featured is 1200x675, so the intrinsic ratio
   sizes the box and no aspect-ratio rule is needed. */
.post-featured {
  margin-top: 56px;
  border-radius: 32px;
  overflow: hidden;
  box-shadow: 0 22px 44px -16px rgba(35, 31, 32, 0.28);
}

.post-featured img {
  display: block;
  width: 100%;
  height: auto;
}

/* Post and page body copy.
   This replaces `prose prose-lg`, which required the Tailwind Typography plugin
   that is not installed — so every page and post body rendered with browser
   defaults and nothing said so. The editor emits bare h2/h3/p/ul/blockquote with
   no classes, so these are element rules scoped to the container. */
.post-body {
  font-size: 17px;
  line-height: 1.7;
  color: var(--ink);
}

.post-body > *:first-child {
  margin-top: 0;
}

.post-body p {
  margin: 0 0 22px;
}

.post-body h2 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(26px, 3vw, 36px);
  line-height: 1.15;
  margin: 48px 0 16px;
}

.post-body h3 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(21px, 2.2vw, 27px);
  line-height: 1.2;
  margin: 36px 0 14px;
}

.post-body h4 {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 18px;
  margin: 30px 0 12px;
}

.post-body a {
  /* --accent-deep clears 4.5:1 on paper; --accent does not. */
  color: var(--accent-deep);
  text-decoration: underline;
}

.post-body a:hover {
  color: var(--ink);
}

.post-body ul,
.post-body ol {
  margin: 0 0 22px;
  padding-left: 24px;
}

.post-body li {
  margin-bottom: 8px;
}

.post-body blockquote {
  margin: 32px 0;
  padding: 4px 0 4px 24px;
  border-left: 3px solid var(--accent);
  font-size: 19px;
  color: var(--ink-soft);
}

.post-body figure {
  margin: 32px 0;
}

.post-body img {
  height: auto;
  border-radius: var(--radius-lg);
}

.post-body figcaption {
  margin-top: 10px;
  font-size: 13.5px;
  color: var(--ink-mute);
}

.post-body hr {
  margin: 40px 0;
  border: 0;
  border-top: 1px solid var(--rule);
}

.post-body table {
  width: 100%;
  margin: 0 0 22px;
  border-collapse: collapse;
}

.post-body th,
.post-body td {
  padding: 10px 12px;
  border: 1px solid var(--rule);
  text-align: left;
}

.post-body pre {
  margin: 0 0 22px;
  padding: 18px;
  border-radius: var(--radius-md);
  background: var(--cream-2);
  overflow-x: auto;
}

.post-body code {
  padding: 2px 6px;
  border-radius: 6px;
  background: var(--cream-2);
  font-size: 0.92em;
}

.post-body pre code {
  padding: 0;
  background: none;
}

/* The share row under a post. The pills themselves are the ported .chip; this
   is only the rule and spacing that separate the row from the article. */
.post-share {
  margin-top: 56px;
  padding-top: 32px;
  border-top: 1px solid var(--rule);
}

.post-share .eyebrow {
  display: block;
  margin-bottom: 16px;
}

/* One share control is a <button>, because assets/js/global.js binds the
   clipboard copy to .share-copy-link and swaps the label text. A button needs
   the pointer that an anchor gets for free. */
.share-copy-link {
  cursor: pointer;
}

.share-copy-label {
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.02em;
}

/* ── .btn-row desktop layout ──────────────────────────────────────────────
   The ported global.css defines .btn-row ONLY inside @media (max-width: 640px),
   so above that breakpoint the class carried no layout at all. It worked in the
   static build because every instance also sets the desktop values INLINE:

     style="display:inline-flex;flex-wrap:wrap;gap:18px;align-items:center"

   Six instances across the home page and Where to Buy, identical every time.
   Moved here so the markup can emit a clean class, which is the documented way
   to handle a component the source styles entirely inline. Values are copied
   exactly — this is not a redesign.

   The mobile block in global.css uses !important and still wins at 640px and
   under, where the row stacks and buttons go full width. */

.btn-row {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 18px;
  align-items: center;
}

/* ── Counterfeit warning band ─────────────────────────────────────────────
   Shared by Home and Where to Buy, identical markup on both.

   The source styles this band almost entirely with inline styles on unclassed
   divs. Every value below is copied from it, not redesigned:

     .counterfeit-band       background:#f7f8f9; color:var(--ink); position:relative
     .counterfeit-grid       display:grid; grid-template-columns:1fr 1fr;
                             gap:80px; align-items:center
     .counterfeit-visual     aspect-ratio:1/1; max-width:520px; border-radius:50px;
                             overflow:hidden; justify-self:center; plus its shadow
     .counterfeit-badge      display:inline-flex; align-items:center; gap:14px;
                             margin-bottom:24px
     .counterfeit-badge-icon 44px circle, rgba(255,64,180,0.12) on var(--accent)

   The stacking breakpoint is 880px, matching the source's own `.split` behaviour
   rather than a new number. */

.counterfeit-band {
  position: relative;
  background: #f7f8f9;
  color: var(--ink);
}

.counterfeit-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 80px;
  align-items: center;
}

.counterfeit-visual {
  position: relative;
  aspect-ratio: 1 / 1;
  width: 100%;
  max-width: 520px;
  border-radius: 50px;
  overflow: hidden;
  justify-self: center;
  box-shadow: 0 20px 60px -15px rgba(255, 232, 0, 0.35);
}

.counterfeit-visual img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

.counterfeit-badge {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  margin-bottom: 24px;
}

.counterfeit-badge .eyebrow {
  margin: 0;
}

.counterfeit-badge-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 999px;
  background: rgba(255, 64, 180, 0.12);
  color: var(--accent);
  flex-shrink: 0;
}

@media (max-width: 880px) {
  .counterfeit-grid {
    grid-template-columns: 1fr;
    gap: 36px;
  }
  .counterfeit-visual {
    max-width: 380px;
  }
}

/* ── Where to Buy: the retailer directory ─────────────────────────────────
   The source styles this band with inline styles on unclassed divs, and puts
   inline styles on .dealer-dir-retailers even though the class exists. Values
   below are copied verbatim; each line says which inline declaration it replaces.

     .dealer-dir-head       text-align:center; margin-bottom:36px
     .dealer-dir-head .eyebrow   margin-bottom:18px; color:var(--accent)
     .dealer-dir-head .display   margin:0 auto 20px; max-width:22ch
     .dealer-dir-lede       font-size:17px; line-height:1.55;
                            color:var(--ink-soft); max-width:60ch; margin:0 auto 32px
     .dealer-dir-retailers  display:flex; gap:14px; justify-content:center;
                            flex-wrap:wrap                                        */

.dealer-dir-head {
  text-align: center;
  margin-bottom: 36px;
}

.dealer-dir-head .eyebrow {
  margin-bottom: 18px;
  color: var(--accent);
}

.dealer-dir-head .display {
  margin: 0 auto 20px;
  max-width: 22ch;
}

.dealer-dir-lede {
  font-size: 17px;
  line-height: 1.55;
  color: var(--ink-soft);
  max-width: 60ch;
  margin: 0 auto 32px;
}

.dealer-dir-retailers {
  display: flex;
  gap: 14px;
  justify-content: center;
  flex-wrap: wrap;
}

/* The count line's secondary half — the source's inline
   font-size:12px; color:var(--ink-mute) on an unclassed span. */
.dealer-dir-hint {
  font-size: 12px;
  color: var(--ink-mute);
}

/* The note's title. The source now renders this as a block <strong> in the
   DISPLAY face; when this theme was first ported the notes had no title at
   all, so the class was hand-authored with body-font values and a heading that
   the seeder never populated. Values below are the source's own.
   An h3 rather than a <strong> because it is a real heading, and that costs
   nothing here: it sits under the section's h2, so the outline stays ordered. */
.dealer-dir-note-title {
  display: block;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 17px;
  /* INHERIT. Same retag, same trap as .spot-check-title: the source's element
     is a <strong> taking the body's 1.55 (26.35px here), and `line-height: 1.3`
     was written to match a heading scale the source never applied to it. Each
     note came out 4.25px short. When a retag is for the OUTLINE, the type has to
     go on unchanged. */
  line-height: inherit;
  color: var(--ink);
  margin: 0 0 4px;
}

/* The body flows as one paragraph with its mailto linked inside the sentence. */
.dealer-dir-note p {
  margin: 0;
}

.dealer-dir-note a {
  color: var(--ink);
  text-decoration: none;
  border-bottom: 1.5px solid var(--accent);
  padding-bottom: 1px;
  font-weight: 600;
}

.dealer-dir-note a:hover,
.dealer-dir-note a:focus-visible {
  color: var(--accent);
}

/* Shown before a search has been run. The source leaves the results container
   empty, which reads as a broken grid. */
.dealer-dir-empty {
  font-size: 15px;
  line-height: 1.6;
  color: var(--ink-mute);
  margin: 0;
}

/* ── Where to Buy: spot a real one ────────────────────────────────────────
   Both columns are unclassed in the source.

     .spot-real-copy      the left column, no styles of its own
     .spot-real-body      font-size:19px; line-height:1.5; max-width:36ch
     .spot-real-btn       background:#fff; color:var(--ink)   (on a .btn)
     .spot-check-title    theme-authored: the source emphasised the title inside
                          the paragraph rather than giving it an element         */

.spot-real-copy {
  min-width: 0;
}

.spot-real-body {
  font-size: 19px;
  line-height: 1.5;
  max-width: 36ch;
}

.spot-real-btn {
  background: #fff;
  color: var(--ink);
}

.spot-check-title {
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 16px;
  line-height: 1.3;
  margin: 0 0 6px;
}

/* ── Contact: the routing list label ──────────────────────────────────────
   Theme-authored. The source runs the label and the value together inside one
   <li>, so there is nothing to hang a rule on; splitting them lets the label
   read as a label to a screen reader as well as visually. */

.inbox-label {
  display: block;
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: currentColor;
  opacity: 0.75;
}

/* ── Home hero: lockup and headline ───────────────────────────────────────
   Both carry their styling inline in the source. Values copied verbatim:

     .hero-lockup        display:inline-flex; align-items:center; gap:12px;
                         margin:0 0 32px; text-decoration:none;
                         filter:drop-shadow(0 8px 18px rgba(0,0,0,0.18));
                         transition:opacity 200ms ease
     .hero-lockup-label  font-family:var(--font-body); font-size:11.5px;
                         font-weight:700; letter-spacing:0.16em;
                         text-transform:uppercase; color:#fff; line-height:1
     .hero-h1            margin:0 0 28px; font-size:clamp(36px,5.2vw,76px);
                         line-height:1.1; max-width:14ch; color:#fff;
                         word-break:normal; overflow-wrap:normal; hyphens:none

   ONE CORRECTION: the source achieves the lockup's hover with inline
   onmouseover / onmouseout attributes that assign style.opacity. That is a real
   :hover rule here — inline event handlers do not work for keyboard focus, and
   they are the kind of thing a strict Content-Security-Policy blocks. :focus-visible
   is included, which the inline version had no equivalent of at all. */

.hero-lockup {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 32px;
  text-decoration: none;
  filter: drop-shadow(0 8px 18px rgba(0, 0, 0, 0.18));
  transition: opacity 200ms ease;
}

.hero-lockup:hover,
.hero-lockup:focus-visible {
  opacity: 0.85;
}

.hero-lockup-label {
  font-family: var(--font-body);
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #fff;
  line-height: 1;
}

.hero-lockup-logo {
  height: 28px;
  width: auto;
  display: block;
}

.hero-h1 {
  margin: 0 0 28px;
  font-size: clamp(36px, 5.2vw, 76px);
  line-height: 1.1;
  max-width: 14ch;
  color: #fff;
  /* The source is explicit about all three: the headline must not hyphenate or
     break mid-word, because it is split per character for the reveal animation. */
  word-break: normal;
  overflow-wrap: normal;
  hyphens: none;
}

/* ── Category tile ───────────────────────────────────────────────────────
   ONE component, used by the home page's featured-categories band and the
   WooCommerce catalog. The tiles are unclassed in the source, so these are its
   inline values moved into named classes:

     .category-tile        text-decoration:none; display:flex;
                           flex-direction:column; gap:16px
     .category-tile-thumb  position:relative; aspect-ratio:1/1;
                           border-radius:32px; overflow:hidden;
                           box-shadow:0 14px 32px -16px rgba(35,31,32,0.22);
                           border:3px solid #EAEAEA
     .category-tile-label  the source's .cat-card-name - emitted alongside it, so
                           the source's own hover rule in injected.css applies -
                           font-family:var(--font-display); font-weight:400;
                           font-size:24px; line-height:1.15; color:var(--ink)
     .category-tile-desc   font-size:14.5px; line-height:1.5

   The name sits BELOW the image, not over it. An earlier port of this theme
   overlaid the name on the thumbnail behind a scrim, with an `.is-light`
   variant flipping it to ink on pale tiles. The source does not do that, so
   the scrim, the variant and the `cat_tile_is_light` field it read have all
   been removed rather than left as dead code that reads as a feature. */

.category-tile {
  display: flex;
  flex-direction: column;
  gap: 16px;
  text-decoration: none;
  color: inherit;
}

.category-tile-thumb {
  position: relative;
  display: block;
  aspect-ratio: 1 / 1;
  border-radius: 32px;
  overflow: hidden;
  box-shadow: 0 14px 32px -16px rgba(35, 31, 32, 0.22);
  border: 3px solid #eaeaea;
}

.category-tile-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  z-index: 1;
}

.category-tile-label {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 24px;
  line-height: 1.15;
  margin: 0;
  color: var(--ink);
}

.category-tile-desc {
  font-size: 14.5px;
  line-height: 1.5;
  margin: 0;
  color: var(--ink-soft);
}

.category-tile:hover .category-tile-thumb {
  box-shadow: 0 22px 44px -18px rgba(35, 31, 32, 0.3);
}

/* ── Home page: elements the source styles inline ─────────────────────────
   Every value here is copied from the rendered home page. The classes are new
   only because the source left these elements unclassed; the declarations are
   its own. Each block names the inline style it replaces.

   Two colour decisions differ, and both are deliberate. Where the source
   hardcodes a text colour that only suits one band's background (`color:#fff` on
   the best-loved lede, `color:var(--paper)` on the audience card title), the rule
   here inherits instead. Those bands set their colour on the section or, for the
   audience cards, per card from a field — so a hardcoded value contradicts the
   field and breaks the two light cards. Inheriting renders identically to the
   source on every card the source actually has. */

/* .hero-lede — font-size:18px; line-height:1.55; color:rgb(255,255,255);
                max-width:54ch; margin:0 0 32px */
.hero-lede {
  font-size: 18px;
  line-height: 1.55;
  color: #fff;
  max-width: 54ch;
  margin: 0 0 32px;
}

/* .hero-cta — the source puts no extra styling on this beyond .btn.btn-primary;
   the class exists as a hook for the reveal script's selector list. Declared so
   it resolves, with nothing that would override .btn-primary. */
.hero-cta {
  position: relative;
}

/* ── #story ───────────────────────────────────────────────────────────────
   .story-band  padding-top:96px; padding-bottom:96px; position:relative;
                background:#FFFFFF; color:var(--ink)
   .story-body  the right column's padding-top:12px, plus the paragraph rhythm
                the source sets on each <p>: font-size:18px; margin:0 0 24px  */
.story-band {
  padding-top: 96px;
  padding-bottom: 96px;
  position: relative;
  background: #ffffff;
  color: var(--ink);
}

.story-body {
  padding-top: 12px;
}

.story-body p {
  font-size: 18px;
  color: var(--ink);
  margin: 0 0 24px;
}

.story-body p:last-child {
  margin-bottom: 0;
}

/* ── The stat strip ───────────────────────────────────────────────────────
   .stat-strip carries `display:none` in the source and in the ported
   page-home.css, because someone chose to hide it. `.is-shown` is the opt-in the
   ACF toggle emits — the strip is not deleted, and turning it on actually shows
   it rather than fighting an inline style.

   .stat        display:flex; flex-direction:column; gap:4px
   .stat-value  font-family:var(--font-display); font-size:56px; line-height:1
   .stat-label  font-size:13px; color:var(--ink-mute); letter-spacing:0.04em;
                text-transform:uppercase                                        */
.stat-strip.is-shown {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 32px;
  border-top: 1px solid rgba(35, 31, 32, 0.12);
  border-bottom: 1px solid rgba(35, 31, 32, 0.12);
  padding: 36px 0;
  margin-top: 56px;
}

.stat {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.stat-value {
  font-family: var(--font-display);
  font-size: 56px;
  line-height: 1;
  color: var(--ink);
}

.stat-label {
  font-size: 13px;
  color: var(--ink-mute);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

@media (max-width: 880px) {
  .stat-strip.is-shown { grid-template-columns: repeat(2, 1fr); }
  .stat-value { font-size: 40px; }
}

/* ── Band head / lede / foot ──────────────────────────────────────────────
   The centred heading block above a grid, and the button below it. The source
   repeats these three inline on every band:

   .band-head         text-align:center; margin-bottom:56px
   .band-head-center  display:flex; flex-direction:column; align-items:center;
                      text-align:center; margin-bottom:64px; position:relative
   .band-lede         font-size:18px; line-height:1.55; max-width:54ch;
                      margin:0 0 32px   (colour inherited — see the note above)
   .band-foot         text-align:center; margin-top:56px                        */
.band-head {
  text-align: center;
  margin-bottom: 56px;
}

/* The source sets each of these three inline on the element, so the port
   brought across none of them and every band heading sat flush against its
   lede. The values are its own: eyebrow 0 0 18px, heading 0 0 24px, lede
   centred with `margin:0 auto`. The lede's own bottom margin is dropped
   here because .band-head already carries the 56px gap to what follows. */
.band-head .eyebrow {
  margin: 0 0 18px;
}

.band-head .display {
  margin: 0 0 24px;
}

.band-head .band-lede {
  margin: 0 auto;
  max-width: 62ch;
}

.band-head-center {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  margin-bottom: 64px;
  position: relative;
}

.band-lede {
  font-size: 18px;
  line-height: 1.55;
  max-width: 54ch;
  margin: 0 auto 32px;
  color: inherit;
}

.band-foot {
  text-align: center;
  margin-top: 56px;
}

/* ── #what-is-needoh ──────────────────────────────────────────────────────
   .what-is-needoh-band  background:var(--ink); position:relative; padding:120px 0;
                    overflow:hidden
   .origin-visual   position:relative; aspect-ratio:1/1; max-width:520px;
                    width:100%; border-radius:50px; overflow:hidden;
                    align-self:center;
                    box-shadow:0 20px 60px -15px rgba(255,114,118,0.3)
                    (the background colour is per-page data, so it stays inline)
   .origin-copy     the right column, unstyled in the source                    */
.what-is-needoh-band {
  background: var(--ink);
  position: relative;
  padding: 120px 0;
  overflow: hidden;
}

.origin-visual {
  position: relative;
  aspect-ratio: 1 / 1;
  max-width: 520px;
  width: 100%;
  border-radius: 50px;
  overflow: hidden;
  align-self: center;
  box-shadow: 0 20px 60px -15px rgba(255, 114, 118, 0.3);
}

.origin-visual img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

.origin-copy {
  min-width: 0;
}

.origin-copy p {
  font-size: 18px;
  color: #fff;
  margin: 0 0 24px;
}

.origin-copy p:last-child {
  margin-bottom: 0;
}

/* ── Audience cards ───────────────────────────────────────────────────────
   .audience-tag-row  margin-bottom:18px
   .audience-tag      font-family:var(--font-body); font-size:12px;
                      letter-spacing:0.18em; text-transform:uppercase;
                      opacity:0.75
   .audience-title    margin:0 0 18px; font-size:clamp(28px,3vw,40px);
                      line-height:1.1; max-width:14ch
   .audience-copy     font-size:15.5px; opacity:0.92; max-width:40ch; margin:0

   Both title and copy inherit their colour. The source hardcodes
   `color:var(--paper)` on two cards and `color:var(--ink)` on the other two; the
   card's own colour now comes from a field, so inheriting is what keeps the pale
   cards readable. */
.audience-tag-row {
  margin-bottom: 18px;
}

.audience-tag {
  font-family: var(--font-body);
  font-size: 12px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  opacity: 0.75;
}

.audience-title {
  margin: 0 0 18px;
  font-size: clamp(28px, 3vw, 40px);
  line-height: 1.1;
  max-width: 14ch;
  color: inherit;
}

.audience-copy {
  font-size: 15.5px;
  opacity: 0.92;
  max-width: 40ch;
  margin: 0;
  color: inherit;
}

/* ── Placeholder tiles that now hold real images ──────────────────────────
   .company-tile, .made-tile and .qa-tile are flat-colour placeholders in the
   static build — no photography was supplied, so none of them has a rule for a
   child image, and two lack the positioning an absolutely-placed image needs.
   All three are ACF image fields now, so setting one rendered an unpositioned,
   uncropped image.

   .origin-tile already has both and is the precedent followed here; the shape is
   the same as the .article-thumb-img / .news-hero-tile-img block above. */

/* Scoped with :has(img), so a tile that is still an empty colour block keeps
   the source's own `position: static; overflow: visible`. Applied
   unconditionally it made twelve computed differences per run on the About page
   that could not move a pixel — `position:relative` with `inset:0` on an empty
   div offsets it by zero — and a guard reporting a dozen inert differences
   every run is how you learn to skim past a real one. */
.company-tile:has(img),
.made-tile:has(img) {
  position: relative;
  overflow: hidden;
}

.company-tile img,
.made-tile img,
.qa-tile img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* The Real Thing band is dark, and the source sets its eyebrow's colour inline
   as rgba(255,255,255,0.85). The ported .story-dark .eyebrow rule is the exact
   precedent for this shape. */
.real-thing-band .eyebrow {
  color: rgba(255, 255, 255, 0.85);
}

/* The band's copy column and its button row, both inline in the source:
     .real-thing-copy  max-width:56ch; position:relative; z-index:2
     .real-thing-actions  margin-top:28px                                    */
.real-thing-copy {
  max-width: 56ch;
  position: relative;
  z-index: 2;
}

.real-thing-actions {
  margin-top: 28px;
}

/* ── Home hero band ───────────────────────────────────────────────────────
   The source puts this ENTIRE declaration inline on #real-needoh:

     position:relative;
     background: linear-gradient(to right,
                   rgba(63,171,225,0.95) 0%, rgba(63,171,225,0.9) 40%,
                   rgba(63,171,225,0.6) 65%, rgba(63,171,225,0) 90%),
                 url('...hero-image.jpg') 10% center/cover no-repeat var(--cream);
     padding:150px 0 0;
     overflow:hidden

   An earlier version of front-page.php emitted only `--hero-bg: url(...)` and
   NO RULE CONSUMED IT. The hero therefore had no background at all, and since its
   heading and lede are white, the entire hero rendered as blank white space with a
   single button floating in it. Nothing caught this: it is an inline style, so
   check-parity compares no class, and check-classes sees no token.

   The image URL stays a custom property because it is per-page data. Everything
   else is here, split into longhands so the gradient and the photograph can carry
   their own position and size. */

#real-needoh {
  position: relative;
  padding: 150px 0 0;
  overflow: hidden;
  background-color: var(--cream);
  background-image:
    linear-gradient(to right,
      rgba(63, 171, 225, 0.95) 0%,
      rgba(63, 171, 225, 0.9) 40%,
      rgba(63, 171, 225, 0.6) 65%,
      rgba(63, 171, 225, 0) 90%),
    var(--hero-bg, none);
  background-position: 0 0, 10% center;
  background-size: auto, cover;
  background-repeat: no-repeat, no-repeat;
}

/* Without a photograph the gradient alone must still carry white text, so the
   band stays fully saturated rather than fading to nothing on the right. */
#real-needoh:not([style*="--hero-bg"]) {
  background-image: linear-gradient(to right,
    rgba(63, 171, 225, 0.95) 0%, rgba(63, 171, 225, 0.9) 100%);
}

/* ── Base layout the source sets INLINE ──────────────────────────────────
   The static build declares each of these grids and cards in a style
   attribute on the element and ships only the RESPONSIVE OVERRIDES in a
   stylesheet. Porting the stylesheets therefore brought across the media
   queries and none of the base layout they override, so every one of these
   containers computed to `display: block` on the built site: the featured
   categories stacked one per row at full width, and the closing CTA card
   grew from 446px to 1330px tall.

   Nothing failed. The markup was right, the classes were right, the CSS
   file was present and loaded, and the media queries inside it were
   correct — they were simply overriding a base rule that did not exist.
   check-parity.py compares class tokens, so it passed; the defect is only
   visible as computed style, which is what bin/check-computed.mjs now
   measures.

   Values are the source's own, taken from bin/capture-source.py output. */

.plan-best-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 28px;
}

.plan-categories-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 28px;
}

.hero-cta-row {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 18px;
  margin: 0;
}

/* The hero grid's gap to the marquee. The stylesheet's mobile rule overrides
   this to 56px !important, which is what made its absence so quiet: the
   spacing was correct at narrow widths and wrong at every other. */
#real-needoh .real-needoh-grid {
  margin-bottom: 60px;
}

.btn-row {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 18px;
}

/* The closing CTA card: a two-column panel, image one side, copy the other. */
.plan-closing-card {
  /* The source's inline declaration is
     `background:var(--ink);color:#fff;border-radius:48px;overflow:hidden;
      box-shadow:...`. The first port took the last three and dropped the first
     two, so the card rendered transparent with ink text on all six pages that
     carry it — a dark panel showing as white-on-white, which is why the band
     read as empty space rather than as a broken colour. Found by adding
     backgroundColor and color to bin/check-computed.mjs; no text or class guard
     can see a colour. */
  background: var(--ink);
  color: #fff;
  border-radius: 48px;
  overflow: hidden;
  box-shadow: 0 28px 60px -24px rgba(35, 31, 32, 0.35);
}

.plan-closing-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0;
  align-items: stretch;
}

.plan-closing-img {
  position: relative;
  min-height: 100%;
}

.plan-closing-copy {
  padding: 72px;
}

/* ── Dark brand-pattern bands ────────────────────────────────────────────
   Two sections sit on ink with the NeeDoh wave pattern washed behind them.
   The pattern is decorative: it is an empty div in the source too, marked
   aria-hidden, carrying the artwork as a background image so it never
   reaches the accessibility tree or needs alt text.

   The `.wrap` inside each band is position:relative;z-index:2 so the copy
   stacks above both the pattern and, on #what-is-needoh, the fade that
   darkens the right-hand side behind the text.

   Asset paths are theme-relative; this file lives in assets/css/. */

#plan-best-loved {
  position: relative;
  overflow: hidden;
  background: var(--ink);
  color: #fff;
  /* The source's own -1px, closing the hairline seam against the section
     above it that some browsers render at fractional zoom levels. */
  margin-top: -1px;
}

.plan-best-loved-pattern {
  position: absolute;
  inset: 0;
  z-index: 1;
  background-image: url('../images/branding-patterns/needoh-branding-pattern-2.svg');
  background-size: cover;
  background-position: center;
  opacity: 0.05;
  pointer-events: none;
}

.what-is-needoh-pattern {
  position: absolute;
  inset: 0;
  z-index: 0;
  background-image: url('../images/branding-patterns/needoh-branding-pattern-2.svg');
  background-size: cover;
  background-position: center left;
  opacity: 0.07;
  pointer-events: none;
}

.what-is-needoh-fade {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(to right,
    rgba(35, 31, 32, 0) 0%,
    rgba(35, 31, 32, 0) 38%,
    var(--ink) 68%);
  pointer-events: none;
}

#plan-best-loved > .wrap,
.what-is-needoh-band > .wrap {
  position: relative;
  z-index: 2;
}

/* ── Accent word highlight ───────────────────────────────────────────────
   The source marks brand words inside body copy with an inline
   `<span style="color:var(--accent)">NeeDoh®</span>`. bin/seed-content.py
   rewrites that to `<span class="accent">` on the way into the editor, so a
   hardcoded hex never lands in content a client edits — and then nothing
   defined the class. Every accent word on the site rendered in the
   surrounding text colour: invisible as a highlight, and pure white against
   the dark bands.

   Half a mechanism reads exactly like a working one from either end. The
   seeder's comment says the colour comes from CSS; the CSS did not have it. */
.accent {
  color: var(--accent);
}

/* ── Section eyebrows ────────────────────────────────────────────────────
   The base `.eyebrow` is ink-soft, which is right on the pale pages. Every
   eyebrow on the home page is overridden to accent inline in the source, and
   so is the counterfeit band's wherever it appears — including Where to Buy.
   Scoped per section rather than globally, because About's dark story band
   uses a translucent white instead and a blanket rule would break it. */
#story .eyebrow,
#plan-best-loved .eyebrow,
#what-is-needoh .eyebrow,
#who .eyebrow,
#counterfeit-warning .eyebrow {
  color: var(--accent);
}

/* The band sits on ink, so its copy is white. The source sets #fff on the
   heading and on each paragraph individually; setting it once on the band
   covers both and the CTA, and is what made the missing rule so easy to
   miss — the paragraphs happened to inherit white from elsewhere, so only
   the H2 rendered dark-on-dark. A heading you cannot read is not a subtle
   defect, and it survived a screenshot review because the eye reads the
   band as intentionally empty space. */
.what-is-needoh-band {
  color: #fff;
}

.what-is-needoh-cta {
  background: var(--accent);
  color: #fff;
  transition: background 200ms ease, color 200ms ease;
}

.what-is-needoh-cta:hover,
.what-is-needoh-cta:focus-visible {
  background: #fff;
  color: var(--ink);
}

/* ── The two shared bands' typography ────────────────────────────────────
   #site-cta and #counterfeit-warning appear on nearly every page, so a
   missing declaration here is a difference on six of them at once — which
   is exactly how the computed guard surfaced this: h2.display differing in
   marginBottom, maxWidth, height, lineHeight and fontSize, everywhere.

   The source sets all of it inline on each element. The heading's own
   line-height and font-size are restated because the global h2.display uses
   1.12, and these two bands use 1.05 — an 4px difference per line that
   compounds into the 133px the closing card was too tall. */

#site-cta .plan-closing-copy .display {
  margin: 0 0 28px;
  max-width: 18ch;
  color: #fff;
  line-height: 1.05;
  font-size: clamp(32px, 4vw, 60px);
}

#site-cta .plan-closing-copy p {
  font-size: 18px;
  line-height: 1.6;
  color: #fff;
  max-width: 58ch;
  margin: 0 0 40px;
}

/* No font-size or line-height here. The source's inline style on this heading
   is only `margin:0 0 28px;max-width:22ch;color:var(--ink)`, so it inherits
   whichever h2.display the PAGE defines — clamp(32px,4vw,60px) on the home
   page, clamp(28px,3.6vw,52px) on the internal ones. Restating them made the
   heading 57.6px everywhere, which is right on Home and 5.8px too big on the
   six internal pages, where it also changed how the 22ch measure resolved. */
#counterfeit-warning .display {
  margin: 0 0 28px;
  max-width: 22ch;
  color: var(--ink);
}

#counterfeit-warning p {
  font-size: 18px;
  line-height: 1.6;
  color: var(--ink);
  max-width: 58ch;
  margin: 0 0 18px;
}

/* The band's second paragraph is the quieter legal line: smaller, softer,
   and carrying the gap to the button row. */
#counterfeit-warning p + p {
  font-size: 16px;
  color: rgba(35, 31, 32, 0.6);
  max-width: 60ch;
  margin: 0 0 40px;
}

/* ── Images that fill their tile ─────────────────────────────────────────
   The source sets `position:absolute; inset:0; width:100%; height:100%;
   object-fit:cover; object-position:center` inline on each of these, so the
   picture fills a box whose size is decided by something else.

   Without it the img sits in normal flow and its INTRINSIC ratio drives the
   container. That is only true here because WordPress emits real width and
   height attributes (which is correct, and required to stop layout shift) —
   so this rule is the other half of that decision, not a workaround for it.
   The closing CTA card came out 579px tall against the source's 446px on six
   pages at once, and the copy column measured identical on both, which is
   what pointed at the image column. */
.plan-closing-img > img,
.counterfeit-visual > img,
.origin-visual > img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* ── Retagged headings ───────────────────────────────────────────────────
   The theme changes several heading LEVELS on purpose, because the source's
   own outline is invalid: the where-to-buy cards ran H1 → H3 → H2, and the
   contact info blocks ran H1 → H3 with no H2 between. Both are WCAG 1.3.1
   failures and both are fixed here.

   What was NOT done is move the styling. Three template docblocks asserted
   "the styling lives on .buy-card / .info-block, not on the tag" — and the
   ported rules are `.buy-card h3` and `.info-block h3`. So each retagged
   heading matched no rule and rendered at 16px body type: the buy cards' 40px
   display headings collapsed to one small line, taking 62px off an 817px card.

   A decision recorded, half carried out, and a comment that made the other
   half look done. bin/check-dead-css.mjs exists because of this: it asks the
   browser which selectors match on the source page and not on ours, which is
   the only way a tag on the far side of a descendant combinator shows up.

   Values are the source's own, re-pointed at the tag the theme emits. The
   generated page-*.css keeps its h3 rules; they are harmless and regenerating
   from the source must not have to remember this. */

.buy-card h2 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: clamp(26px, 3vw, 40px);
  line-height: 1.1;
  margin: 0;
}

.buy-card.buy-direct h2,
.buy-card.buy-retail h2 {
  color: #fff;
}

.info-block h2 {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 22px;
  margin: 0 0 14px;
  color: inherit;
}

/* The source uses a block <strong> for both of these; the theme uses a real
   heading and a labelled span, which is what the markup means. */
.spot-check-title {
  display: block;
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 22px;
  /* INHERIT, not the heading scale. The source's element is a <strong>, which
     takes the body's 1.55 — 34.1px at this size. Retagged to an <h3> it picked
     up the heading rule's 1.3 instead, 28.6px, and each of the three cards came
     out 5.5px short. The retag is for the page outline; it is not meant to
     change the type. */
  line-height: inherit;
  color: #fff;
  margin: 0 0 6px;
}

.inbox-list li .inbox-label {
  color: #fff;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 12.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* ── News page: classes for the source's inline layout ───────────────────
   The index head is an unclassed <div> in the source carrying
   `display:flex;justify-content:space-between;align-items:flex-end;gap:24px;
   flex-wrap:wrap` inline, and the two paragraphs beside it carry their own
   max-widths the same way. Named here because the arrangement repeats and
   because an unclassed div cannot be checked by any guard in this repo. */

.news-index-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 24px;
  flex-wrap: wrap;
  margin-bottom: 40px;
}

.news-index-note {
  max-width: 36ch;
  margin: 0;
}

.news-hero-body {
  max-width: 52ch;
  margin-top: 14px;
}

/* The featured tile takes the same tone palette as the index cards, so the
   band and the card for one article agree on its colour. */
.featured-tile {
  position: relative;
  overflow: hidden;
}

@media (max-width: 720px) {
  .news-index-head {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* A draft card is a <div>, not an <a href="#">: it has no destination, so it
   must not be focusable or announce as a link. It still has to look and lay
   out exactly like the anchor version. */
.article-card.coming-soon {
  cursor: default;
}

/* ── Retailer directory filter row ───────────────────────────────────────
   The source puts the three controls straight into the grid. The theme wraps
   each one in a .form-group so its label has somewhere to live — the source
   labels them with aria-label only, and a real <label> is better — but
   .form-group carries `margin-bottom: 18px` for stacked forms, which made the
   row 75px tall against the source's 56px and pushed everything below it down.

   The label is .screen-reader-text and takes no space, so inside this grid the
   group is a pass-through. */
.dealer-dir-filters .form-group {
  margin-bottom: 0;
  gap: 0;
}

/* The directory's own heading is centred on its measure, which the source sets
   inline as `margin:0 auto 20px;max-width:22ch`. Without the measure it stayed
   on one line where the source wraps to two — 58px against 116px, and every
   box below it moved up by the difference. */
#retailer-directory .dealer-dir-head .display,
#retailer-directory > .wrap > .dealer-dir-head .display {
  margin: 0 auto 20px;
  max-width: 22ch;
}

/* ── Honeypot ────────────────────────────────────────────────────────────
   The spam trap on the contact form. Without this rule the field renders in
   full view, labelled "Leave this field empty", which is both obviously broken
   and worse than no honeypot: a real person reads the label, types nothing,
   and the only people who fill it are the ones who cannot see it.

   display:none, NOT off-screen positioning. `left:-9999px` hides a field
   visually while leaving it in the tab order and the accessibility tree, so a
   screen reader announces it and a keyboard user tabs into it — and a
   submission that trips the honeypot is usually rejected with no email, no
   error and no stored record, so the loss is invisible in all three places at
   once.

   READ THIS BEFORE CHANGING HOW IT IS HIDDEN. `display:none` is currently the
   ONLY thing keeping this field out of the tab order. The CF7 tag asks for
   `tabindex:-1`, and CF7 DROPS it — the rendered input has no tabindex
   attribute at all (checked in a browser: the attribute is null and .focus()
   leaves activeElement on <body>, which is display:none doing the work, not
   the tabindex). `autocomplete="off"` does survive, and the wrapper keeps its
   aria-hidden="true".

   So switching to off-screen positioning without first adding tabindex="-1"
   through a wpcf7_form_elements filter would put a labelled "Leave this field
   empty" input back in the tab order, where the only people who can reach it
   are the ones who cannot see it.

   The field is named `needoh-hp` on purpose. `website`, `company` and
   `address` are names a password manager recognises and will fill, which would
   silently bin the submission of anyone using one. */
.hp-wrap {
  display: none;
}

/* The hero lockup's leaves-this-site arrow. The source sets both values inline
   (`color:#fff;flex-shrink:0`); named here because the lockup sits on the dark
   hero and the arrow has to stay the same white as the label beside it. */
.hero-lockup-arrow {
  color: #fff;
  flex-shrink: 0;
}

/* ── Product gallery placeholder ─────────────────────────────────────────
   Shown when a product has no photography. The reference renders a flat tile
   in a per-product colour with the product name over it, and 15 of the 62
   imported products are in that state — so this is a normal state, not an
   error state, and it must not look like one.

   The colour comes through --placeholder-bg because it is per product; the
   gradient over it is the reference's own, and it exists to keep white text
   readable on whichever colour a product carries. */
.gallery-placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background:
    linear-gradient(135deg, rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.35)),
    var(--placeholder-bg, #f48220);
  color: #fff;
  font-family: var(--font-display);
  font-size: 22px;
  line-height: 1.2;
  text-align: center;
  padding: 32px;
}

/* ── Product archive ─────────────────────────────────────────────────────
   The reference declares all of this inline on /products/. Named here because
   the same card renders in three places (the shop page, each category archive,
   and the related-products row) and an inline copy in each would drift.

   Colours come through custom properties because they are per CATEGORY: the
   tile takes the category's colour and the card's hover accent matches it. */

.archive-hero .hero-content { max-width: 720px; }
.archive-hero h1.display { font-size: clamp(44px, 5.6vw, 84px); line-height: 1.02; }
.archive-hero .lede { font-size: 19px; line-height: 1.55; }

.cat-chip-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 28px;
}

.cat-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px;
  border: 1px solid transparent;
  border-radius: 999px;
  background: var(--chip-color, #3fabe1);
  color: #fff;
  text-decoration: none;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 13px;
  letter-spacing: 0.06em;
  transition: transform 200ms cubic-bezier(.4, 0, .2, 1);
}

.cat-chip:hover { transform: translateY(-2px); }

/* Sections alternate white and cream, starting white. */
.archive-cat { background: #fff; }
.archive-cat-alt { background: var(--cream-2); }

.cat-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  margin-bottom: 36px;
}

.cat-head h2.display { margin: 0; font-size: clamp(32px, 3.6vw, 48px); line-height: 1.05; }

/* Two structural wrappers the REFERENCE emits and styles nowhere — every rule
   is on .cat-head itself or on the .btn-arrow the second one also carries.
   Given a rule here so the flex row behaves without relying on defaults, and so
   bin/check-classes.py is not asked to exempt markup we chose to emit. */
.cat-head-text { flex: 1 1 auto; min-width: 0; }
.cat-head-all { flex: 0 0 auto; }
.cat-head-desc { margin: 14px 0 0; font-size: 16px; line-height: 1.55; color: var(--ink-soft); max-width: 54ch; }

.archive-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 28px;
}

@media (max-width: 1024px) { .archive-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 760px)  { .archive-grid { grid-template-columns: repeat(2, 1fr); } }

.archive-card {
  text-decoration: none;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.archive-card-tile {
  position: relative;
  aspect-ratio: 1 / 1;
  border-radius: 28px;
  overflow: hidden;
  box-shadow: 0 14px 32px -16px rgba(35, 31, 32, 0.28);
  border: 3px solid #eaeaea;
  transition: transform 220ms cubic-bezier(.4, 0, .2, 1);
}

.archive-card:hover .archive-card-tile { transform: translateY(-4px); }

.archive-card-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* A product with no photography, on the archive. Same treatment as its own
   product page, so the two do not disagree about what "no photo yet" looks
   like. 15 of the 62 are in that state. */
.archive-card-placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  text-align: center;
  background: linear-gradient(135deg, rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.35));
  color: #fff;
  font-family: var(--font-display);
  font-size: 16px;
  line-height: 1.2;
}

.archive-card-name {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 18px;
  line-height: 1.15;
  color: var(--ink);
  transition: color 200ms ease;
}

.archive-card:hover .archive-card-name { color: var(--card-hover-color, var(--ink)); }

.archive-card-tagline {
  font-size: 14px;
  line-height: 1.5;
  color: rgba(35, 31, 32, 0.7);
  margin: 0;
}

/* Placeholder thumbnails, for a product with no photography. The reference
   renders three, each reading "Pending" over the product's own colour. They
   are `disabled` because there is nothing to switch to — a focusable control
   that does nothing is worse than no control. */
.gallery-thumb[disabled] {
  cursor: default;
  background:
    linear-gradient(135deg, rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.35)),
    var(--placeholder-bg, #f48220);
}

.gallery-thumb-pending {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  color: #fff;
  font-family: var(--font-body);
  font-size: 10px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-weight: 700;
}

/* The italic aside beside the Category row in the product meta. */
.product-meta-note {
  font-style: italic;
  color: rgba(35, 31, 32, 0.55);
  font-weight: 400;
}

/* An unpriced product. The reference shows this muted and italic rather than
   omitting the element. */
.product-price-pending {
  font-style: italic;
  color: rgba(35, 31, 32, 0.55);
  font-size: 18px;
  font-family: var(--font-body);
  font-weight: 400;
}

/* ── Related products band ───────────────────────────────────────────────
   The reference's own shape, which the theme was not using — it rendered
   Woo's default product loop instead. Tile colour comes from the category, so
   the band reads as part of the family you are already looking at. */
.related-band { padding-block: 72px; }

.related-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  margin-bottom: 36px;
}

.related-see-all {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-body);
  font-size: 14.5px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
}

.related-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 28px;
}

@media (max-width: 1024px) { .related-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 760px)  { .related-grid { grid-template-columns: repeat(2, 1fr); } }

.related-card { text-decoration: none; display: flex; flex-direction: column; gap: 14px; }

.related-card-tile {
  position: relative;
  aspect-ratio: 1 / 1;
  border-radius: 28px;
  overflow: hidden;
  background: var(--tile-color, #f48220);
  box-shadow: 0 14px 32px -16px rgba(35, 31, 32, 0.28);
  border: 3px solid #eaeaea;
  transition: transform 220ms cubic-bezier(.4, 0, .2, 1);
}

.related-card:hover .related-card-tile { transform: translateY(-4px); }

.related-card-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

.related-card-placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 18px;
  text-align: center;
  background: linear-gradient(135deg, rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.35));
  color: #fff;
  font-family: var(--font-display);
  font-size: 15px;
  line-height: 1.2;
}

.related-card-name {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 18px;
  line-height: 1.15;
  color: var(--ink);
  transition: color 200ms ease;
}

.related-card:hover .related-card-name { color: var(--card-hover-color, var(--ink)); }

/* The caption under a placeholder tile. The reference states why the tile is
   flat rather than leaving a visitor to guess the image failed to load. */
.gallery-placeholder-note {
  display: block;
  margin-top: 14px;
  font-family: var(--font-body);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  opacity: 0.85;
}

/* ── "Or explore another family" band ───────────────────────────────────
   Closes every category archive with links to the other six. The reference
   declares it inline; named here because the card repeats and because a
   category page without it dead-ends at its own grid. Not on /products/ —
   every category is already a section there. */
.other-cats-band {
  padding: 96px 0;
  background: var(--cream-2);
}

.other-cats-band .eyebrow { color: var(--accent); margin-bottom: 14px; }
.other-cats-band h2.display { margin: 0 0 36px; font-size: clamp(28px, 3.2vw, 42px); }

.other-cats-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 28px;
}

@media (max-width: 900px) { .other-cats-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .other-cats-grid { grid-template-columns: 1fr; } }

.other-cat-card {
  text-decoration: none;
  display: flex;
  flex-direction: column;
  gap: 16px;
  text-align: left;
}

.other-cat-tile {
  position: relative;
  display: block;
  aspect-ratio: 1 / 1;
  border-radius: 32px;
  overflow: hidden;
  box-shadow: 0 14px 32px -16px rgba(35, 31, 32, 0.22);
  border: 3px solid #eaeaea;
  transition: transform 220ms cubic-bezier(.4, 0, .2, 1);
}

.other-cat-card:hover .other-cat-tile { transform: translateY(-4px); }

.other-cat-img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

.other-cat-card .cat-card-name {
  font-family: var(--font-display);
  font-weight: 400;
  font-size: 24px;
  line-height: 1.15;
  color: var(--ink);
  transition: color 200ms ease;
}

.other-cat-card:hover .cat-card-name { color: var(--card-hover-color, var(--ink)); }

.other-cat-desc {
  font-size: 14.5px;
  line-height: 1.5;
  color: var(--ink-soft);
  margin: 0;
}

/* ── Archive hero (shop page and every category) ─────────────────────────
   THIS IS A DARK BAND, and it was rendering as plain text on white. The
   reference gives /products/ and all seven category pages the same treatment
   the interior .page-hero gets: ink background, the branding pattern at 7%,
   a left-to-right ink gradient over it, and the bottom corners rounded. The
   port had the markup right and none of the styling, so the heading sat on
   white in ink type — a 165px block where the reference is 441px.

   THE HEADING TAKES THE CATEGORY'S COLOUR. On the hub it is the brand blue;
   on a category page it is that category's own colour, which is why it comes
   through --hero-accent rather than being fixed here. */
.archive-hero {
  padding: 178px 0 122px;
  background: var(--ink);
  color: #fff;
  position: relative;
  overflow: hidden;
  border-radius: 0 0 48px 48px;
}

/* Slightly tighter on a category page, as the reference has it. */
.archive-hero.archive-hero-term { padding-bottom: 96px; }

.archive-hero::before {
  content: '';
  position: absolute;
  inset: 0;
  background-image: url('../images/branding-patterns/needoh-branding-pattern-2.svg');
  background-size: cover;
  background-position: center left;
  opacity: 0.07;
}

/* Keeps the type readable over the pattern on the left, where it sits. */
.archive-hero::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to right, var(--ink) 0%, var(--ink) 32%, rgba(35, 31, 32, 0) 62%);
  z-index: 1;
  pointer-events: none;
}

.archive-hero .wrap { position: relative; z-index: 2; }
.archive-hero .eyebrow { color: rgba(255, 255, 255, 0.65); margin-bottom: 16px; }
.archive-hero h1.display { color: var(--hero-accent, #3fabe1); margin-bottom: 20px; }
.archive-hero .lede { color: rgba(255, 255, 255, 0.9); margin-bottom: 0; max-width: 52ch; }
.archive-hero p { color: rgba(255, 255, 255, 0.9); }
.archive-hero .cat-chip-row { margin-top: 28px; }
