/* -------------------------------------------------------------------------
   KinkBox theme styles

   theme.json carries the design system. This file is only for things
   theme.json cannot express, and for the guardrails that came out of the
   7 Sep 2026 bug hunt on the Elementor site.
   ---------------------------------------------------------------------- */

/* -------------------------------------------------------------------------
   1. iOS form-field zoom. DO NOT "TIDY" THIS.

   iOS zooms the whole page in whenever a form field's text is smaller than
   16px, then leaves the page zoomed so the user has to scroll left and right.
   The old site set inputs to 15px via Elementor's global Accent typography and
   the checkout was unusable on a phone because of it.

   Every browser on iPhone uses Safari's engine, so Chrome and Firefox there
   behave identically. 16px is the floor, not a preference.
   ---------------------------------------------------------------------- */
@media (max-width: 767px) {
  input:not([type="button"]):not([type="submit"]):not([type="checkbox"]):not([type="radio"]),
  textarea,
  select,
  .select2-container .select2-search__field,
  .select2-selection__rendered {
    font-size: 16px !important;
  }
}

/* -------------------------------------------------------------------------
   2. Nothing may be wider than the screen.

   The old homepage scrolled 40px sideways because a carousel let its
   off-screen slides widen the document. Release gate: on every template,
   document.scrollWidth must equal document.clientWidth at 375px.
   ---------------------------------------------------------------------- */
html,
body {
  overflow-x: clip;
}

/* Wide content scrolls inside itself rather than pushing the page out. */
.wp-block-table,
.wp-block-code,
pre {
  max-width: 100%;
  overflow-x: auto;
}

img,
video,
iframe {
  max-width: 100%;
  height: auto;
}

/* -------------------------------------------------------------------------
   3. Header

   Target: one row, roughly 66px tall on mobile. Logo left, navigation and
   cart right. The old header was 203px because three icons wrapped onto
   three lines inside a container that was too narrow.
   ---------------------------------------------------------------------- */
.kb-header {
  border-bottom: 1px solid var(--wp--preset--color--hairline);
}

.kb-header .wp-block-site-logo img {
  max-width: 185px;
  height: auto;
  display: block;
}

@media (max-width: 767px) {
  .kb-header .wp-block-site-logo img {
    max-width: 185px;
  }
}

/* Cart and account icons sit on one row, always. */
.kb-header-actions {
  flex-wrap: nowrap;
}

/* -------------------------------------------------------------------------
   4. Type details theme.json cannot reach
   ---------------------------------------------------------------------- */

/* Measure is handled by contentSize in theme.json (700px). Do not add a
   ch-based max-width here: it centred the paragraphs inside the column and
   left the body text indented 45px from the headings. */

h1,
h2,
h3 {
  text-wrap: balance;
}

/* Uppercase labels need a little air. */
.kb-eyebrow {
  font-family: var(--wp--preset--font-family--body);
  font-size: 0.9375rem;
  font-weight: 400;
  letter-spacing: 1.1px;
  line-height: 1.3;
  text-transform: uppercase;
}

/* -------------------------------------------------------------------------
   5. Accessibility
   ---------------------------------------------------------------------- */
:where(a, button, input, select, textarea, summary):focus-visible {
  outline: 2px solid var(--wp--preset--color--brand-red);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* -------------------------------------------------------------------------
   6. WooCommerce

   Cart and checkout are transactional. No decorative banners above the
   content on mobile: the old cart page pushed the actual cart items to
   666px down the screen behind a 433px photo.
   ---------------------------------------------------------------------- */
.woocommerce-cart .kb-page-banner,
.woocommerce-checkout .kb-page-banner {
  display: none;
}

@media (min-width: 768px) {
  .woocommerce-cart .kb-page-banner,
  .woocommerce-checkout .kb-page-banner {
    display: block;
    min-height: 433px;
    margin-block: 0 40px;
    background-image: url("https://kinkbox.com/wp-content/uploads/2026/01/KB-Hero-woman-in-bed-2500x700-1.avif");
    background-position: 50% 50%;
    background-size: cover;
    background-repeat: no-repeat;
  }
}

/* Shop archive hero.

   The band is 350px tall with the image painted on a ::before at full
   opacity. The eyebrow and h1 sit over it in white and are centred.

   background-size is COVER, not auto. The source file
   (Which-KinkBox-are-You_Grainy.avif) is only 1200x400, which is narrower
   than the band at any desktop width - at a 1920 viewport the band is 1635px
   and the last 435px rendered as a flat black slab, with the artwork pinned
   to the top-left. On a phone, natural size showed only the top-left corner
   of the photo. Production pins it top-left at natural size and has the same
   defect on a wide screen; do not copy it back. Cover upscales a deliberately
   grainy image ~1.36x, which is invisible, and it covers at every width. */
.kb-shop-hero {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 350px;
  padding: 40px 20px;
  isolation: isolate;
}

.kb-shop-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: url("https://kinkbox.com/wp-content/uploads/2025/12/Which-KinkBox-are-You_Grainy.avif");
  background-position: 50% 50%;
  background-size: cover;
  background-repeat: no-repeat;
  background-color: var(--wp--preset--color--ink);
}

.kb-shop-hero > * {
  margin-block: 0;
}

.kb-shop-hero .kb-eyebrow {
  margin-block-end: 20px;
  font-size: 1.0625rem;
  font-weight: 500;
}

/* The SEO copy block under the grid. Production's eyebrow here is the red
   one at 15px/500, and the paragraphs run at the normal body measure. */
.kb-shop-copy .kb-eyebrow {
  font-weight: 500;
  margin-block-end: 8px;
}

.kb-shop-copy h2 {
  margin-block: 8px 24px;
}

/* Cart layout.

   UNVERIFIED as of 8 Sep 2026. The staging cart page is still rendered by
   Elementor (see below), so page-cart.html has never actually been on screen
   and these numbers have not been checked against a real render. They are
   production's measurements, not staging's:

     .e-cart__container   1455 wide
     .woocommerce-cart-form   859 wide, flush left    -> 59%
     .cart-collaterals        507 wide, flush right   -> 34.8%
     gap between them          60

   Re-measure the moment the page renders through the theme. */
.woocommerce-cart .woocommerce-cart-form,
.woocommerce-cart .cart-collaterals {
  float: none;
  width: auto;
}

@media (min-width: 900px) {
  .woocommerce-cart .woocommerce-cart-form {
    float: left;
    width: 59%;
  }

  .woocommerce-cart .cart-collaterals {
    float: right;
    width: 34.8%;
  }

  .woocommerce-cart .woocommerce-cart-form::after,
  .woocommerce-cart .cart-collaterals::after {
    content: "";
    display: table;
    clear: both;
  }
}

/* Checkout form fields, measured on production 8 Sep 2026. Same UNVERIFIED
   caveat as the cart above - the staging checkout is still Elementor's. */
.woocommerce-checkout .woocommerce-checkout h3 {
  /* theme.json registers exactly two families: `body` and `display`. There is
     no `heading` slug, so var(--wp--preset--font-family--heading) resolved to
     nothing and these headings fell back to Lato. Production uses Lustria. */
  font-family: var(--wp--preset--font-family--display);
  font-size: 1.75rem;
  font-weight: 400;
  margin-block-end: 16px;
}

.woocommerce-checkout .woocommerce-checkout label {
  font-size: 1.0625rem;
  font-weight: 400;
  color: var(--wp--preset--color--body-text);
}

/* Desktop only. Below 768px the 16px iOS floor in section 1 wins on purpose -
   never lower these on a phone, or the checkout zooms on focus again. */
@media (min-width: 768px) {
  body.woocommerce-checkout .woocommerce-checkout input[type="text"],
  body.woocommerce-checkout .woocommerce-checkout input[type="email"],
  body.woocommerce-checkout .woocommerce-checkout input[type="tel"],
  body.woocommerce-checkout .woocommerce-checkout textarea,
  body.woocommerce-checkout .woocommerce-checkout .select2-selection {
    font-size: 0.9375rem;
    padding: 14px;
    min-height: 50px;
    border: 1px solid rgba(32, 7, 7, 0.8);
    border-radius: 0;
    background-color: #fff;
  }
}

/* Width of the cart and checkout.

   The classic-shortcode block wraps WooCommerce's output in its OWN
   `.woocommerce` group, and that group carries a hard `max-width: 1000px`.
   The template's `contentSize` never reaches inside it, which is why
   page-checkout.html asking for 1100 still rendered at 1000 and the cart's
   two columns came out 590/348 instead of production's 859/507.

   Set the width here, on the element that actually has the cap. */
.woocommerce-cart .woocommerce.wp-block-group {
  max-width: 1455px;
}

.woocommerce-checkout .woocommerce.wp-block-group,
.woocommerce-account .woocommerce.wp-block-group {
  max-width: 1100px;
}

/* The "Cart" heading has to share the cart box's left edge. Constrained
   layout was centring it at the 700px measure, so it sat inset from the
   table below it. Same max-width, same auto margins, edges line up. */
.kb-cart-heading {
  max-width: 1455px;
}

/* Product gallery.

   The frame reserves its space so the page does not jump while loading, but
   it must reserve the RIGHT space: production's main product image is
   723x483, i.e. 3:2, and the source files are 1600x1070 which is the same
   ratio. A 1:1 frame - which is what this was - left roughly 170px of blank
   white between the photo and the thumbnails on every product page.

   If the product photography ever changes shape, change this number to
   match it rather than going back to a square. */
.woocommerce div.product div.images .woocommerce-product-gallery__image,
.wp-block-woocommerce-product-image-gallery .woocommerce-product-gallery__image {
  aspect-ratio: 3 / 2;
}

.woocommerce div.product div.images .woocommerce-product-gallery__image img,
.wp-block-woocommerce-product-image-gallery .woocommerce-product-gallery__image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* WooCommerce caps the gallery block at the source image's own width, which
   held it to 512px inside a 696px column and left the row lopsided, with a
   220px trench between the photo and the copy.

   THE SELECTOR NEEDS BOTH CLASSES. WooCommerce's own rule is
   `.woocommerce .wp-block-woocommerce-product-image-gallery { max-width: 512px }`
   at specificity 0,2,0. A single-class override loses to it silently, which is
   why this was written in 0.8 and never actually took effect until 13 Sep.

   The gallery is a FlexSlider: it measures its container once at init and
   writes a pixel width onto every slide. So this rule only works from the
   stylesheet, at load. Injecting it afterwards leaves the slides at their old
   width until a resize, and the resize leaves the track mid-slide. */
.woocommerce .wp-block-woocommerce-product-image-gallery {
  max-width: none;
}

/* -------------------------------------------------------------------------
   7. Desktop left navigation rail

   Ported from the production Elementor header (element 5d5ec7a), measured at
   1425px on 7 Sep 2026:

     position fixed, top 0 / left 0, width 270px, full viewport height
     background white, padding 30px, z-index 999
     flex column, justify-content space-between, gap 30px
     logo 210px wide at the top, nav + divider + cart in the middle,
     social icons pinned to the bottom
     nav links: Lato 13px / 400 / 1.1px letter-spacing / uppercase / #000,
     padding 15px 20px, 47px tall each

   Shown from 1025px up. Below that the top bar in parts/header.html takes
   over, which is what production does too (its rail is hidden on tablet).
   ---------------------------------------------------------------------- */

/* Specificity note: the rail group carries a WordPress-generated layout class
   (.wp-container-core-group-is-layout-*) that sets `display: flex`. A single
   .kb-rail selector loses to it, and the rail then renders in flow on phones -
   a second logo and a duplicate menu stacked above the top bar. Two classes
   beats one, so both rules below are written as .wp-block-group.kb-rail. */
.wp-block-group.kb-rail {
  display: none;
}

@media (min-width: 1025px) {
  /* The rail sits over the page; main content is inset so nothing hides
     behind it. Production lets full-bleed sections run underneath and simply
     covers them with the rail's white background - same result for text,
     without permanently hiding 270px of every hero image. */
  body {
    padding-left: 270px;
  }

  .wp-block-group.kb-rail {
    /* The base rule above sets display:none. It must be turned back on here,
       or the rail silently never appears. */
    display: flex;
    flex-direction: column;
    position: fixed;
    inset: 0 auto 0 0;
    z-index: 999;
    width: 270px;
    max-width: min(100%, 270px);
    height: 100vh;
    padding: 30px;
    gap: 30px;
    justify-content: space-between;
    overflow-y: auto;
    overscroll-behavior: contain;
    /* Production has no divider line here - the rail reads as separate because
       page content starts hard against it. If it ever looks too blended on a
       white page, one line brings it back:
       border-right: 1px solid var(--wp--preset--color--hairline); */
  }

  /* The mobile top bar is not needed once the rail is showing. */
  .kb-header {
    display: none;
  }

  .kb-rail-top {
    gap: 30px;
  }

  .kb-rail .wp-block-site-logo img {
    max-width: 210px;
    height: auto;
    display: block;
  }

  .kb-rail-middle {
    gap: 0;
  }

  /* Navigation */
  .kb-rail-nav ul.wp-block-navigation__container {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
  }

  .kb-rail-nav .wp-block-navigation-item {
    display: block;
  }

  .kb-rail-nav .wp-block-navigation-item__content {
    display: block;
    padding: 15px 20px;
    font-family: var(--wp--preset--font-family--body);
    font-size: 13px;
    font-weight: 400;
    letter-spacing: 1.1px;
    line-height: 1.3;
    text-transform: uppercase;
    color: var(--wp--preset--color--ink);
    text-decoration: none;
  }

  .kb-rail-nav .wp-block-navigation-item__content:hover,
  .kb-rail-nav .current-menu-item > .wp-block-navigation-item__content {
    color: var(--wp--preset--color--brand-red);
  }

  /* The item that has a submenu (Products) must keep its link and its little
     toggle arrow on ONE 47px row. Left as display:block, the <li> stacks the
     link, the arrow and the collapsed submenu strip and the row becomes 69px,
     which pushes the whole nav 22px out of step with production. */
  .kb-rail-nav .wp-block-navigation-item.has-child {
    position: relative;
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
  }

  .kb-rail-nav .wp-block-navigation__submenu-icon {
    padding: 0;
    margin-left: -8px;
  }

  /* Submenu stays collapsed until opened, same as production, where the nav
     is exactly five rows tall (5 x 47px = 235px) with Products closed.
     It must be taken out of flow, or its collapsed strip adds height to the
     row. Do not set position:static here. */
  .kb-rail-nav .wp-block-navigation__submenu-container {
    position: absolute;
    top: 100%;
    left: 0;
    border: 1px solid var(--wp--preset--color--hairline);
    background: var(--wp--preset--color--surface);
    min-width: 210px;
    padding: 6px 0;
  }

  .kb-rail-nav .wp-block-navigation__submenu-container .wp-block-navigation-item__content {
    padding: 9px 20px;
    font-size: 12px;
    color: var(--wp--preset--color--body-text);
  }

  .kb-rail-nav .wp-block-navigation__submenu-container .wp-block-navigation-item__content:hover {
    color: var(--wp--preset--color--brand-red);
  }

  /* Divider: 124px line, inset 20px, matching production. */
  .kb-rail-divider.wp-block-separator {
    width: 124px;
    max-width: 124px;
    margin: 10px 0 10px 20px;
    height: 1px;
    border: 0;
  }

  /* Cart row */
  .kb-rail-cart {
    padding: 20px 0;
    gap: 20px;
  }

  .kb-rail-cart .wc-block-mini-cart__button {
    padding: 0;
  }

  /* Socials pinned to the bottom of the rail */
  .kb-rail-social {
    gap: 5px;
    margin: 0;
  }

  .kb-rail-social .wp-social-link {
    width: 36px;
    height: 36px;
  }
}

/* Very short viewports: let the rail scroll rather than clip the socials. */
@media (min-width: 1025px) and (max-height: 700px) {
  .kb-rail {
    justify-content: flex-start;
  }

  .kb-rail-social {
    margin-top: 30px;
  }
}

/* -------------------------------------------------------------------------
   8. Image slider

   Replaces the Qi Addons image slider used on the homepage product sections.
   No JavaScript and no library: a scroll-snap row. Swipes on touch, scrolls
   with a trackpad, and keyboard-scrolls when focused.

   Qi Addons is on the drop list, so this is what those three sliders become.
   ---------------------------------------------------------------------- */
/* The wrapper is added by the script in functions.php and exists only to
   give the arrows something to sit on: the slider itself scrolls, so an
   absolutely positioned child inside it would scroll away with the images. */
/* The wrapper is added by the script in functions.php and exists only to
   give the arrows something to sit on. */
.kb-slider-wrap {
  position: relative;
}

/* A transform track, deliberately NOT a scroll container.
   The first build of this used overflow-x + scroll-snap-type: x mandatory
   and scroll-behavior: smooth. Those two fight: the smooth scroll animates
   toward the next slide and mandatory snap then yanks it straight back to
   0, so the arrows did nothing at all. Proven on 7 Sep - removing either
   one alone did not help; only removing both did.

   Translating the slides instead has no such failure mode, needs no scroll
   container, and gives the same result on every browser. Touch swipe is
   handled explicitly in the script. */
.kb-slider {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  gap: 0;
  overflow: hidden;
  border-radius: 3px;
}

.kb-slider > * {
  flex: 0 0 100%;
  margin: 0;
  /* Each slide shifts by one full slide width per index step. */
  transform: translateX(calc(var(--kb-i, 0) * -100%));
  transition: transform 0.45s ease;
}

.kb-slider figure,
.kb-slider .wp-block-image {
  width: 100%;
}

.kb-slider img {
  display: block;
  width: 100%;
  height: auto;
  /* Production's product image is 974x490 - 2:1, not 3:2. A 3:2 frame made
     each product row 112px taller than the live page. */
  aspect-ratio: 2 / 1;
  object-fit: cover;
}

@media (prefers-reduced-motion: reduce) {
  .kb-slider > * {
    transition: none;
  }
}

/* Arrows, matched to production's Qi slider: a bare 28x26 white glyph with
   2px strokes, no background, no border, no circle, inset 30px from each
   edge and vertically centred over the photo. */
.kb-slider-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 10;
  width: 32px;
  height: 26px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  line-height: 0;
}

.kb-slider-arrow--prev {
  left: 30px;
}

.kb-slider-arrow--next {
  right: 30px;
}

.kb-slider-arrow svg {
  display: block;
  width: 28px;
  height: 26px;
  overflow: visible;
}

/* Production draws these at 2px, which disappears against a light photo.
   3px with round caps and a soft shadow keeps the same shape and weight
   language while staying legible on every slide. */
.kb-slider-arrow line {
  stroke: #fff;
  stroke-width: 3px;
  stroke-linecap: round;
}

.kb-slider-arrow svg {
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.45));
}

.kb-slider-arrow:hover line {
  stroke: var(--wp--preset--color--brand-red);
}

/* An arrow with nowhere left to go is greyed rather than left as a control
   that silently does nothing. */
.kb-slider-arrow[disabled] {
  opacity: 0.35;
  cursor: default;
}

@media (max-width: 781px) {
  .kb-slider-arrow--prev {
    left: 12px;
  }

  .kb-slider-arrow--next {
    right: 12px;
  }
}

/* -------------------------------------------------------------------------
   9. Homepage
   ---------------------------------------------------------------------- */

/* The hero keeps production's proportions: 500px tall on desktop, 250px on
   phones, photo cropped to centre. */
.kb-hero {
  min-height: 500px;
}

@media (max-width: 767px) {
  /* The cover block writes min-height as an INLINE style, which beats any
     selector. !important is the only way to reach it, and matching production's
     250px on phones matters: a 500px hero pushes the first product most of a
     screen down, which is the exact problem that was fixed on the old site. */
  .wp-block-cover.kb-hero {
    min-height: 250px !important;
  }
}

.kb-hero h1,
.kb-hero p {
  color: #fff;
}

/* The reassurance band's own type and spacing live in section 10, matched
   to production. Nothing for it here. */

/* -------------------------------------------------------------------------
   10. Live-site parity

   Every number in this section was measured off the production Elementor
   homepage (kinkbox.com) at a 1920px viewport on 7 Sep 2026, then matched
   here. Where production and this theme disagreed, production wins - the
   brief is "look just like what we have today, with less bloat".

   Type sizes are NOT set here. WordPress prints its font-size presets with
   !important, so a rule in this file cannot beat one; the scale in
   theme.json was rebuilt to production's actual sizes instead (14 / 17 / 19
   / 20 / 28 / 32 / 40 / 60) and the blocks on the homepage point at the
   right preset. Only the things a preset cannot express live below.

   Section padding is set on the blocks themselves (page 3053), because
   WordPress writes block spacing as an inline style that no selector can
   beat. That is why the phone overrides at the end of this section need
   !important - it is the only way past an inline style, not laziness.

   The full delta table is in claude/homepage-parity-2026-09-07.md.
   ---------------------------------------------------------------------- */

/* --- Hero -------------------------------------------------------------- */
/* Production tracks the sub-heading 0.2px looser than body copy. */
.kb-hero p {
  letter-spacing: 0.2px;
}

/* --- Product rows (The KinkBox / The Collector / The Exhibitionist) ----- */
/* Production does NOT cap these at the theme's wide size. The row is fluid:
   96% of the space beside the rail, so at 1920px it is 1555px, not 1200px.
   Inside it sits 40px of padding, leaving 1475px of content split ~66/33
   with 20px between - which is where production's 974px image and 482px
   copy column come from. */
.kb-product-row > .wp-block-columns {
  max-width: none;
  width: 96%;
  margin-inline: auto;
  padding-inline: 40px;
  gap: 20px;
}

/* The 68/32 split lives on the columns block itself (page 3053) and lands
   within ~20px of production's 974/482. Overriding flex-basis from here
   fights the block's own inline width and makes it worse, not better -
   tried on 7 Sep, reverted. */
.kb-product-row > .wp-block-columns > .wp-block-column:last-child {
  padding: 25px;
}

/* --- Reassurance band ("Shhh... it's on the way") ----------------------- */
/* Production: no background tint, 50px of margin above and below, a 1370px
   row of three 450px columns with 10px between them. The grey band that was
   here before is not on the live site. */
/* Production tints this band #EAE1DD, full bleed. It is painted on an
   Elementor ::before pseudo-element, which is why an audit that only looked
   at elements reported "no background" and the tint was wrongly dropped from
   the first build. Same for the AfterGlow and the newsletter below - if you
   are ever comparing backgrounds against production, check ::before. */
.kb-trust {
  margin-block: 50px;
  background-color: var(--wp--preset--color--blush);
}

.kb-trust > .wp-block-columns {
  max-width: 1370px;
  margin-inline: auto;
  gap: 10px;
}

/* Production's blurbs sit in a 348px measure inside a 450px column - the
   inline padding is what makes each wrap to three lines and the band come
   out 243px tall. Without it the band is 45px short. */
.kb-trust .wp-block-column {
  padding: 20px 50px;
}

.kb-trust h3 {
  margin-bottom: 20px;
}

.kb-trust p {
  color: var(--wp--preset--color--body-text);
}

/* --- The Case for Sexy, Secure Storage ---------------------------------- */
/* Production lays this out as a two-column row - a still image on the left
   (873px) and the copy on the right (582px), 20px between. It is served
   through an Elementor video widget on the live site, but the widget is
   playing a single .avif frame, so a plain image block is the same picture
   with none of the machinery. The row is fluid like the product rows. */
.kb-case > .wp-block-columns {
  max-width: none;
  width: 96%;
  margin-inline: auto;
  padding-inline: 40px;
  gap: 20px;
}

.kb-case > .wp-block-columns > .wp-block-column:first-child {
  padding: 10px;
}

.kb-case > .wp-block-columns > .wp-block-column:last-child {
  padding: 25px;
}

.kb-case p {
  margin-bottom: 23px;
}

/* The video is a click-to-play, never an autoplay: the file is 14.7MB, and
   preload="none" on the block means not a byte of it is fetched until a
   visitor actually presses play. The poster carries the whole first
   impression, so it has to look deliberate.

   Chrome draws no centre play button on a paused video - only the bottom
   control bar - so the poster would otherwise look like a plain image with
   no affordance. The button below is drawn by the theme and removed the
   moment playback starts, after which the native controls take over. It is
   injected by a small script in functions.php. */
.kb-video {
  position: relative;
  margin: 0;
}

.kb-video video {
  display: block;
  width: 100%;
  height: auto;
}

.kb-video__play {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  transition: opacity 0.15s ease;
}

.kb-video__play::before {
  content: "";
  width: 78px;
  height: 78px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  transition: background 0.15s ease;
}

/* The triangle, drawn with borders so there is no icon font and no SVG. */
.kb-video__play::after {
  content: "";
  position: absolute;
  margin-left: 6px;
  border-style: solid;
  border-width: 15px 0 15px 26px;
  border-color: transparent transparent transparent #fff;
}

.kb-video__play:hover::before,
.kb-video__play:focus-visible::before {
  background: var(--wp--preset--color--brand-red);
}

.kb-video.is-playing .kb-video__play {
  display: none;
}

@media (max-width: 781px) {
  .kb-video__play::before {
    width: 60px;
    height: 60px;
  }

  .kb-video__play::after {
    border-width: 12px 0 12px 20px;
  }
}

/* --- The AfterGlow ------------------------------------------------------ */
/* The 60px Lustria heading is the one oversized heading on the page and it
   is what makes this section read as a break. Production sets it a shade
   heavier than the rest of the display type. */
/* Production runs a tinted photograph behind this section at 70% opacity,
   constrained to the 1440px content column rather than full bleed. The tint
   sits on its own layer so the type above it stays at full contrast. */
.kb-afterglow-band,
.kb-newsletter {
  position: relative;
  isolation: isolate;
}

.kb-afterglow-band::before,
.kb-newsletter::before {
  content: "";
  position: absolute;
  inset-block: 0;
  z-index: -1;
  background-repeat: no-repeat;
}

/* Production does NOT run this photograph behind the whole section, and it
   does NOT scale it. It is a 214px band sitting behind the eyebrow and the
   heading only, 1440px wide, with the image at its natural 2500x700 size
   centred - so what you see is the middle slice of the photo, not the whole
   thing squashed into the section.

   Stretching it over the full 871px section with background-size: cover -
   which is what the first attempt did - reads as a completely different
   image. Keep background-size at its natural value. */
/* Width is set here rather than left to the alignwide default: the parent
   group resolves alignwide to 1200px, not the theme's 1440px wide size, so
   the band came out 240px narrower than production's. */
.kb-afterglow-band {
  max-width: min(100%, 1440px);
  margin-inline: auto;
  padding-block: 43px;
}

.kb-afterglow-band::before {
  left: 50%;
  transform: translateX(-50%);
  width: min(100%, 1440px);
  background-image: url("https://kinkbox.com/wp-content/uploads/2025/12/KB-Hero-Images-Legs-on-bed-2500x700-Tint1.avif");
  background-position: 50% 50%;
  background-size: auto;
  opacity: 0.7;
}

/* The newsletter's photograph runs edge to edge at 57%. This one is scaled
   to fill, because production's is 459px tall against a 700px source - close
   enough that cover and natural size look the same, and cover survives a
   narrow viewport without showing a bare strip. */
.kb-newsletter::before {
  inset-inline: 0;
  background-image: url("https://kinkbox.com/wp-content/uploads/2026/01/KB-Hero-Images-Messy-Bed1-2500x700-1.avif");
  background-position: center;
  background-size: cover;
  opacity: 0.57;
}

.kb-afterglow h2 {
  font-weight: 500;
}

/* The eyebrow above it carries no font-size preset class, so this rule can
   set the size directly: production runs it at 18px bold, sentence case. */
.kb-afterglow .kb-eyebrow {
  font-size: 18px;
  line-height: 1;
  font-weight: 700;
  letter-spacing: normal;
  text-transform: none;
}

/* --- AfterGlow post cards -----------------------------------------------
   Production's card, measured: a 453x562 white panel, image on top at
   1.9:1 cropped to fill, then content padded 27px top / 40px sides / 45px
   bottom, a title clamped to TWO lines, 17px, then the excerpt.

   The two-line clamp is the whole reason the cards line up. Without it a
   long headline runs to five lines, the card grows, and the row goes
   ragged - which is exactly what it was doing before.

   The block markup has no content wrapper to pad, so the horizontal
   padding goes on the title and excerpt directly. That is the reason for
   the repetition below; it is not an oversight. */
/* Do NOT make this a flex container. A flex item has its display value
   blockified, which turns the title's display: -webkit-box into flow-root
   and silently kills the line clamp below - the titles then run to five
   lines again. The children stack fine in normal flow. */
.kb-afterglow .wp-block-post {
  background-color: var(--wp--preset--color--surface);
}

.kb-afterglow .wp-block-post-featured-image {
  margin: 0;
}

.kb-afterglow .wp-block-post-featured-image img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1.9 / 1;
  object-fit: cover;
}

/* -webkit-line-clamp only does anything on display: -webkit-box, and
   WordPress sets post titles to display: flow-root with a rule this cannot
   outrank by specificity alone. Without the override the clamp is computed,
   ignored, and the titles run to five lines - which is what they were doing.
   The !important is on `display` only, for that one reason. */
/* Two lines, and the height cap is what actually enforces it.
   -webkit-line-clamp needs display: -webkit-box, and on this element Chrome
   refuses that value outright - it computes flow-root even when set inline
   with !important, so the clamp is accepted and then ignored. Chased on
   7 Sep; do not "fix" it by adding the display back.
   max-height does the same job with no such dependency: 2 lines x the 1.3
   heading line-height. The clamp properties stay because where -webkit-box
   IS honoured they add the ellipsis; the cap is the guarantee. */
.kb-afterglow .wp-block-post-title {
  margin-block: 27px 17px;
  padding-inline: 40px;
  max-height: 2.6em;
  overflow: hidden;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

/* The excerpt is clamped too, so one wordy opening paragraph cannot
   unbalance the row the way the titles were doing. */
/* Three lines, same reasoning: 3 x the 1.5 body line-height.
   min-height as well as max-height, because WordPress truncates an excerpt
   by WORD COUNT, not by lines - the same word budget wraps to two lines on
   one card and three on the next, which left one description visibly short
   and the row uneven. Fixing the box at three lines reserves the same space
   on every card whatever the wrap. */
.kb-afterglow .wp-block-post-excerpt {
  margin-block: 0;
  padding-inline: 40px;
  padding-bottom: 45px;
  min-height: 4.5em;
  max-height: 4.5em;
  overflow: hidden;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
}

/* Titles get the same treatment for the same reason: a one-line headline
   would otherwise pull its excerpt up out of line with its neighbours. */
.kb-afterglow .wp-block-post-title {
  min-height: 2.6em;
}

/* Production's post titles are black, not brand red. */
/* :visited is in the list deliberately - without it a post the reader has
   already opened comes back in the link colour and one card in the row
   turns red while its neighbours stay black. */
.kb-afterglow h3,
.kb-afterglow h3 a,
.kb-afterglow h3 a:visited,
.kb-afterglow .wp-block-post-title,
.kb-afterglow .wp-block-post-title a,
.kb-afterglow .wp-block-post-title a:visited {
  color: var(--wp--preset--color--ink);
  text-decoration: none;
}

.kb-afterglow h3 a:hover,
.kb-afterglow .wp-block-post-title a:hover {
  color: var(--wp--preset--color--brand-red);
}

/* --- Footer -------------------------------------------------------------
   Production's footer link lists carry no bullets and no indent - they read
   as stacked links, not as lists. WordPress renders a list block with discs
   and a 40px indent by default, which is what was showing. */
.kb-footer-list {
  list-style: none;
  padding-left: 0;
  margin-left: 0;
}

/* Production's social icons are white circles with a black glyph at 36px.
   WordPress ships the social-links block in each platform's brand colour -
   Facebook blue, Instagram magenta - which is what was on staging and is
   nothing like the live site. The white circle is invisible against the
   white page by design; what you see is the black glyph. */
.kb-rail-social .wp-social-link,
.wp-block-social-links .wp-social-link {
  background-color: var(--wp--preset--color--surface);
  color: var(--wp--preset--color--ink);
  width: 36px;
  height: 36px;
  padding: 0;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.kb-rail-social .wp-social-link svg,
.wp-block-social-links .wp-social-link svg {
  width: 18px;
  height: 18px;
  fill: currentColor;
}

.kb-rail-social .wp-social-link:hover,
.wp-block-social-links .wp-social-link:hover {
  color: var(--wp--preset--color--brand-red);
}

/* --- Newsletter -------------------------------------------------------- */
.kb-newsletter p {
  margin-bottom: 20px;
}

/* --- Phones ------------------------------------------------------------ */
/* The fluid product row and the 60px/90px section padding are desktop
   numbers. On a phone they waste most of a screen, which is the exact
   problem that was fixed on the live site in September. !important is
   required here to reach the inline block spacing - see the note at the top
   of this section. */
@media (max-width: 781px) {
  .kb-product-row {
    padding-block: 30px !important;
  }

  /* This row sets its own padding-inline, so it does not inherit the gutter
     from the root-padding property the way has-global-padding blocks do. */
  .kb-product-row > .wp-block-columns {
    width: 100%;
    padding-inline: 1.5rem;
  }

  .kb-product-row > .wp-block-columns > .wp-block-column:last-child {
    padding: 20px 0 0;
  }

  .kb-trust {
    margin-block: 30px;
    padding-block: 20px !important;
  }

  /* Full-bleed bands now reach the edge on phones, so their own children
     have to carry the gutter that body padding used to give them. */
  .kb-trust .wp-block-column {
    padding: 10px 1.5rem;
  }

  .kb-afterglow {
    padding-block: 40px !important;
  }

  /* Both of these carry padding-left:0 / padding-right:0 as inline block
     attributes from the page content, so the gutter has to be forced. Each
     paints its artwork on a ::before that is inset against the padding box,
     so the band still reaches both edges - only the text moves in. */
  .kb-afterglow-band,
  .kb-newsletter {
    padding-inline: 1.5rem !important;
  }

  .kb-case,
  .kb-newsletter {
    padding-block: 30px !important;
  }

  .kb-case > .wp-block-columns {
    width: 100%;
    padding-inline: 1.5rem;
  }

  .kb-case > .wp-block-columns > .wp-block-column:first-child {
    padding: 0;
  }

  .kb-case > .wp-block-columns > .wp-block-column:last-child {
    padding: 20px 0 0;
  }
}

/* -------------------------------------------------------------------------
   11. Nested-constrained width corrections

   A `constrained` layout sets the measure for a container's CHILDREN. It does
   not widen the container itself - that is still sized by ITS parent's
   constraint. So `contentSize: 895px` on the page's post-content, sitting
   inside a main whose measure is theme.json's 700px, produced an 895px rule
   that nothing could reach: the element itself was still 700 wide.

   Both blocks below hit this. The fix is to give the element its own width,
   not to widen theme.json (which would change every template) and not to make
   them alignwide (which would jump them to 1440).
   ---------------------------------------------------------------------- */
/* Widened from 895px to 1390px on 15 Sep. Production runs its page content at
   1390px, and at 895 the About page's two columns came out 591 + 269 against
   live's 935 + 375 - which is what Jake saw as "the copy section is too
   narrow". This is a max-width, so nothing below 1390 is affected: narrower
   screens simply fill the space they have and the gutters are unchanged.
   Measured after the change at 1920, 1440, 1280, 1024, 768 and 390 - no
   overflow at any of them. */
.wp-block-post-content.kb-page-body {
  max-width: 1390px;
  margin-inline: auto;
}

.kb-shop-copy {
  max-width: 860px;
  margin-inline: auto;
}

/* -------------------------------------------------------------------------
   12. Cart and checkout button hierarchy

   Production paints the secondary cart actions black and reserves red for the
   one action it actually wants pressed. The rebuild had made all three red -
   a knock-on from the homepage decision to use red buttons - which flattened
   the hierarchy: "Proceed to checkout" looked identical to "Update cart" and
   "Apply coupon" sitting right next to it.

   WooCommerce's own convention does the sorting for us: it puts `.alt` on the
   primary action (Proceed to checkout, Place order) and leaves it off the
   secondary ones. No per-button selectors needed, so buttons WooCommerce adds
   later land on the right side of this automatically.

   Scoped to cart and checkout only - everywhere else on the site, buttons stay
   red per the homepage decision (decisions-log, 7 Sep).
   ---------------------------------------------------------------------- */
.woocommerce-cart .woocommerce .button:not(.alt),
.woocommerce-checkout .woocommerce .button:not(.alt),
.woocommerce-account .woocommerce .button:not(.alt) {
  background-color: var(--wp--preset--color--ink);
  color: var(--wp--preset--color--surface);
}

.woocommerce-cart .woocommerce .button:not(.alt):hover,
.woocommerce-cart .woocommerce .button:not(.alt):focus,
.woocommerce-checkout .woocommerce .button:not(.alt):hover,
.woocommerce-checkout .woocommerce .button:not(.alt):focus {
  background-color: var(--wp--preset--color--brand-red);
  color: var(--wp--preset--color--surface);
}

/* The primary action, stated explicitly rather than left to inheritance:
   WooPayments styles #place_order itself (production's is the plugin's own
   purple), and without this the one button that matters would look like
   whichever plugin loaded last. */
.woocommerce-cart .woocommerce .button.alt,
.woocommerce-checkout .woocommerce .button.alt,
.woocommerce-checkout #place_order {
  background-color: var(--wp--preset--color--brand-red);
  color: var(--wp--preset--color--surface);
}

.woocommerce-cart .woocommerce .button.alt:hover,
.woocommerce-checkout .woocommerce .button.alt:hover,
.woocommerce-checkout #place_order:hover {
  background-color: var(--wp--preset--color--ink);
  color: var(--wp--preset--color--surface);
}

/* -------------------------------------------------------------------------
   13. AfterGlow: breathing room above the "Read the blog" button

   Jake, 7 Sep: the cards and the button sat too close together. Measured at
   11px, which reads as the button belonging to the last card rather than to
   the section.

   Production has no equivalent to match - its AfterGlow section ends with the
   cards and has no button - so 2.5rem is a judgement call, not a ported
   measurement.

   WRITTEN AS AN EXPLICIT VALUE ON PURPOSE. The first attempt used
   var(--wp--preset--spacing--40) and rendered at 16px, because that variable
   does NOT hold this theme's value - see the note at the end of this file.
   Do not "tidy" this back into a preset var.
   ---------------------------------------------------------------------- */
.kb-afterglow .wp-block-buttons {
  margin-top: 2.5rem;
}

/* -------------------------------------------------------------------------
   KNOWN ISSUE - the theme's spacing scale is not the one being served.

   theme.json declares spacingScale steps 0 and its own spacingSizes
   (10=0.5rem, 20=1, 30=1.5, 40=2.5, 50=4, 60=6rem). What the browser actually
   receives from global-styles-inline-css is WordPress's DEFAULT generated
   scale:

       20=0.44rem  30=0.67rem  40=1rem  50=1.5rem  60=2.25rem  70=3.38  80=5.06

   Only slug 10 survives, because it is the one slug that does not collide with
   a core-generated step. So every `var:preset|spacing|NN` in the templates is
   resolving to a core value, not the theme's.

   THIS HAS NOT BEEN "FIXED" DELIBERATELY. Every page was measured against
   production with these effective values in place and matches it. Forcing the
   intended scale would change padding on every template at once - main padding
   40 would go 16px to 40px, 50 would go 24px to 64px - and would introduce
   regressions into pages that currently render correctly.

   Resolve it during the redesign, not during the port: give the custom sizes
   slugs that cannot collide (kb-10 ... kb-60), update the template references,
   and re-measure. Until then, write spacing values explicitly in this file.
   ---------------------------------------------------------------------- */

/* -------------------------------------------------------------------------
   14. Mobile touch targets

   Measured on staging at 375px against production, 9 Sep 2026. Production is
   not a high bar here - its own controls are 42px and its footer links 42px -
   but three of the rebuild's were meaningfully worse, and one of those is the
   single most-used control on a phone:

     menu toggle    24 x 24   <- production's is larger; this is the worst one
     mini-cart      43 x 43   <- one pixel short of the 44px floor
     slider arrows  32 x 26   <- production has no equivalent to compare

   44px is the long-standing floor in both Apple's and Google's guidance, and
   it is roughly the contact patch of an adult fingertip. Below it, tapping
   becomes a game of aim.

   The VISUAL size of each control is unchanged - only the hit area grows, via
   padding and min-height. Nothing moves on screen.
   ---------------------------------------------------------------------- */
@media (max-width: 1024px) {
  /* Menu toggle. The icon stays 24px; the button around it becomes 44x44. */
  .wp-block-navigation__responsive-container-open,
  .wp-block-navigation__responsive-container-close {
    min-width: 44px;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  .wc-block-mini-cart__button {
    min-width: 44px;
    min-height: 44px;
  }

  /* Links inside the opened mobile menu: production gives its footer links a
     42px row, so this matches and slightly betters it. */
  .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation-item__content {
    display: block;
    padding-block: 12px;
    min-height: 44px;
  }
}

/* Slider arrows. The drawn arrow stays 32x26 - the box around it grows, which
   is why the padding is negative-margined back out: the arrow must not shift
   away from the edge it sits against. */
@media (max-width: 1024px) {
  .kb-slider-arrow {
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .kb-slider-arrow--prev {
    left: 24px;
  }

  .kb-slider-arrow--next {
    right: 24px;
  }
}

/* Product card titles in a grid are the tap target for the whole card as far
   as a thumb is concerned, but the link itself was only 17px tall. Give the
   line some vertical padding so the reachable area matches the text people
   are aiming at. */
@media (max-width: 1024px) {
  .wp-block-post-template .wp-block-post-title a,
  .wc-block-grid__product-title a {
    display: inline-block;
    padding-block: 6px;
  }
}

/* Product reviews. The block needs its inner blocks to render anything at all -
   an empty woocommerce/product-reviews outputs a wrapper of height 0, which is
   exactly what 0.9.0 shipped. Structure mirrors core/comments. */
.wp-block-woocommerce-product-reviews {
  margin-top: 4rem;
}

.wp-block-woocommerce-product-reviews .wp-block-woocommerce-product-review-template > li {
  list-style: none;
  padding-block: 24px;
  border-top: 1px solid var(--wp--preset--color--hairline);
}

.wp-block-woocommerce-product-reviews .wp-block-woocommerce-product-review-template {
  margin: 0;
  padding: 0;
}

/* -------------------------------------------------------------------------
   15. Cart: the remove-item control on mobile

   Measured at 375px on 9 Sep: WooCommerce's "x" remove link renders 26 x 26
   and sits immediately beside the product-name link in the same row.

   Small is bad; small AND destructive AND adjacent to something people
   deliberately tap is worse. A mis-tap here silently empties a line item on
   the last screen before payment, and there is no undo.

   44 x 44 hit area, the visual glyph unchanged. Everything else on cart and
   checkout measured clean at 375px: no horizontal scroll, table and form both
   fit inside 346px, all 22 checkout inputs at 16px, Place order 312 x 49.
   ---------------------------------------------------------------------- */
@media (max-width: 767px) {
  .woocommerce-cart .product-remove a.remove,
  .woocommerce-cart td.product-remove a {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin: 0;
    font-size: 20px;
    line-height: 1;
  }

  /* Keep the destructive control off the product link it sits beside. */
  .woocommerce-cart td.product-remove {
    padding-right: 8px;
  }
}

/* -------------------------------------------------------------------------
   16. Horizontal padding on mobile — the real fix

   Jake spotted the homepage running edge to edge on his phone. It was not the
   homepage: it was every page. Measured at 375px, 26 of 44 text elements on
   the product page and 35 of 42 on the homepage sat at left: 7.

   Seven pixels, not zero - and that number is the tell. theme.json DOES set
   root padding, and `useRootPaddingAwareAlignments` IS on, so the machinery
   was working correctly the whole time. The value it was given was
   `var(--wp--preset--spacing--20)`, which this theme intends to be 1rem and
   which WordPress's default scale actually serves as **0.44rem = 7px**. See
   the known-issue note further down: the theme's spacing scale is not the one
   being served.

   So this is that bug producing a visible defect, and the note above it -
   "every page was measured against production with these effective values and
   matches it" - was wrong on this point. Horizontal padding does not match:
   production sits at 30px, the rebuild at 7px.

   Fixed by giving the root padding an explicit value rather than the preset
   that does not resolve. Scoped BELOW 1025px on purpose: at desktop widths the
   left rail's 270px inset comes from this same body padding, and overriding it
   there drags every page out from under the rail. Verified at 1440px - main
   stays at 270, the rail stays at 0:270, nothing moves.

   With this in place, flush count is 0 on the homepage, product page and shop
   archive, and no page scrolls sideways.

   Correction, 9 Sep: the first version of this fix put the gutter on body's
   own padding as well as the custom property, and set the property on :root.
   Both were wrong. WordPress declares these properties on `body`, so a :root
   declaration never reaches anything; and padding on body cannot be cancelled
   by `.alignfull`, whose rule only takes back `var(--wp--style--root--padding-*)`.
   The result was that every full-bleed band - the homepage hero, the trust
   strip, the AfterGlow band, the shop hero - sat 24px in from both edges on a
   phone while production runs them to the edge. Measured 336@24 against
   production's 360@0.

   So the gutter now lives ONLY in the property, declared on body. Blocks with
   `has-global-padding` pick it up, `.alignfull` takes it back off, and the
   bands reach the edge again.
   ---------------------------------------------------------------------- */
@media (max-width: 1024px) {
  body {
    padding-left: 0;
    padding-right: 0;
    --wp--style--root--padding-left: 1.5rem;
    --wp--style--root--padding-right: 1.5rem;
  }
}

/* -------------------------------------------------------------------------
   17. Footer, measured against production at 375px

   Production's footer links are body-text coloured, not underlined, 17px, on
   42px rows. The rebuild had them brand-red, underlined, 14px, on 21px rows -
   because theme.json styles every link that way globally, which is right for
   links inside copy and wrong for a stacked footer menu.
   ---------------------------------------------------------------------- */
.kb-footer a {
  color: var(--wp--preset--color--body-text);
  text-decoration: none;
}

.kb-footer a:hover,
.kb-footer a:focus {
  color: var(--wp--preset--color--brand-red);
  text-decoration: none;
}

/* Production's footer headings are sentence case. The theme's global h4 is a
   small uppercase label, which is right elsewhere and wrong here. */
.kb-footer h4 {
  text-transform: none;
  letter-spacing: normal;
}

/* 42px rows on production. The padding also buys these links a 44px touch
   target, which they did not have. */
.kb-footer .kb-footer-list li {
  margin-block: 0;
}

.kb-footer .kb-footer-list li a {
  display: inline-block;
  padding-block: 10px;
}

.kb-footer .wp-social-link {
  width: 36px;
  height: 36px;
}

/* ------------------------------------------------------------------------
   18. Blog index (The AfterGlow)

   Production opens /blog/ with a 450px band carrying the pre-tinted
   Legs-on-bed artwork at its natural size, centred, with a 18px uppercase
   eyebrow over a 60px Lustria h1 in white. The staging index had no band and
   no h1 at all, which cost the page its only heading as well as the look.

   The image file is already tinted, so it goes on the element rather than a
   ::before at reduced opacity - there is nothing to fade.
   --------------------------------------------------------------------- */
.kb-blog-hero {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 450px;
  padding: 40px 20px;
  background-image: url("https://kinkbox.com/wp-content/uploads/2025/12/KB-Hero-Images-Legs-on-bed-2500x700-Tint1.avif");
  background-position: 50% 50%;
  background-size: auto;
  background-repeat: no-repeat;
  background-color: var(--wp--preset--color--ink);
}

.kb-blog-hero > * {
  margin-block: 0;
}

/* 18px/500, and production leaves 30px between it and the h1. */
.kb-blog-hero .kb-eyebrow {
  font-size: 18px;
  font-weight: 500;
  margin-block-end: 30px;
}

/* Production's h1 runs 60px on a 78px line at both breakpoints - it does not
   shrink on phones, which is why the hero preset is no longer fluid. */
.kb-blog-hero h1 {
  line-height: 1.3;
  font-weight: 500;
}

/* Card titles are 28px on a 36.4px line in production, not the 19px the
   large preset was giving them, and the excerpt runs at body size. */
.kb-blog-list .wp-block-post-title {
  line-height: 1.3;
  margin-block: 16px 8px;
}

.kb-blog-list .wp-block-post-excerpt {
  margin-block-start: 0;
}

/* ------------------------------------------------------------------------
   19. Interior page hero bands (About, Playlists)

   Production gives these two pages a full-bleed band - 350px on desktop,
   200px on phones - with the artwork on a ::before at 0.65 opacity and the
   page heading centred over it in white. Staging had neither, so both pages
   opened on a bare black heading against white.

   The band is printed by the page template, not the page content, so the
   first h1 in the content (which carries the same words) is hidden here.
   --------------------------------------------------------------------- */
.kb-page-hero {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 350px;
  padding: 40px 20px;
  isolation: isolate;
  background-color: var(--wp--preset--color--ink);
}

@media (max-width: 1024px) {
  .kb-page-hero {
    min-height: 200px;
    padding: 24px 20px;
  }
}

.kb-page-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-position: 50% 50%;
  background-size: cover;
  background-repeat: no-repeat;
  opacity: 0.65;
}

.kb-page-hero > * {
  margin-block: 0;
}

.kb-page-hero--about::before {
  background-image: url("https://kinkbox.com/wp-content/uploads/2025/12/KB-Hero-Images-Kissing-2500x700-Original.avif");
}

.kb-page-hero--playlists::before {
  background-image: url("https://kinkbox.com/wp-content/uploads/2026/02/Playlist.avif");
}

/* The page content still carries the heading the band now shows. */
.kb-hero-page > h1:first-child {
  display: none;
}

/* ------------------------------------------------------------------------
   20. Related posts under a single post

   Production closes every post with nine related posts - three up on desktop,
   one up on a phone - and they are the only internal links most posts carry.
   The rebuild had none, which is most of why a post measured 7593px against
   production's 12224px.
   --------------------------------------------------------------------- */
.kb-related > h2 {
  margin-block-end: 24px;
}

.kb-related .wp-block-post-title {
  line-height: 1.3;
  margin-block: 16px 8px;
}

.kb-related .wp-block-post-excerpt {
  margin-block-start: 0;
}

/* ---------------------------------------------------------------------
   21. No gap above the first band on any page.

   WordPress emits `:where(.wp-site-blocks) > * { margin-block: <blockGap> 0 }`,
   so every top-level block - the header template part and `main` included -
   carries a 20px top margin once blockGap is set (0.9.7). The header part is
   zero-height on desktop, so its margin and main's collapse together and
   escape to the top of the document, putting a 20px white strip above the
   hero on EVERY page. Jake spotted it on the homepage and the shop archive;
   it was on all twelve templates.

   Zeroing the top-level children is not quite enough on its own. On the shop
   archive and the single-product template the first in-flow element inside
   the content wrapper is an empty WooCommerce notices div, so the BAND is the
   second child and its own 20px gap collapses out to the top instead. The
   band classes are therefore zeroed directly as well.

   The footer keeps its 20px, which is the gap between the last section and
   the footer and is wanted.
   --------------------------------------------------------------------- */
.wp-site-blocks > *:not(footer) {
  margin-block-start: 0;
}

.kb-hero,
.kb-shop-hero,
.kb-blog-hero,
.kb-page-hero,
.kb-page-banner {
  margin-block-start: 0;
}

/* Production runs the product price at 28px BOLD. WooCommerce's own amount
   span inherits the body weight, so the price came out 28px regular and read
   lighter than production's. The weight belongs on the amount span, not on the
   block wrapper - the wrapper is body text at 17px. */
.wp-block-woocommerce-product-price .woocommerce-Price-amount,
.woocommerce div.product p.price .woocommerce-Price-amount {
  font-weight: 700;
}

/* Product short description (woocommerce/product-summary).

   Production's short description is authored HTML - paragraphs, an h5 and a
   bullet list - and production styles it with the BODY font, not the display
   font. theme.json styles every h5 as Lustria 19.55px/400 with a 32px rhythm,
   which made "Features you'll love" read as a section heading floating in
   whitespace instead of a label above its own list.

   Measured against kinkbox.com/product/the-kinkbox/ on 13 Sep 2026:
   h5 20px/600 Lato on 26px, 8px above and 16px below; paragraphs 0 above and
   20px below; the list tight against the h5. */
.wp-block-woocommerce-product-summary p {
  margin-block: 0 20px;
}

.wp-block-woocommerce-product-summary h5 {
  font-family: var(--wp--preset--font-family--body);
  font-size: 20px;
  font-weight: 600;
  line-height: 26px;
  margin-block: 8px 16px;
}

/* Each bullet in the short description is its own single-item <ul> - that is
   how the copy was authored, on live as well as here - so the space between
   bullets is the margin BETWEEN LISTS, not between list items. Production runs
   those lists at margin 0 top and bottom, which closes the bullets up to plain
   line spacing. Setting only margin-block-start left a 17px gap under each
   one. */
.wp-block-woocommerce-product-summary ul {
  margin-block: 0;
}

/* Product reviews.

   The avatar is removed in functions.php (WooCommerce's
   woocommerce_review_display_gravatar), but its 50px reserve lives in
   WooCommerce's own stylesheet at
   `.woocommerce #reviews #comments ol.commentlist li .comment-text`, so the
   review body stayed indented against nothing. The selector below matches that
   specificity rather than fighting it with !important.

   Stars: the filled glyphs take the element's colour, which was body ink. They
   are brand red now. The empty state is the same red at 25% rather than
   WooCommerce's lilac #CFC8D8, which belonged to no palette on this site. */
.woocommerce #reviews #comments ol.commentlist li .comment-text {
  margin-left: 0;
}

.woocommerce .star-rating,
.woocommerce .star-rating span,
.woocommerce p.stars a {
  color: var(--wp--preset--color--brand-red);
}

.woocommerce .star-rating::before {
  color: rgba(145, 31, 29, 0.25);
}

/* Review form fields.

   The whole review form was browser-default: 13.33px text in 21px-tall inputs
   with 2px padding, on a site that runs 17px body copy. Jake asked for taller
   name and email fields; the rest of the form had the same problem, so all
   three fields are brought onto the same footing as checkout - 15px text, 14px
   padding, 50px minimum, and the checkout border.

   box-sizing matters here: these inputs are content-box by default, so the
   min-height and the padding would otherwise add up instead of the padding
   sitting inside the box.

   The 16px mobile input floor in section 14 still applies on top of this and
   must keep winning - 15px inputs make iOS zoom the page on focus. */
#reviews #respond input[type="text"],
#reviews #respond input[type="email"],
#reviews #respond input[type="url"],
#reviews #respond textarea {
  box-sizing: border-box;
  font-family: var(--wp--preset--font-family--body);
  font-size: 0.9375rem;
  padding: 14px;
  border: 1px solid rgba(32, 7, 7, 0.8);
  border-radius: 0;
  background-color: #fff;
}

#reviews #respond input[type="text"],
#reviews #respond input[type="email"],
#reviews #respond input[type="url"] {
  width: 100%;
  max-width: 420px;
  min-height: 50px;
}

#reviews #respond textarea {
  width: 100%;
  min-height: 140px;
}

/* Product description tab.

   The description is authored HTML with real heading tags in it: an h4 lead-in
   and an h5 closing call to action. theme.json styles those two site-wide as a
   small uppercase Lato label (h4) and Lustria 14px (h5), so inside a product
   description the lead-in shouted in capitals and the closing line came out
   SMALLER than the body copy it sat under.

   Production (measured on kinkbox.com/product/the-collector/, 13 Sep 2026)
   styles both with the body font at weight 600, sentence case, no tracking:
   h4 24px/31.2, h5 20px/26, both 35px above and 10px below. Paragraphs run 0
   above and 20px below rather than the theme's symmetric 17px.

   Scoped to the tab panels so the site-wide h4 label style is untouched
   everywhere else. */
.woocommerce-Tabs-panel h4,
.woocommerce-Tabs-panel h5 {
  font-family: var(--wp--preset--font-family--body);
  font-weight: 600;
  text-transform: none;
  letter-spacing: normal;
  margin-block: 35px 10px;
}

.woocommerce-Tabs-panel h4 {
  font-size: 24px;
  line-height: 31.2px;
}

.woocommerce-Tabs-panel h5 {
  font-size: 20px;
  line-height: 26px;
}

.woocommerce-Tabs-panel p {
  margin-block: 0 20px;
}

/* -------------------------------------------------------------------------
   22. Shop grid: the classic Add to Cart link

   The woocommerce/product-button block is filtered in functions.php to output
   WooCommerce's classic <a class="button add_to_cart_button"> instead of the
   Interactivity API <button>, because the block version was inert on this
   install. See the docblock on render_block_woocommerce/product-button.

   That swap costs two things the block gave us for free, and both are put
   back here.

   1. The block wrapper carried has-text-align-center. The replacement wrapper
      centres its own child instead.

   2. Specificity. theme.json emits its button styles as
      :root :where(.wp-element-button, .wp-block-button__link), and :where()
      contributes nothing, so WooCommerce's own .woocommerce a.button (0,1,1)
      outranks it and the button would render WooCommerce grey. The rules
      below are written at 0,2,1 so the brand red actually wins. Do not
      "simplify" them down to a single class - trap 4 in the gotchas doc.
   ---------------------------------------------------------------------- */
.kb-product-button {
  display: flex;
  justify-content: center;
  margin-block-start: 18px;
}

.woocommerce a.kb-loop-add-to-cart,
.woocommerce-page a.kb-loop-add-to-cart {
  display: inline-block;
  background-color: var(--wp--preset--color--brand-red);
  color: var(--wp--preset--color--surface);
  font-family: var(--wp--preset--font-family--body);
  font-size: 0.875rem;
  font-weight: 400;
  letter-spacing: 1.1px;
  text-transform: uppercase;
  text-decoration: none;
  line-height: 1.2;
  border: 0;
  border-radius: 0;
  padding: 14px 30px;
}

.woocommerce a.kb-loop-add-to-cart:hover,
.woocommerce a.kb-loop-add-to-cart:focus,
.woocommerce-page a.kb-loop-add-to-cart:hover,
.woocommerce-page a.kb-loop-add-to-cart:focus {
  background-color: var(--wp--preset--color--ink);
  color: var(--wp--preset--color--surface);
}

/* WooCommerce's AJAX script appends "View cart" after a successful add and
   parks a spinner on the button itself. Both are wanted - the second is the
   only feedback a customer gets that the click landed. */
.woocommerce a.kb-loop-add-to-cart.loading {
  opacity: 0.72;
}

/* The "View cart" link WooCommerce appends after a successful add. Kept, not
   hidden: with the cart badge up in the header it is the only confirmation at
   eye level that the click did anything. The wrapper is a flex row, so it
   needs wrapping or it sits alongside the button and squashes it. */
.kb-product-button {
  flex-wrap: wrap;
  gap: 10px;
}

.woocommerce .kb-product-button .added_to_cart {
  flex-basis: 100%;
  text-align: center;
  font-family: var(--wp--preset--font-family--body);
  font-size: 0.8125rem;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  text-decoration: underline;
  color: var(--wp--preset--color--ink);
}

/* -------------------------------------------------------------------------
   23. Homepage media height while the columns are side by side

   Kitty, 13 Sep: "The 3 product rows - significant extra space between,
   laptop only. Same issue between other homepage sections too."

   The cause was rule 8's `aspect-ratio: 2 / 1` on the slider image. That
   number came from measuring production at ONE viewport - 1920, where the
   image really is 974x490 - and generalising. Production does not hold a
   ratio; it holds a HEIGHT. Its product image is 490px tall at 1920 and
   still 490px tall at 1440 (669x490, so 1.37:1, nowhere near 2:1).

   Holding the ratio instead means the image shrinks as the column narrows
   while the text column GROWS, because narrower text wraps more:

   | viewport | staging image | text column | imbalance |
   |---|---|---|---|
   | 1920 | 956x478 | 446-492 | none, which is why this was never seen |
   | 1440 | 652x326 | 523-630 | ~300px |
   | 1280 | 548x274 | 610-656 | ~380px |

   The image is vertically centred, so that imbalance renders as a thick
   empty band above AND below it. At 1920 the two columns happen to balance,
   which is exactly why the defect was invisible to every check run so far.

   Fixing it: match production and pin the height while the columns are side
   by side. Mobile is untouched - the stacked layout was reviewed and passed,
   and the 2:1 frame is right there.

   Residual, accepted: below about 1300 the text column is still taller than
   490px, so a little imbalance remains. Production avoids that by stacking
   its columns at ~1280 rather than at core's 781. Moving our breakpoint to
   match is a layout change across the whole page and is not worth doing the
   day before a cutover - revisit after launch.
   ---------------------------------------------------------------------- */
@media (min-width: 782px) {
  .kb-product-row .kb-slider img {
    aspect-ratio: auto;
    height: 490px;
  }

  /* Same imbalance, same cause, in the Case band: the video kept its natural
     ratio (565x318 at 1440) beside a 582px text column. The clip is abstract
     colour transitions, so cropping it costs nothing. */
  .kb-case .kb-video video {
    height: 460px;
    object-fit: cover;
  }
}

/* -------------------------------------------------------------------------
   24. AfterGlow cards: drop the reserved heights once the cards stack

   Kitty, 13 Sep: "Blog cards at bottom are level with each other - too much
   space between blog title and 1st paragraph. Mobile only."

   Rule 10 fixes the title box at 2.6em and the excerpt box at 4.5em so that
   three cards SIDE BY SIDE stay level whatever their titles do. Stacked,
   there is no row to keep level, and a title that needs only one line leaves
   a whole reserved line of white between it and the excerpt.

   Not reproducible in Chrome at 390, 430 or 768 - all three titles run to
   exactly two lines there, so the boxes are filled and the gap measures a
   consistent 17px. Safari on iOS lays out text on different metrics, which is
   the likely difference. Rather than guess at the metric, this removes the
   mechanism: below the stacking breakpoint the reserved heights serve no
   purpose at all, so they come off and each card is as tall as its own
   content.
   ---------------------------------------------------------------------- */
@media (max-width: 781px) {
  .kb-afterglow .wp-block-post-title,
  .kb-afterglow .wp-block-post-excerpt {
    min-height: 0;
    max-height: none;
  }
}

/* -------------------------------------------------------------------------
   25. Cart: contain the floats so the footer stops collapsing

   Kitty, 13 Sep: "Cart page ('view cart') totally broken. Unable to check if
   I can change quantity/update, total recalculates, etc. Checkout is oddly
   working fine though, if I go straight to that page."

   WooCommerce's classic cart floats its two halves: form.woocommerce-cart-form
   left, .cart_totals right. Nothing in the rebuild contained them, so `main`
   ended shorter than its own contents (559px against floats 605px tall) and
   the footer, with clear:none, flowed straight into the narrow channel
   BETWEEN the two floats.

   Measured on the cart at 1920: footer columns 31px, 4px, 4px and 4px wide
   and 1412px tall. Four pixels is narrower than one character, so every word
   in the footer wrapped to one letter per line - which is exactly what
   Kitty's screenshot shows, a vertical ladder of single letters where the
   footer should be.

   Not laptop-only, despite the report. It reproduces at 1920 as well; she
   only had a laptop to look at. It does not happen on a phone because the
   floats stack there.

   display:flow-root makes main establish a block formatting context, so it
   contains the floats and ends below them. The clear on the footer is the
   belt to that pair of braces: if a future WooCommerce release floats
   something the container misses, the footer still starts on a clean line.
   ---------------------------------------------------------------------- */
.woocommerce-cart main,
.woocommerce-checkout main,
.woocommerce-account main {
  display: flow-root;
}

.wp-site-blocks > footer {
  clear: both;
}

/* -------------------------------------------------------------------------
   26. Blog index and archives: post titles black, like production

   Jake, 15 Sep: "the heading for each post is the wrong color."

   Rule 11 already turns post titles black, but it is scoped to
   .kb-afterglow - the band on the HOMEPAGE. The blog index at
   /the-afterglow/ and the category archives are different templates
   entirely, so the rule never reached them and their titles fell back to the
   link colour, brand red.

   Measured against production on the same page: post card titles are
   rgb(0, 0, 0), Lustria 28px, weight 400, no underline. Staging was
   rgb(145, 31, 29).

   Both templates now carry .kb-blog-list on the query, so one selector
   covers the index and every archive. :visited is in the list for the same
   reason as rule 11 - without it, a post the reader has already opened comes
   back red while its neighbours stay black.
   ---------------------------------------------------------------------- */
.kb-blog-list .wp-block-post-title,
.kb-blog-list .wp-block-post-title a,
.kb-blog-list .wp-block-post-title a:visited {
  color: var(--wp--preset--color--ink);
  text-decoration: none;
}

/* Two lines, then an ellipsis - production clamps these and so must we, or a
   96-character title runs to three lines and knocks its row out of line.
   Measured on live: -webkit-line-clamp 2, overflow hidden, 73px tall, and the
   full title still in the DOM so search engines and screen readers get all of
   it. display:-webkit-box is what actually draws the ellipsis; the clamp
   properties do nothing without it, which is the trap rule 10 ran into. */
.kb-blog-list .wp-block-post-title {
  overflow: hidden;
}

/* The clamp goes on the ANCHOR, not the h3. Core gives .wp-block-post-title
   display:flow-root through its layout support, and that beat a
   display:-webkit-box set here - measured, the h3 stayed flow-root and a
   96-character title still ran to three lines. The anchor carries no such
   rule, so the clamp holds there. */
.kb-blog-list .wp-block-post-title a {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.kb-blog-list .wp-block-post-title a:hover,
.kb-blog-list .wp-block-post-title a:focus {
  color: var(--wp--preset--color--brand-red);
}

/* -------------------------------------------------------------------------
   27. Blog cards: the "Read More" link

   Jake, 15 Sep: "There's no read more under each post."

   Production puts one under every card on the blog index and on the homepage
   band. Measured on live:

     colour        rgb(145, 31, 29)  - brand red
     font          Lato 15px / 400   - the body face, not the heading face
     transform     uppercase
     letterspacing 1.1px
     decoration    none, no background, no padding, no border
     gap above     29px from the excerpt

   The source text is "Read More" and the uppercase is done in CSS, so a
   screen reader says it normally rather than spelling it out.

   This is a core/read-more block rather than post-excerpt's own moreText,
   deliberately: as a separate block it sits outside the excerpt box, which
   on the homepage is height-clamped to keep the cards level. Inside the
   excerpt it would be clipped.
   ---------------------------------------------------------------------- */
.kb-blog-list .wp-block-read-more,
.kb-afterglow .wp-block-read-more {
  display: inline-block;
  margin-block-start: 29px;
  color: var(--wp--preset--color--brand-red);
  font-family: var(--wp--preset--font-family--body);
  font-size: 15px;
  font-weight: 400;
  line-height: 20px;
  letter-spacing: 1.1px;
  text-transform: uppercase;
  text-decoration: none;
}

.kb-blog-list .wp-block-read-more:hover,
.kb-blog-list .wp-block-read-more:focus,
.kb-afterglow .wp-block-read-more:hover,
.kb-afterglow .wp-block-read-more:focus {
  color: var(--wp--preset--color--ink);
  text-decoration: underline;
}

/* The excerpt above carries 20px of its own bottom margin, which stacked on
   top of the 29px above and measured 49px against production's 29px. Zero the
   excerpt's margin here so the 29px above is the whole gap and the number in
   this file is the number on the page. */
.kb-blog-list .wp-block-post-excerpt {
  margin-block-end: 0;
}

/* -------------------------------------------------------------------------
   28. FAQ questions, matched to production

   Jake, 15 Sep: the FAQ "is completely different than page on the live site."

   Production renders each question as an h3 in Lustria at 19px. The rebuild
   renders it as a bare <summary>, which inherited body type - Lato at 17px -
   so every question was smaller, in the wrong face, and carried no heading
   level at all. Twenty-seven questions and not one h3 on the page.

   Measured on live: Lustria 19px / 26.6px, weight 400, colour rgb(38,38,38),
   wrapper padding 20px 44px 20px 0.

   The heading itself is injected by a filter on render_block_core/details in
   functions.php rather than written into the page, because the details block
   stores its summary as an attribute: an <h3> typed into the page content
   would render, but the block editor would flag the block as corrupted the
   next time anyone opened the page. The filter keeps the content clean and
   travels with the theme.

   display:inline on the h3 so the accordion's disclosure triangle keeps its
   own layout - a block-level heading inside a summary pushes the marker onto
   its own line.
   ---------------------------------------------------------------------- */
.kb-page-body .wp-block-details > summary {
  padding-block: 20px;
  padding-inline-end: 44px;
  cursor: pointer;
}

.kb-faq-question {
  display: inline;
  margin: 0;
  font-family: var(--wp--preset--font-family--display);
  font-size: 19px;
  line-height: 26.6px;
  font-weight: 400;
  color: var(--wp--preset--color--body-text);
}

/* -------------------------------------------------------------------------
   29. FAQ: the jump-to nav, section rules and accordion chevrons

   Jake sent production's FAQ as the target. Everything here is measured off
   that page rather than eyeballed from the screenshot.

   Worth knowing: production's FAQ is NOT Elementor. It is the same
   details/summary markup we use, styled by a custom rule set - its wrapper
   even carries the class `kb-faq-nav`. So this is not a port, it is the same
   component finally given its styling.

   Measured on live:

     nav box        bg #F4F4F4, border-top 3px #911F1D, padding 28px 32px,
                    margin-bottom 56px
     nav label      Lato 13px/600, uppercase, letter-spacing 1.3px, #262626,
                    margin-bottom 16px
     nav links      two columns, brand red, no underline, rows 33px apart
     section rule   h2 32px with border-bottom 2px #911F1D, padding-bottom 14,
                    margin 56px 0 8px
     row divider    border-bottom 1px #E2E2E2 on the details element
     chevron        9x9 box, right/bottom borders 2px #911F1D, rotated 45deg,
                    absolute at right 10px / top 28px, 0.2s transition;
                    open state rotates to 225deg and drops to top 32px
     answer         Lato 17px/25.5, padding-right 44px, 24px clear at the end

   The 44px right padding on both summary and answer is what keeps the text
   clear of the chevron. list-style:none plus the -webkit-details-marker rule
   removes the browser's own disclosure triangle - both are needed, Safari
   only honours the second.
   ---------------------------------------------------------------------- */
.kb-faq-nav {
  background-color: var(--wp--preset--color--surface-muted);
  border-top: 3px solid var(--wp--preset--color--brand-red);
  padding: 28px 32px;
  margin-block: 0 56px;
}

/* The group is a constrained layout, so core caps its children at
   theme.json's 700px measure and centres them - the label came out 700 wide
   with 313px of auto margin either side instead of spanning the box. Written
   at 0,2,0 so it beats core's own layout rule whichever order they load in. */
.kb-page-body .kb-faq-nav > * {
  max-width: none;
  margin-inline: 0;
}

.kb-faq-nav__label {
  margin-block: 0 16px;
  font-family: var(--wp--preset--font-family--body);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 1.3px;
  text-transform: uppercase;
  color: var(--wp--preset--color--body-text);
}

.kb-faq-nav__list {
  column-count: 2;
  column-gap: 40px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.kb-faq-nav__list li {
  margin-block: 0 11px;
  break-inside: avoid;
}

.kb-faq-nav__list a {
  color: var(--wp--preset--color--brand-red);
  text-decoration: none;
}

.kb-faq-nav__list a:hover,
.kb-faq-nav__list a:focus {
  text-decoration: underline;
}

/* The seven section headings are the only h2s on the page carrying an id -
   they are the jump targets - so the attribute selector reaches exactly them
   and nothing else. */
.kb-page-body h2[id] {
  /* Production's section headings are #262626, not the pure black the theme
     uses for headings elsewhere. Measured, not assumed. */
  color: var(--wp--preset--color--body-text);
  border-bottom: 2px solid var(--wp--preset--color--brand-red);
  padding-bottom: 14px;
  margin-block: 56px 8px;
}

.kb-page-body .wp-block-details {
  border-bottom: 1px solid #e2e2e2;
}

.kb-page-body .wp-block-details > summary {
  position: relative;
  list-style: none;
}

.kb-page-body .wp-block-details > summary::-webkit-details-marker {
  display: none;
}

.kb-page-body .wp-block-details > summary::after {
  content: "";
  position: absolute;
  right: 10px;
  top: 28px;
  width: 9px;
  height: 9px;
  border-right: 2px solid var(--wp--preset--color--brand-red);
  border-bottom: 2px solid var(--wp--preset--color--brand-red);
  transform: rotate(45deg);
  transition: transform 0.2s ease, top 0.2s ease;
}

.kb-page-body .wp-block-details[open] > summary::after {
  top: 32px;
  transform: rotate(225deg);
}

@media (prefers-reduced-motion: reduce) {
  .kb-page-body .wp-block-details > summary::after {
    transition: none;
  }
}

.kb-page-body .wp-block-details > :not(summary) {
  margin-block: 0 12px;
  padding-inline-end: 44px;
}

.kb-page-body .wp-block-details > :last-child:not(summary) {
  margin-block-end: 0;
  padding-bottom: 24px;
}

/* -------------------------------------------------------------------------
   30. Image bands on FAQ, Contact and Refund and Returns

   Jake, 15 Sep: "Where is the hero image?"

   Production opens these three pages with a full-bleed photographic band and
   the rebuild had none. Measured on live:

     FAQ       350px, KB-Hero-Images-Kissing-2500x700-Original.avif
     Contact   350px, KB-Hero-woman-in-bed-2500x700-1.avif
     Returns   300px, KB-Blog-The_Afterglow-Header-2200x600-4.avif

   All three carry no text, and unlike the About and Playlists bands they run
   at FULL strength - no ink background and no 0.65 overlay, because there is
   no white heading that needs contrast behind it.

   These live in the page CONTENT rather than in a template, and deliberately.
   A per-page template would mean new files (page-faq.html and friends), and
   the Theme File Editor can only edit files that already exist - new ones
   need a whole theme zip. Putting the band in content needs no new file and
   travels with the page paste at cutover, which the template would not.

   The images are absolute kinkbox.com URLs, like every other band in this
   theme, so nothing has to be re-pointed on cutover.
   ---------------------------------------------------------------------- */
.kb-page-band {
  min-height: 350px;
  margin-block: 0 40px;
  background-position: 50% 50%;
  background-size: cover;
  background-repeat: no-repeat;
}

.kb-page-band--faq {
  background-image: url("https://kinkbox.com/wp-content/uploads/2025/12/KB-Hero-Images-Kissing-2500x700-Original.avif");
}

.kb-page-band--contact {
  background-image: url("https://kinkbox.com/wp-content/uploads/2026/01/KB-Hero-woman-in-bed-2500x700-1.avif");
}

.kb-page-band--returns,
.kb-page-band--privacy {
  min-height: 300px;
  background-image: url("https://kinkbox.com/wp-content/uploads/2025/11/KB-Blog-The_Afterglow-Header-2200x600-4.avif");
}

@media (max-width: 1024px) {
  .kb-page-band,
  .kb-page-band--returns,
  .kb-page-band--privacy {
    min-height: 200px;
  }
}

/* Break the band out to the full content width.

   The band is INSIDE post-content, and rule 11 caps that container at 1390px,
   so an alignfull child could only ever reach 1390px - 122px short of the
   1635px the About and Playlists bands get from sitting at template level.

   Core's constrained layout already caps non-alignfull children at 1390px and
   already exempts .alignfull, so uncapping the container is enough; every
   other block on the page keeps its 1390px measure. :has() scopes this to the
   three pages that actually carry a band, so About and Playlists are untouched.

   The negative margins then cancel main's own padding, which the band has to
   clear to sit flush with the header the way the About band does. They are
   written against the root-padding properties rather than a fixed value
   because rule 16 swaps that gutter from 0.44rem to 1.5rem below 1024px - a
   hard-coded -7.04px left the band 17px in from both edges on tablet and
   phone, which is exactly the inset rule 16 exists to remove. */
.wp-block-post-content.kb-page-body:has(> .kb-page-band) {
  max-width: none;
}

.kb-page-body > .kb-page-band {
  margin-top: calc(-1 * var(--wp--preset--spacing--40));
  margin-inline: calc(-1 * var(--wp--style--root--padding-left))
                 calc(-1 * var(--wp--style--root--padding-right));
}

/* -------------------------------------------------------------------------
   31. One reading measure for body copy, 895px

   Jake, 16 Sep: "I like the width of the body text on the about us page...
   should we use the same width on all pages?"

   Production has no single measure - it is 895px on About, 670px on the FAQ
   answers and Contact, and 1300px/1144px on Playlists and Refund. The rebuild
   had every one of them running the full 1390px container, which at 17px Lato
   is about 193 characters a line. Comfortable reading tops out near 90.

   So this caps body copy - and ONLY body copy - at About's 895px. Headings,
   the FAQ question rows and the FAQ jump list keep the full container width,
   which is what gives the page its structure. Contact does not move at all:
   its copy is already 806px because the form column beside it constrains it.

   This is a deliberate step away from production on two pages. Playlists and
   Refund run wider on live (1300px and 1144px, 180 and 158 characters), and
   narrowing them to 895px is the same fix being applied to the FAQ, for the
   same reason. Consistency here is the point of the change, not a side effect.

   box-sizing matters here. These paragraphs are content-box, and the FAQ
   answers carry a 44px padding-right to clear the chevron, so a bare
   max-width:895px measured the CONTENT and let them render 939px. Forcing
   border-box makes 895px mean the total width on every page. Everything else
   in the selector has no padding, so it is a no-op there.
   ---------------------------------------------------------------------- */
.kb-page-body p,
.kb-page-body ol,
.kb-page-body blockquote,
.kb-page-body ul:not(.kb-faq-nav__list) {
  box-sizing: border-box;
  max-width: 895px;
}

/* -------------------------------------------------------------------------
   34. Capped body copy lines up under the headings (18 Sep)

   Jake: "the indented copy is way off" (Refund, Privacy). Core's constrained
   layout centres every child with auto margins, so a 895px paragraph sat
   centred inside the 1390px measure while the headings ran the full 1390 -
   copy started ~250px right of its heading.

   Pin the left edge to where the 1390px headings start instead. On pages
   with a band the container is uncapped (wider than 1390), hence the calc;
   everywhere else it resolves to 0. Only direct children of the page body,
   so columns, FAQ answers and lists inside other blocks are untouched.
   ---------------------------------------------------------------------- */
.kb-page-body.is-layout-constrained > :is(p, ol, ul, blockquote):not(.alignleft):not(.alignright):not(.alignfull):not(.kb-faq-nav__list) {
  margin-left: max(0px, calc((100% - 1390px) / 2)) !important;
  margin-right: auto !important;
}

/* -------------------------------------------------------------------------
   32. FAQ answers narrower than the rest of the body copy

   Jake, 16 Sep: "Go ahead and narrow the FAQ answers."

   Rule 31 put every page on one 895px measure, which is About's. About and
   Contact carry it because a column sits beside them and breaks up the page.
   The FAQ answers have nothing beside them, so 895px made them the longest
   lines on the site at roughly 124 characters.

   Production sets them at 670px, about 93 characters, and that is the number
   being matched here. 714px rather than 670px because these children carry the
   44px padding-inline-end that keeps them off the chevron gutter, and rule 31
   put them in border-box - so 714 minus 44 renders exactly production's 670px
   of text while the answer still aligns under its question.

   The questions themselves stay at the full 1390px container width. That
   contrast is the page's structure, and narrowing them too would lose it.
   ---------------------------------------------------------------------- */
.kb-page-body .wp-block-details > :not(summary) {
  max-width: 714px;
}

/* -------------------------------------------------------------------------
   33. Breathing room around the product description

   Jake, 16 Sep: "add space between the description and the 'Some things are
   better kept between us' section", then "cut that space by 25% and also add
   space above the description."

   The description section sits between the tabs bar and the newsletter band,
   and both edges were running 24px - no real break on either side.

   BELOW, the band. Production runs 130/120/105, which is what the first pass
   matched, and it came out too generous. Cut 25%:

     1920   130 -> 98px
     1024   120 -> 90px
     390    105 -> 79px

   ABOVE, the tabs bar: 60/50/40. Smaller than the gap below on purpose - this
   break opens a section and the other closes one, and the closing break should
   read as the larger of the two.

   Neither margin is set in the template any more. Both were inline block
   styles, which no selector can override (trap 4), so neither could step down
   on smaller screens.

   Margins collapse with the neighbouring block's, so each value IS the gap it
   produces - it is not additive. The first pass used 120/110/95 expecting the
   description's trailing 10px on top and measured 10px short at every width.

   Scoped to `.single-product` on purpose: the same `.kb-newsletter` band ends
   the homepage and the shop archive, where the spacing above it is already
   correct and comes from a different block.
   ---------------------------------------------------------------------- */
.single-product .wp-block-woocommerce-product-details {
  margin-top: 60px;
}

.single-product .kb-newsletter {
  margin-top: 98px;
}

@media (max-width: 1024px) {
  .single-product .wp-block-woocommerce-product-details {
    margin-top: 50px;
  }

  .single-product .kb-newsletter {
    margin-top: 90px;
  }
}

@media (max-width: 781px) {
  .single-product .wp-block-woocommerce-product-details {
    margin-top: 40px;
  }

  .single-product .kb-newsletter {
    margin-top: 79px;
  }
}

/* Mobile gutter for the homepage AfterGlow cards (3 Oct 2026, Jake).
   The .kb-afterglow group carries inline padding 0 in the page content, so on
   phones and tablets the post images ran edge to edge while every other
   section sits 24px in. Pad the query instead of the group (the section
   heading already sits at 24px) and drop the desktop 40px text inset so
   image and text line up, matching /the-afterglow/. */
@media (max-width: 1024px) {
	.kb-afterglow .wp-block-query {
		padding-left: 1.5rem;
		padding-right: 1.5rem;
	}
	.kb-afterglow .wp-block-post-title,
	.kb-afterglow .wp-block-post-excerpt {
		padding-left: 0 !important;
		padding-right: 0 !important;
	}
}

/* -------------------------------------------------------------------------
   0.9.53 - Phone type scale and cart coupon (Jake, 10 Oct 2026)

   Headings on phones were desktop-sized (H2 32px was even bigger than the
   H1 at ~30px). Shrinking the size tokens on phones only scales every
   heading that uses them, classed or not. Desktop unchanged.

   Coupon: WooCommerce's default floats left the field and the button side
   by side with the label wrapping onto two lines. On phones, stack them
   full width to match Update cart.
   ---------------------------------------------------------------------- */
@media (max-width: 767px) {
	body {
		--wp--preset--font-size--hero: 2.5rem;
		--wp--preset--font-size--display: 1.75rem;
		--wp--preset--font-size--heading: 1.5rem;
		--wp--preset--font-size--xx-large: 1.3125rem;
	}
	.woocommerce-Tabs-panel h4 {
		font-size: 1.125rem;
	}
	.woocommerce table.cart td.actions .coupon,
	.woocommerce-page table.cart td.actions .coupon {
		display: flex;
		flex-direction: column;
		gap: 12px;
		float: none !important;
		width: 100%;
		margin: 0 0 12px;
		padding: 0;
		border: 0;
	}
	.woocommerce table.cart td.actions .coupon .input-text,
	.woocommerce table.cart td.actions .coupon .button,
	.woocommerce-page table.cart td.actions .coupon .input-text,
	.woocommerce-page table.cart td.actions .coupon .button {
		float: none !important;
		width: 100% !important;
		margin: 0 !important;
		min-height: 52px;
		box-sizing: border-box;
	}
	.woocommerce table.cart td.actions .coupon .button,
	.woocommerce-page table.cart td.actions .coupon .button {
		white-space: nowrap;
	}
}

/* 0.9.55 - Bold, slightly larger "View cart" link that appears under Add to cart on
   phones (Jake, 10 Oct 2026). */
@media (max-width: 767px) {
	.wc-block-add-to-cart-form a.added_to_cart,
	a.added_to_cart.wc-forward {
		font-weight: 700;
		font-size: 1.1875rem; /* 19px, up from 17px */
	}
}

/* 0.9.56 - Sticky logo/nav bar on phones (Jake, 10 Oct 2026).
   The header stays pinned to the top while scrolling. Anchor links (FAQ
   etc.) get a matching scroll offset so headings don't land under it.
   Desktop unchanged. */
@media (max-width: 767px) {
	html {
		scroll-padding-top: 80px;
	}
	.wp-site-blocks > header.wp-block-template-part {
		position: sticky;
		top: 0;
		z-index: 100;
		box-shadow: 0 1px 0 rgba(0, 0, 0, 0.08);
	}
}
/* Logged-in only: between 601px and 767px the WP admin bar is pinned too,
   so sit the header just under it. */
@media (min-width: 601px) and (max-width: 767px) {
	.admin-bar .wp-site-blocks > header.wp-block-template-part {
		top: 46px;
	}
}

/* 0.9.57 - Footer column headings on phones match the link size below them
   (Jake, 10 Oct 2026). Was 16px headings over 17px links. Desktop unchanged. */
@media (max-width: 767px) {
	footer.wp-block-template-part h4.wp-block-heading {
		font-size: 1.0625rem !important;
	}
}

/* 0.9.49.2 - Blog card titles on phones (Kitty/Jake, 10 Oct 2026).
   The 2.6em cap assumed a 1.3 line-height; on iPhones the title font runs
   taller, so a sliver of a third line showed under the ellipsis. On phones
   the cards are one column, so equal heights do not matter: show the full
   title. Desktop keeps the two-line clamp. */
@media (max-width: 767px) {
  .kb-afterglow .wp-block-post-title,
  .kb-blog-list .wp-block-post-title a {
    max-height: none;
    overflow: visible;
    display: block;
    -webkit-line-clamp: unset;
  }
}

/* 0.9.62 - Product page on phones: less space between the header and the
   gallery (Jake, 11 Oct 2026). Was 50px main padding + 11px columns margin
   = 61px; now 20px, matching the side gutter. Desktop unchanged. */
@media (max-width: 767px) {
	body.single-product main.wp-block-group {
		padding-top: 20px !important;
	}
	body.single-product main.wp-block-group > .wp-block-columns:first-of-type,
	body.single-product main.wp-block-group .wp-block-columns.alignwide:has(.wp-block-woocommerce-product-image-gallery) {
		margin-top: 0 !important;
	}
}

/* ---------------------------------------------------------------------------
 * 0.9.64 Product page polish (11 Oct 2026, Jake)
 * Red title bar, light border around the gallery (matches the Description
 * tabs), 8px gap above the thumbnails, desktop "See it in action" banner and
 * its video popup. Behaviour lives in functions.php ("theme 0.9.64").
 * ------------------------------------------------------------------------ */
body.single-product .wp-block-columns h1.wp-block-post-title {
	background: #911F1D;
	color: #fff !important;
	padding: 12px 20px 6px !important;
	margin-bottom: 20px !important;
}
body.single-product .woocommerce-product-gallery {
	border: 1px solid #CFC8D8;
	padding: 2px;
	background: #fff;
	box-sizing: border-box;
}
body.single-product .woocommerce-product-gallery .kb-thumbs-wrap {
	margin-top: 8px !important;
}
.kb-ia {
	position: relative;
	display: block;
	width: 100%;
	height: 200px;
	margin-top: 16px;
	padding: 2px;
	border: 1px solid #CFC8D8;
	box-sizing: border-box;
	background-color: #222;
	background-position: center 34%;
	background-size: cover;
	background-repeat: no-repeat;
	background-clip: content-box;
	background-origin: content-box;
	cursor: pointer;
	overflow: hidden;
	border-radius: 0;
}
.kb-ia::before {
	content: "";
	position: absolute;
	inset: 2px;
	background: linear-gradient(90deg, rgba(0,0,0,.6), rgba(0,0,0,.15));
	transition: background .2s;
}
.kb-ia:hover::before,
.kb-ia:focus-visible::before {
	background: linear-gradient(90deg, rgba(0,0,0,.7), rgba(0,0,0,.25));
}
.kb-ia__in {
	position: absolute;
	left: 28px;
	top: 50%;
	transform: translateY(-50%);
	display: flex;
	align-items: center;
	gap: 16px;
	color: #fff;
	font-family: Lustria, Georgia, serif;
	font-size: 26px;
	line-height: 1.2;
	text-align: left;
}
.kb-ia__play {
	flex: none;
	width: 56px;
	height: 56px;
	border-radius: 50%;
	background: #911F1D url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23fff' d='M9 7v10l8-5z'/%3E%3C/svg%3E") center/30px no-repeat;
}
.kb-modal {
	position: fixed;
	inset: 0;
	z-index: 100000;
	display: flex;
	align-items: center;
	justify-content: center;
	background: rgba(0,0,0,.85);
}
.kb-modal video {
	width: 100%;
	max-width: min(90vw, 1100px);
	max-height: 85vh;
	background: #000;
}
.kb-modal__x {
	position: absolute;
	top: 20px;
	right: 28px;
	padding: 0;
	border: 0;
	background: none;
	color: #fff;
	font-size: 40px;
	line-height: 1;
	cursor: pointer;
}
@media (max-width: 767px) {
	.kb-ia { display: none !important; }
}

/* 0.9.65: phones - tighter space between the gallery and the title bar (was 50px, now 16px) */
@media (max-width: 767px) {
	body.single-product .woocommerce-product-gallery { margin-bottom: 0 !important; }
}

/* 0.9.68 (11 Oct 2026, Jake): slimmer product title bar (padding set above),
 * product text indented 20px to line up with the title text, and the footer's
 * top line trimmed to the same width as the line above the copyright. */
body.single-product .wp-block-columns > .wp-block-column > .wp-block-woocommerce-product-price,
body.single-product .wp-block-columns > .wp-block-column > .wp-block-woocommerce-product-summary,
body.single-product .wp-block-columns > .wp-block-column > .wp-block-add-to-cart-form form.cart {
	padding-left: 20px !important;
	padding-right: 20px !important;
	box-sizing: border-box;
}
footer .kb-footer {
	border-top: 0 !important;
	padding-top: 0 !important;
}
footer .kb-footer > .wp-block-columns.alignwide:first-child {
	border-top: 1px solid #E3E1DF;
	padding-top: 24px !important;
}

/* ---------------------------------------------------------------------------
 * 0.9.69 Subscribe popup (11 Oct 2026, Jake). Behaviour: functions.php
 * ("Subscribe popup (theme 0.9.69").
 * ------------------------------------------------------------------------ */
.kb-pop {
	position: fixed;
	inset: 0;
	z-index: 100001;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 16px;
	background: rgba(20, 16, 15, .55);
}
.kb-pop[hidden] { display: none; }
html.kb-pop-open { overflow: hidden; }
.kb-pop__box {
	position: relative;
	display: grid;
	grid-template-columns: 1fr 1fr;
	width: 100%;
	max-width: 820px;
	max-height: calc(100vh - 32px);
	overflow-y: auto;
	background: #fff;
	box-shadow: 0 20px 60px rgba(0, 0, 0, .3);
}
.kb-pop__img {
	min-height: 420px;
	background: #e9e6e3 center / cover no-repeat;
}
.kb-pop__body {
	display: flex;
	flex-direction: column;
	justify-content: center;
	padding: 44px 40px 32px;
}
.kb-pop__eyebrow {
	margin: 0 0 16px;
	font: 600 20px/1.2 Lato, sans-serif;
	letter-spacing: 2px;
	text-transform: uppercase;
	color: #911F1D;
}
.kb-pop__text {
	margin: 0 0 8px;
	font: 400 16px/1.5 Lato, sans-serif;
	color: #262626;
}
.kb-pop__form { min-height: 150px; }
.kb-pop__fine {
	margin: 12px 0 0;
	font: 400 12px/1.5 Lato, sans-serif;
	color: #6b6b6b;
}
.kb-pop__no {
	align-self: flex-start;
	margin-top: 14px;
	padding: 0;
	border: 0;
	background: none;
	font: 400 13px Lato, sans-serif;
	color: #6b6b6b;
	text-decoration: underline;
	cursor: pointer;
}
.kb-pop__x {
	position: absolute;
	top: 10px;
	right: 12px;
	z-index: 1;
	width: 36px;
	height: 36px;
	padding: 0;
	border: 0;
	border-radius: 50%;
	background: transparent;
	font-size: 30px;
	line-height: 36px;
	color: #262626;
	cursor: pointer;
}
@media (max-width: 640px) {
	.kb-pop__box { grid-template-columns: 1fr; }
	.kb-pop__img { min-height: 170px; }
	.kb-pop__body { padding: 26px 22px 22px; }
	.kb-pop__eyebrow { margin-bottom: 12px; font-size: 18px; letter-spacing: 1.6px; }
	.kb-pop__x { background: rgba(255, 255, 255, .9); box-shadow: 0 1px 4px rgba(0, 0, 0, .2); }
}

/* ---------------------------------------------------------------------------
 * 0.9.70 Homepage polish (11 Oct 2026, Jake)
 * Product titles in red bars (same as product pages) with the copy below
 * indented to match, black "Explore" buttons (red on hover), photos 450px
 * tall and brightened like the product photos, title bar level with the top
 * of the photo, 15% less space between products, trust band at the standard
 * section width, lighter hero.
 * ------------------------------------------------------------------------ */
body.home .wp-block-column > h2.wp-block-heading.has-heading-font-size:has(+ h3.has-xx-large-font-size) {
	background: #911F1D;
	color: #fff !important;
	padding: 12px 20px 6px !important;
	margin-top: 0 !important;
	margin-bottom: 20px !important;
}
body.home .wp-block-column > h2.wp-block-heading.has-heading-font-size:has(+ h3.has-xx-large-font-size) ~ * {
	padding-left: 20px !important;
	padding-right: 20px !important;
	box-sizing: border-box;
}
body.home .wp-block-column > h2.wp-block-heading.has-heading-font-size:has(+ h3.has-xx-large-font-size) ~ .wp-block-buttons .wp-block-button__link {
	background: #000 !important;
	color: #fff !important;
}
body.home .wp-block-column > h2.wp-block-heading.has-heading-font-size:has(+ h3.has-xx-large-font-size) ~ .wp-block-buttons .wp-block-button__link:hover,
body.home .wp-block-column > h2.wp-block-heading.has-heading-font-size:has(+ h3.has-xx-large-font-size) ~ .wp-block-buttons .wp-block-button__link:focus-visible {
	background: #911F1D !important;
}
body.home .kb-slider-wrap img {
	filter: brightness(1.14) contrast(1.03) saturate(1.02);
}
@media (min-width: 782px) {
	body.home .wp-block-columns:has(> .wp-block-column > .kb-slider-wrap) {
		align-items: flex-start !important;
	}
	body.home .wp-block-columns:has(> .wp-block-column > .kb-slider-wrap) > .wp-block-column {
		align-self: flex-start !important;
	}
	body.home .wp-block-columns:has(> .wp-block-column > .kb-slider-wrap) > .wp-block-column:has(> .kb-slider-wrap) {
		flex-basis: 64% !important;
	}
	body.home .wp-block-columns:has(> .wp-block-column > .kb-slider-wrap) > .wp-block-column:not(:has(> .kb-slider-wrap)) {
		padding-top: 0 !important;
	}
	body.home .kb-slider-wrap,
	body.home .kb-slider-wrap .kb-slider,
	body.home .kb-slider-wrap figure,
	body.home .kb-slider-wrap img {
		height: 465px !important;
	}
	body.home .kb-product-row:has(+ .kb-product-row) {
		padding-bottom: 30px !important;
	}
	body.home .kb-product-row + .kb-product-row {
		margin-top: 15px !important;
		padding-top: 30px !important;
	}
}
body.home .kb-trust.alignfull {
	max-width: var(--wp--style--global--wide-size, 1440px) !important;
	margin-left: auto !important;
	margin-right: auto !important;
}
body.home .kb-hero .wp-block-cover__background {
	opacity: .15 !important;
}
body.home .kb-hero .wp-block-cover__image-background {
	filter: brightness(1.12);
}

/* 0.9.70: phones - only the 3 newest AfterGlow posts on the homepage (desktop keeps 6) */
@media (max-width: 767px) {
	body.home .kb-afterglow .wp-block-post-template > li.wp-block-post:nth-child(n+4) { display: none !important; }
}

/* ---------------------------------------------------------------------------
 * 0.9.72 Blog posts (11 Oct 2026, Jake): title/photos 1100px wide, reading
 * text 900px (was 800px until 0.9.88, 13 Oct 2026), tables 1000px, featured photo capped at 500px tall, titles wrap naturally,
 * smaller in-post photos centred, Related Posts heading lined up with its
 * grid, 3 related posts per page with Newer/Older pagination (single.html).
 * ------------------------------------------------------------------------ */
body.single-post main {
	--wp--style--global--content-size: 1100px;
}
body.single-post .wp-block-post-content > :is(p, ul, ol, blockquote, h2, h3, h4, h5, h6, table, .wp-block-list, .wp-block-quote) {
	max-width: 900px !important;
	margin-left: auto !important;
	margin-right: auto !important;
}
body.single-post h1.wp-block-post-title {
	text-wrap: pretty;
}
body.single-post .wp-block-post-featured-image:not(.kb-related *) {
	height: 500px;
	overflow: hidden;
}
body.single-post .wp-block-post-featured-image:not(.kb-related *) img {
	width: 100% !important;
	height: 100% !important;
	object-fit: cover !important;
	aspect-ratio: auto !important;
}
body.single-post .wp-block-post-content .wp-block-image:not(.alignleft):not(.alignright) img {
	display: block;
	margin-left: auto;
	margin-right: auto;
}
body.single-post .kb-related {
	--wp--style--global--content-size: 1440px;
}
body.single-post .kb-related .wp-block-query-pagination {
	margin-top: 2rem;
	gap: 1.25rem;
	font-size: 16px;
}
body.single-post .kb-related .wp-block-query-pagination a {
	color: #911F1D;
}
body.single-post .kb-related .wp-block-query-pagination .current {
	font-weight: 700;
}
@media (max-width: 767px) {
	body.single-post .wp-block-post-featured-image:not(.kb-related *) { height: auto; }
	body.single-post .wp-block-post-featured-image:not(.kb-related *) img { aspect-ratio: 3 / 2 !important; height: auto !important; }
}

/* 0.9.77 (11 Oct 2026, Jake): homepage product photos are at least 465px tall and grow to
 * match the text beside them on narrower desktops, so the space between products
 * stays even on every screen size. The slider fills its box instead of setting the height. */
@media (min-width: 782px) {
	body.home .wp-block-columns:has(> .wp-block-column > .kb-slider-wrap) {
		align-items: stretch !important;
	}
	body.home .wp-block-columns:has(> .wp-block-column > .kb-slider-wrap) > .wp-block-column:has(> .kb-slider-wrap) {
		align-self: stretch !important;
		display: flex;
		flex-direction: column;
	}
	body.home .kb-slider-wrap {
		position: relative;
		height: auto !important;
		min-height: 465px;
		flex: 1 1 auto;
	}
	body.home .kb-slider-wrap .kb-slider {
		position: absolute !important;
		inset: 0;
		height: 100% !important;
		margin: 0 !important;
	}
	body.home .kb-slider-wrap figure,
	body.home .kb-slider-wrap img {
		height: 100% !important;
	}
}

/* 0.9.79 (11 Oct 2026, Jake): shop page "The KinkBox difference" copy - more space below the
 * Add to Cart buttons, a wider text column (was 860px) and 75px of space below it. */
body.post-type-archive-product .kb-shop-copy {
	margin-top: 110px !important;
	padding-bottom: 75px !important;
	max-width: 1000px !important;
}
body.post-type-archive-product .kb-shop-copy > :not(.alignfull) {
	max-width: 1000px !important;
}
body.post-type-archive-product .kb-shop-copy + * {
	margin-top: 0 !important;
}
@media (max-width: 767px) {
	body.post-type-archive-product .kb-shop-copy { margin-top: 64px !important; }
}

/* ---------------------------------------------------------------------------
 * 0.9.82 Blog post spacing (12 Oct 2026, Jake): more room between the
 * featured image and the first line of copy (was 20px).
 * ------------------------------------------------------------------------ */
body.single-post .wp-block-post-featured-image + .wp-block-post-content {
	margin-top: 45px !important;
}
@media (max-width: 767px) {
	body.single-post .wp-block-post-featured-image + .wp-block-post-content {
		margin-top: 36px !important;
	}
}

/* ---------------------------------------------------------------------------
 * 0.9.83 Blog post intro (12 Oct 2026, Jake): every paragraph before the
 * first heading is the intro, set larger in Lustria (the heading font).
 * Revert: delete this block.
 * ------------------------------------------------------------------------ */
body.single-post .wp-block-post-content > p:not(:is(h1, h2, h3, h4, hr, figure, .wp-block-table) ~ p) {
	font-family: Lustria, Georgia, serif;
	font-size: 22px;
	line-height: 1.55;
	color: #1a1a1a;
}
@media (max-width: 767px) {
	body.single-post .wp-block-post-content > p:not(:is(h1, h2, h3, h4, hr, figure, .wp-block-table) ~ p) {
		font-size: 19px;
	}
}

/* ---------------------------------------------------------------------------
 * 0.9.84 FAQ section divider (12 Oct 2026, Jake): space and a thin line
 * above the FAQ heading on blog posts. Class added in functions.php.
 * ------------------------------------------------------------------------ */
body.single-post .wp-block-post-content > .kb-faq-heading {
	margin-top: 64px !important;
	padding-top: 48px;
	border-top: 1px solid #D9D6D2;
}
@media (max-width: 767px) {
	body.single-post .wp-block-post-content > .kb-faq-heading {
		margin-top: 48px !important;
		padding-top: 36px;
	}
}

/* ---------------------------------------------------------------------------
 * 0.9.85 Blog post end (12 Oct 2026, Jake): more room above the closing line
 * after the post (was 24px) and between it and the Shop button (was 20px).
 * ------------------------------------------------------------------------ */
body.single-post .wp-block-post-content + .wp-block-separator {
	margin-top: 64px !important;
	margin-bottom: 0 !important;
}
body.single-post .wp-block-post-content + .wp-block-separator + .wp-block-buttons {
	margin-top: 48px !important;
}
@media (max-width: 767px) {
	body.single-post .wp-block-post-content + .wp-block-separator {
		margin-top: 48px !important;
	}
	body.single-post .wp-block-post-content + .wp-block-separator + .wp-block-buttons {
		margin-top: 36px !important;
	}
}

/* ---------------------------------------------------------------------------
 * 0.9.86 FAQ answers indented (12 Oct 2026, Jake): the paragraph right after
 * each FAQ question sits 20px in from the question.
 * ------------------------------------------------------------------------ */
body.single-post .wp-block-post-content > .kb-faq-heading ~ h3 + p {
	padding-left: 20px;
}

/* ---------------------------------------------------------------------------
 * 0.9.87 Blog section spacing (12 Oct 2026, Jake): 40px above each heading
 * that follows copy (was 20px). Headings right after another heading (FAQ to
 * its first question) keep the normal gap; the FAQ heading keeps its 64px.
 * ------------------------------------------------------------------------ */
body.single-post .wp-block-post-content > :is(h2, h3, h4):not(.kb-faq-heading):not(:is(h1, h2, h3, h4) + *) {
	margin-top: 40px !important;
}
@media (max-width: 767px) {
	body.single-post .wp-block-post-content > :is(h2, h3, h4):not(.kb-faq-heading):not(:is(h1, h2, h3, h4) + *) {
		margin-top: 32px !important;
	}
}


/* 0.9.88 (13 Oct 2026, Jake): blog reading text widened 800 -> 900px (rule above);
 * tables capped at 1000px and centred so they sit just past the copy instead of
 * spanning the full 1100px. Max widths only, so phones still shrink to fit. */
body.single-post .wp-block-post-content > .wp-block-table {
	max-width: 1000px !important;
	margin-left: auto !important;
	margin-right: auto !important;
}

/* ---------------------------------------------------------------------------
 * 0.9.89 Blog FAQ accordions (13 Oct 2026, Jake): each FAQ question opens and
 * closes (markup from functions.php "0.9.89"). Plus/minus in brand red, thin
 * line between questions, answers indented 20px. Bottom line now sits above
 * the FAQ (moved in functions.php), so it keeps normal paragraph styling.
 * ------------------------------------------------------------------------ */
body.single-post .wp-block-post-content > .kb-faq-item {
	max-width: 900px !important;
	margin: 0 auto !important;
	border-bottom: 1px solid #D9D6D2;
}
body.single-post .kb-faq-item > summary {
	list-style: none;
	cursor: pointer;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 16px;
	padding: 20px 0;
}
body.single-post .kb-faq-item > summary::-webkit-details-marker { display: none; }
body.single-post .kb-faq-item > summary h3 { margin: 0 !important; }
body.single-post .kb-faq-item > summary::after {
	content: "+";
	flex: 0 0 auto;
	font-family: Lato, sans-serif;
	font-size: 28px;
	line-height: 1;
	color: #911F1D;
}
body.single-post .kb-faq-item[open] > summary::after { content: "\2212"; }
body.single-post .kb-faq-item > summary:focus-visible { outline: 2px solid #911F1D; outline-offset: 4px; }
body.single-post .kb-faq-answer { padding: 0 0 20px 20px; }
body.single-post .kb-faq-answer > p { margin: 0; }
