/* ==========================================================================
   Liquicity Store — child theme stylesheet
   Version 8.

   CHANGED IN V8
     P06  Badges moved back to the TOP RIGHT, on the owner's call. The
          left-hand corner was chosen to keep the right free for a wishlist
          control that does not exist yet; the corner is wanted now and the
          wishlist can move when it arrives. On the product page a plain right
          inset would land on the SECOND photograph, because the anchor wraps
          the whole two column grid, so that rule is pushed past the midpoint
          instead. See PATCH 06.
     F1   FIXED the archive title sitting 32px further in than the header and
          the product grid. .hero-section carries Blocksy's
          is-width-constrained and reads --theme-container-width, and it is
          nested inside the .ct-container that already read it, so the 32px
          gutter came off twice. Confirmed in the DOM. The variable is reset
          to 100% on that one element.

   CHANGED IN V7
     P13  The card track is no longer drag-scrolled. It only ever ran for a
          mouse, which is where the hover advance already moves it, and it made
          the product photograph a drag surface competing with the click that
          the whole card exists for.

   CHANGED IN V6
     P13  The second photograph is back inside the rows that scroll sideways,
          on pointer devices only. Hover is not a gesture, so it cannot compete
          with the row's own swipe; the track is simply closed to hand
          movement there and the hover advance still drives it. On touch the
          extra slide stays hidden, and being display: none it is never even
          requested.

   CHANGED IN V5
     P13  Checked the last unverified selector in this patch against the live
          DOM and cleared its marker, 23 down to 22, and recorded that the
          product link sits inside the slide rather than around the media.
     P13  The indicator is mobile only. The hover advance in functions.php
          brings the second photograph in by itself on a pointer device, which
          leaves the bar answering a question that was never asked, and a black
          rule under every card in a four-across grid is a lot of furniture for
          that. The arrows are off by default now for the same reason, behind
          liq_card_swiper_arrows, and their styling stays here so turning them
          back on at three slides needs no CSS.

   CHANGED IN V4
     §5   Category chips are generated from the term tree by functions.php
          instead of pasted into a Content Block per category, restyled to
          measured values, always a scroller, and carrying a current state.
     P12  FIXED a rule with no declaration block. .lq-cat-tiles.is-dragging a
          ended in a comma and joined the swatch rule below it, so the homepage
          tiles went display: none while being dragged.
     P13  Indicator and arrows now appear on hover where there is a pointer
          that can hover, and stay visible where there is not. FIXED an initial
          jump to the last slide, caused by an end-aligned final slide under
          mandatory snapping while the lazy second image was still loading.
          Card image swiper. Blocksy has no slider on the product card, only a
          hover swap of one extra image, and hover does not exist on touch.
          This is native overflow plus scroll-snap, driven by the shared
          liqDragScroll, with a proportional thumb rather than a segmented bar
          so the slide count lives in one PHP filter and nowhere in CSS.
          Two images to start. Downloads, the [liq_products] rows and the
          related-products row are all excluded in PHP, so the swiper is a
          category-page and search thing, matching the reference. What is left
          here is a backstop for the opt-in case. Needs the matching block in
          functions.php.

   CHANGED IN V3
     §2   Rewritten. Every body.product_cat-* selector matched nothing: on a
          single product that class sits on the product wrapper, not the body.
          The download gallery is now one column, sized through the variable
          Blocksy reads.
     §4   Still owns the space inside the hero band, without !important:
          12 below everywhere, 32 above on the product page and 64 above on
          archives, split on Blocksy's own data-prefix. Blocksy exposes no
          padding control for a type-1 hero; handing it over was tried and the
          band collapsed to the text. The block lists all four gaps around a
          hero, says which two are Customizer settings, and carries a table for
          dialling the archive value without measuring again.
     P06  Badges restyled from Tomorrowland to Patta, and NEW became a
          secondary badge that sits beside a primary one. Needs the matching
          change to liq_get_product_badges() in the WPCode snippet. The
          inverted badge has no border; one was tried and rejected, and the
          reason it might look like it needs one is written into the rule.

   Version 2 notes follow. Roughly one third the size of v1.

   SCOPE RULE
   This file contains ONLY what Blocksy cannot express as a Customizer
   setting. Anything with a control in the Customizer belongs there, because
   settings are visible to whoever maintains the site and they survive a
   stylesheet rewrite.

   Removed in v2, now Customizer settings:
     typography, button colour/height/radius, form colours, link colour,
     gallery ratio, gallery type, sticky summary, swatch shape,
     out-of-stock swatch, pagination, sort visibility, card container.

   WHY A FILE AND NOT "ADDITIONAL CSS"
   WordPress prints Additional CSS inline in <head> on every request, so it
   costs bytes even on a cache HIT. A file is cached by browser and CDN and
   is free from the second page onwards. Blocksy makes the same choice for
   its own output (dynamic_css_file = file).

   Use Additional CSS as a scratchpad while iterating, then move anything
   permanent into this file.

   [VERIFY] marks a Blocksy-internal class not confirmed against a live
   install. Check in devtools and correct here. WooCommerce core selectors
   are correct as written.

   Sections match liquicity-design-spec.md.
   ========================================================================== */


/* --------------------------------------------------------------------------
   TOKENS
   Duplicated from the Customizer palette so the rules below can reference
   them. Keep in sync with Customizer > Colors.
   -------------------------------------------------------------------------- */

:root {
  --lq-ink:     #0A0A0A;
  --lq-paper:   #FFFFFF;
  --lq-canvas:  #EFEFEF;
  --lq-rule:    #E2E2E2;
  --lq-muted:   #767676;
  --lq-accent:  #1B4DE4;
  --lq-sale:    #C8102E;
  --lq-popular: #6B3FD9;

  --lq-t-xs:    0.6875rem;
  --lq-t-sm:    0.8125rem;
  --lq-t-base:  0.9375rem;

  --lq-s1: 4px;  --lq-s2: 8px;   --lq-s3: 12px;  --lq-s4: 16px;
  --lq-s6: 24px; --lq-s8: 32px;  --lq-s12: 48px; --lq-s16: 64px;
}


/* --------------------------------------------------------------------------
   §3  SQUARE EVERYTHING
   Blocksy exposes border radius per element (buttons, forms, gallery), not
   globally. This catches everything else: badges, notices, swatches,
   images, cards, dropdowns, the search field.

   The single highest-impact rule in the design.
   -------------------------------------------------------------------------- */

*,
*::before,
*::after {
  border-radius: 0 !important;
  box-shadow: none !important;
}

.avatar,
[class*="rounded-full"] {
  border-radius: 50% !important;
}

/* Keep keyboard navigation visible. The blanket box-shadow removal above
   would otherwise destroy focus indication. */
:focus-visible {
  outline: 2px solid var(--lq-accent) !important;
  outline-offset: 2px;
}


/* --------------------------------------------------------------------------
   §4  ARCHIVE GRID
   Column count is a Customizer setting; gutter width is not.

   One value for both axes was wrong. Measured against the reference:

                              Tomorrowland   staging
     column gutter                   20px       8px
     image bottom to title           20px      28px
     price to next row's image       34px      12px

   A column gutter separates two things of equal standing. A row gutter has
   to detach a caption from an image it does not belong to. At 8px on both
   axes the title and price sat closer to the row BELOW them than to their
   own photo, so every caption in the grid had to be read twice.
   -------------------------------------------------------------------------- */

.products {
  column-gap: var(--lq-s6) !important;  /* 24, was 8 */
  row-gap: var(--lq-s8) !important;     /* 32, was 8 */
}

/* Aspect ratio, the honest version.

   An earlier draft of this file claimed merch and music had to share one
   ratio because WooCommerce has a single global cropping setting. That is
   true of CROPPING and not of DISPLAY. With cropping set to "uncropped" the
   original file is preserved, and CSS picks the ratio per context:

     merch  4/5 portrait   garments read better standing up
     music  1/1 square     artwork is square by nature

   object-fit: cover fills the box. A portrait photo in a 4/5 box loses
   almost nothing; a square cover in a 1/1 box loses nothing at all. */
.products .product img,
.wc-block-grid__product-image img {
  background: var(--lq-canvas);
  aspect-ratio: 4 / 5;
  object-fit: cover;
  width: 100%;
  height: auto;
}


/* --------------------------------------------------------------------------
   §6  CARD: TITLE AND PRICE ON ONE BASELINE
   Blocksy stacks card layers vertically with no option to pair two on a
   row. This is New Amsterdam's move and it makes the row read as a line of
   type rather than a stack of boxes.

   THERE IS NO CARD INNER WRAPPER. An earlier version of this block targeted
   .ct-woo-card-inner, which does not exist. With shop_cards_type on type-1
   Blocksy renders every layer as a direct child of the li:

     <li class="product …">            display: flex
       <figure data-current-variation="…">
       <h2 class="woocommerce-loop-product__title"><a>…</a></h2>
       <span class="price">…</span>
       <div class="ct-card-variation-swatches" …>
     </li>

   So the li itself is the container, and it is flex rather than grid, which
   is why the grid-column on the title and the price did nothing and the
   price sat on its own line.

   Turning the card element into a two-column grid rather than adding a
   wrapper keeps the markup Blocksy's business. Everything defaults to
   spanning both columns, so any layer switched on later (rating, stock, add
   to cart) lands full width and behaves as before; only the title and the
   price are pulled onto one row.

   THE SELECTOR REPEATS .product ON PURPOSE. It was li.product, which read
   better and worked on the archive, and silently missed the related-products
   scroller: that row renders its cards as something other than an li, so the
   captions there stayed stacked while the archive was on one line. Repeating
   the class buys the same specificity (0,3,0) without asserting anything
   about the tag, and beats Blocksy's own (0,2,0) without !important.
   Deliberate: display here is static, and forcing it is what broke the
   waitlist notice in P9.1.

   This now governs the archive, the related-products scroller and the
   up-sells row, which is the point: one caption layout everywhere.
   -------------------------------------------------------------------------- */

.products .product.product {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: start;

  /* align-content, not align-items. They are different properties and only one
     of them causes this bug.

     A card is stretched to the height of the tallest in its row. align-content
     defaults to normal, which for grid behaves as stretch, so the auto-sized
     rows grow to absorb that extra height. A card with three rows (image,
     caption, sizes) spreads it over three; a card with two spreads it over two.
     The result is captions sitting at different heights across one row, and the
     card with LESS content sitting lower, which is the opposite of what anyone
     would predict from looking at it.

     start pins the rows to their natural height and lets the leftover space
     collect at the bottom, where it is invisible. */
  align-content: start;
  column-gap: var(--lq-s3);
  /* Image to caption. Measured 47px with row-gap at 16, because the <figure>
     still carried the browser default margin (1em 40px) underneath the image
     and the title's line box adds its own leading on top of that. The figure
     margin is zeroed below, so this value is now the whole distance plus a
     few pixels of leading. Reference is 20. */
  row-gap: var(--lq-s2);
}

/* The image sits in a <figure>. Left alone that is 16px of margin above and
   below from the UA stylesheet, which lands between the photo and the
   caption and makes the caption look detached from its own product. */
.products .product.product > figure {
  margin: 0;
}

.products .product.product > * {
  grid-column: 1 / -1;
}

.products .product.product > .woocommerce-loop-product__title {
  grid-column: 1;
  align-self: baseline;
  margin: 0;
}

.products .product.product > .price {
  grid-column: 2;
  align-self: baseline;
  white-space: nowrap;
  margin: 0;
  /* Blocksy ships the card price at --theme-font-weight: 600. The reference
     runs it regular and muted, so the title carries the row and the price
     answers it. Delete these two lines to put the emphasis back. */
  font-weight: 400;
  color: var(--lq-muted);
}

/* Sale price: struck original muted, current in the one sale red. */
.price del {
  color: var(--lq-muted);
  margin-right: var(--lq-s2);
}

.price ins {
  color: var(--lq-sale);
  text-decoration: none;
}


/* --------------------------------------------------------------------------
   §6  BADGES INSIDE THE IMAGE
   Blocksy renders NEW and the custom BESTSELLER badge and positions them
   with its own defaults. This pins them to the reference: solid rectangle,
   white uppercase, tight padding.
   -------------------------------------------------------------------------- */

.products .product .onsale,
.woocommerce-product-gallery .onsale,
.ct-product-badge,     /* [VERIFY] */
.ct-label-container {  /* [VERIFY] */
  background: var(--lq-ink) !important;
  color: #fff !important;
  font-size: var(--lq-t-xs);
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 6px 10px;
  line-height: 1;
  min-width: 0;
  min-height: 0;
}

/* Discount badges use the sale red; status badges stay ink. */
.products .product .onsale,
.woocommerce-product-gallery .onsale {
  background: var(--lq-sale) !important;
}


/* --------------------------------------------------------------------------
   §4  HERO BAND, ALL TEMPLATES

   Blocksy hangs the breadcrumb, and on archives the title as well, inside the
   same .hero-section wrapper on every template. This block owns the space
   INSIDE that band. Keep it that way: the moment a second rule sets spacing on
   the breadcrumb or on the wrapper, the band differs per page and there is no
   single number left to turn.

   WHY THESE TWO NUMBERS ARE NOT IN THE CUSTOMIZER, HAVING BEEN TRIED TWICE

   Blocksy exposes no padding control for a type-1 hero. Measured on both
   templates with these declarations removed:

     Archive   header ends 109, title ink 116.5   (7.5px, all of it leading)
     Product   header ends 105.5, breadcrumb 110.5 to 123, gallery at 129

   The band collapsed to the text itself at every Customizer value then set,
   including woo_categories_hero_height at 6px, which is bound to something
   else. There is no setting to prefer here, so the rule stays.

   FOUR GAPS, AND ONLY TWO OF THEM LIVE HERE

     above the band    .ct-container[data-vertical-spacing], per prefix, times
                       the global contentAreaSpacing. Customizer.
     above the text    padding-top below. Here.
     below the text    padding-bottom below. Here.
     below the band    product_hero_margin / woo_categories_hero_margin, a real
                       number per prefix. Customizer.

   Worth knowing before reaching for the first one: contentAreaSpacing is 90px
   and it is the only value there is. Switching a prefix to top-and-bottom buys
   90px above the band or nothing, with no number in between. That is why the
   two tunable gaps ended up in CSS and not because nobody looked.

   ARCHIVES GET MORE AIR ABOVE, AND THAT IS NOT A SCOPED EXCEPTION

   One number served both templates until the archive title needed more room
   than the product breadcrumb. That is not a per-page special case, it is two
   different contents: a 40px Fraunces H1 and a 13px breadcrumb do not want the
   same space above them, and pretending otherwise short-changes one of them.

   Split on data-prefix, which Blocksy puts on the body and which is the same
   axis every per-template Customizer control uses. Confirmed in the DOM:

       <body ... data-prefix="woo_categories" data-header="type-1" ...>

   DIALLING IT. Measured on the T-Shirts archive the header ends at 94.5 and
   the title's line box sits 7px above its ink, so the gap you will see is the
   token plus 7:

       --lq-s6    24  ->  31
       --lq-s8    32  ->  39
       --lq-s12   48  ->  55
       --lq-s16   64  ->  71

   Change the one token below rather than measuring again. For reference, 90px
   of Content Area Spacing on top of --lq-s8 gives 129, which was too much.

   32 ABOVE, 12 BELOW, AND THE REASONING FLIPPED

   It was 16 and 24, on the argument that the extra weight underneath ties the
   band to what follows rather than to what came before. That reads the band as
   a lead-in.

   The opposite reading is better and is the one in force: a breadcrumb saying
   Home > Merchandise > T-Shirts is a LABEL for the thing beneath it, and a
   label belongs against what it labels. Same for a category title. Pushing the
   band down away from the header and pulling the content up under it says that
   out loud, where an evenly padded band says only that it is a band.

   Both numbers are on the existing scale rather than free values, so the band
   keeps step with everything else in the file.

   MARGIN is not set here. Blocksy's own rule is
   margin-bottom: var(--margin-bottom, 40px), from a Customizer control per
   prefix, with product_hero_margin at 0px and woo_categories_hero_margin at
   20px. An earlier version of this block pinned the margin to 0 with
   !important, which was defensible when nothing was set and became wrong the
   moment it was. That 20px on archives stacks on the 12 below, so the archive
   currently sits looser under the title than the product page does under the
   breadcrumb; take it to 0 in the Customizer rather than shaving padding here.

   NO !important. It was there against a Blocksy default said to be 48px above
   this band. Measured with the rule gone, the padding was zero, so there is
   nothing to fight and the child stylesheet wins on load order anyway. If this
   band ever comes out taller than the numbers below, check whether Blocksy
   started emitting a value, and whether a control finally exists, in which case
   these two lines belong there instead.

   BACKGROUND is already a setting: woo_categories_pageTitleBackground points
   at palette color-8.

   .page-header and [class*="ct-hero"] used to be in this selector list. Both
   carried a [VERIFY] marker, neither was ever confirmed against a DOM, and
   .hero-section is confirmed on both the archive and the PDP. If a template
   turns up using one of them, add it back with a measurement next to it.
   -------------------------------------------------------------------------- */

.hero-section {
  padding-top: var(--lq-s8);       /* 32, was --lq-s4 */
  padding-bottom: var(--lq-s3);    /* 12, was --lq-s6 */

  /* Guard. Blocksy's Height control belongs to the type-2 hero and both
     prefixes are on type-1, so this should be inert. It stays because a band
     inheriting a min-height it was never given is the one failure here that is
     invisible until someone opens devtools. */
  min-height: 0;
}

/* Archives only. See the dialling table above: this is the one token to change
   if the category title wants more or less room. The product page keeps 32,
   which is right for a breadcrumb and would be mean under an H1.

   Requires Content Area Vertical Spacing on BOTTOM ONLY for this prefix.
   contentAreaSpacing is 90px and switching the prefix to top and bottom adds
   all 90 of it above this padding, with no value in between. */
[data-prefix="woo_categories"] .hero-section {
  padding-top: var(--lq-s16);
}


/* --------------------------------------------------------------------------
   §4  ARCHIVE CONTROL ROW
   Filter left, product count beside it, sort right. Blocksy renders sort as
   a bordered select; Tomorrowland uses plain text, which reads lighter and
   stops it competing with the products.
   -------------------------------------------------------------------------- */

.woocommerce-result-count {
  color: var(--lq-muted);
  font-size: var(--lq-t-base);
  margin: 0;
}

.woocommerce-ordering select {
  border: none !important;
  background: transparent !important;
  padding: 0 var(--lq-s6) 0 0;
  font-size: var(--lq-t-base);
  color: var(--lq-ink);
  cursor: pointer;
}

/* Filter button: outlined square, matching the reference */
.ct-filter-canvas-trigger, /* [VERIFY] */
.ct-filters-trigger {      /* [VERIFY] */
  border: 1px solid var(--lq-rule) !important;
  background: transparent !important;
  color: var(--lq-ink) !important;
  padding: var(--lq-s3) var(--lq-s6);
  font-size: var(--lq-t-base);
}

.ct-filter-canvas-trigger:hover,
.ct-filters-trigger:hover {
  border-color: var(--lq-ink) !important;
}


/* --------------------------------------------------------------------------
   §6  WAITLIST LINK OFF THE ARCHIVE
   WooCommerce Waitlist prints a "SIGN UP" link under every out-of-stock
   card. On a page where most items are sold out that becomes the loudest
   repeated element. The SOLD OUT badge already carries the information, and
   the waitlist form lives on the product page.
   -------------------------------------------------------------------------- */

.products .product .wcwl_add_to_waitlist,
.products .product .wcwl_wrap { /* [VERIFY] */
  display: none !important;
}


/* --------------------------------------------------------------------------
   §5  CATEGORY CHIPS
   Tomorrowland's answer to a large catalogue. No Blocksy equivalent.

   NOW BUILT BY functions.php, not by hand in a Content Block. The previous
   version of this comment asked for the markup to be pasted into a Content
   Block per category, which meant every new subcategory needed someone to
   remember to add a chip, and a renamed or emptied one stayed on the page
   pointing nowhere. The terms already know their own names, thumbnails and
   order; a hand-built copy of that is a second source of truth that can only
   ever drift.

   For music this is the format entry point (Digital, CD, Vinyl), which is the
   most useful cut of that catalogue and comes out of the tree for free.

   MEASURED off the reference at 3x, converted to CSS px:

     chip height              54          (1 + 6 + 40 + 6 + 1)
     border                   1px, and the SAME width in both states
     inactive border          #E6E6E6     -> --lq-rule, #E2E2E2
     current border           #000000     -> --lq-ink
     padding                  6 / 16 / 6 / 12
     thumbnail                40 x 40 on #F1F1F1  -> --lq-canvas
     thumbnail to label       12
     chip to chip             10
     label                    ~14px, regular, #1A1A1A

   THE BORDER WIDTH DOES NOT CHANGE BETWEEN STATES, and that is worth keeping.
   The current chip reads as heavier because it is black next to hairline grey,
   not because it is thicker. Measured both at 3 device pixels. A 2px border on
   the current one would move every chip after it by a pixel the moment the
   page changed, which is a layout shift bought for nothing.

   CHIP TO CHIP IS 10 AND THIS FILE USES 12. --lq-s2 is 8 and --lq-s3 is 12,
   so the measured value is off the scale in a way the badge gap was not. Two
   pixels either side of a 54px chip is below the point where the difference
   reads, and staying on the scale is worth more than being right to the pixel
   here. If it ever looks wrong, the token is on the next line.
   -------------------------------------------------------------------------- */

.lq-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--lq-s3);
  margin: var(--lq-s8) 0;
}

.lq-chips a {
  display: flex;
  align-items: center;
  gap: var(--lq-s3);
  border: 1px solid var(--lq-rule);

  /* 6 top and bottom, 12 left, 16 right. Asymmetric because the thumbnail
     brings its own visual margin and the label does not, so equal padding put
     the image closer to the edge than the text was. */
  padding: 6px var(--lq-s4) 6px var(--lq-s3);

  color: var(--lq-ink);
  text-decoration: none;
  font-size: var(--lq-t-base);

  /* A chip that wraps stops being a chip. Required once this is a scroller,
     where there is no width to wrap into anyway. */
  white-space: nowrap;

  transition: border-color .15s ease;
}

.lq-chips a:hover {
  border-color: var(--lq-ink);
}

/* WHERE YOU ARE. The strip shows siblings on a child archive, so one of them
   is always the page you are on, and without this it reads as a link to
   nowhere. Border colour only: see the note above about width. */
.lq-chips a[aria-current] {
  border-color: var(--lq-ink);
}

.lq-chips img {
  width: 40px;
  height: 40px;
  object-fit: contain;
  background: var(--lq-canvas);

  /* Chips without a thumbnail sit beside chips with one, and a 40px box of
     nothing keeps them the same height. Term thumbnails are optional and
     half the tree does not have them. */
  flex: 0 0 auto;
}


/* --------------------------------------------------------------------------
   §7  BREADCRUMBS
   Wayfinding, not a call to action. Blocksy applies the link colour, which
   makes them accent blue. Mute them.
   -------------------------------------------------------------------------- */

.ct-breadcrumbs,
.woocommerce-breadcrumb {
  font-size: var(--lq-t-sm);
  color: var(--lq-muted);
}

.ct-breadcrumbs a,
.woocommerce-breadcrumb a {
  color: var(--lq-muted);
  text-decoration: none;
}

.ct-breadcrumbs a:hover,
.woocommerce-breadcrumb a:hover {
  color: var(--lq-ink);
  text-decoration: underline;
}

.ct-breadcrumbs > *:last-child,
.woocommerce-breadcrumb > *:last-child {
  color: var(--lq-ink);
}


/* --------------------------------------------------------------------------
   §7  BUTTON HIERARCHY ON OUT-OF-STOCK PRODUCTS
   Out of stock renders a waitlist button beside a disabled add-to-cart.
   Without this the inert button draws the most attention.

   Primary action is always ink. Secondary is always outlined.
   -------------------------------------------------------------------------- */

.single_add_to_cart_button:disabled,
.single_add_to_cart_button.disabled {
  background: var(--lq-rule) !important;
  color: var(--lq-muted) !important;
  border-color: var(--lq-rule) !important;
  cursor: not-allowed;
}

.wcwl_add_to_waitlist button, /* [VERIFY] */
button[name="wcwl_submit"] {  /* [VERIFY] */
  background: transparent !important;
  color: var(--lq-ink) !important;
  border: 1px solid var(--lq-ink) !important;
  width: 100%;
}

.wcwl_add_to_waitlist button:hover,
button[name="wcwl_submit"]:hover {
  background: var(--lq-ink) !important;
  color: #fff !important;
}

/* Out of stock notice: informative, not alarming. */
.stock.out-of-stock {
  font-size: var(--lq-t-sm);
  color: var(--lq-muted);
}


/* --------------------------------------------------------------------------
   §7  ASTRONAUTS UPSELL
   Rendered by functions.php in this theme, after the add-to-cart form, where
   Tomorrowland puts its Klarna line. Merch categories only, and never shown to
   someone who is already a member.
   -------------------------------------------------------------------------- */

.liq-astronaut-upsell {
  border-top: 1px solid var(--lq-rule);
  margin: var(--lq-s6) 0 0;
  padding-top: var(--lq-s4);
  font-size: var(--lq-t-sm);
  color: var(--lq-muted);
  line-height: 1.6;
}

.liq-astronaut-upsell__link {
  color: var(--lq-ink);
  text-decoration: underline;
  text-underline-offset: 3px;
  white-space: nowrap;
}

.liq-astronaut-upsell__link:hover {
  color: var(--lq-accent);
}


/* --------------------------------------------------------------------------
   §7  REASONS TO BUY
   Blocksy Pro element. Layout and icon sizing are not exposed as settings.
   -------------------------------------------------------------------------- */

.ct-product-reasons-to-buy { /* [VERIFY] */
  display: flex;
  flex-direction: column;
  gap: var(--lq-s3);
  font-size: var(--lq-t-sm);
  list-style: none;
  padding: 0;
}

.ct-product-reasons-to-buy li {
  display: flex;
  align-items: center;
  gap: var(--lq-s3);
  margin: 0;
}

.ct-product-reasons-to-buy svg {
  width: 18px;
  height: 18px;
  flex: 0 0 18px;
}


/* --------------------------------------------------------------------------
   §2  PER-CATALOGUE OVERRIDES
   Blocksy's Single Product settings are global, so the element stack is a
   superset and each catalogue hides what it does not need.

   CORRECTED AGAINST THE LIVE DOM. The previous version of this section opened
   with:

     WooCommerce puts product_cat-<slug> on the body.

   It does not, at least not here. On a single product the body carries only

     wp-singular product-template-default single single-product postid-103628

   and the category classes sit on the product wrapper instead:

     <div id="product-103628" class="product … product_cat-downloads
          product_cat-music …">

   So all three body.product_cat-* blocks matched nothing and had been doing
   nothing since they were written. The two body.tax-product_cat.term-downloads
   blocks further down are correct and are left alone: on a TAXONOMY ARCHIVE
   WordPress does put those classes on the body. That split is why the music
   archive looked right while the music product page did not, and why the
   problem read as a gallery bug rather than as a selector that never fired.

   Scoped on downloads rather than music on purpose. This product carries both
   classes. A CD or a vinyl is a physical object that can have several
   photographs and wants the merch treatment; only the download is guaranteed
   to be one square cover.
   -------------------------------------------------------------------------- */

.product_cat-downloads .lq-fit-note {
  display: none;
}


/* THE GALLERY IS A GRID, AND THAT IS THE ACTUAL PROBLEM.

   Measured before touching anything: .woocommerce-product-gallery is
   1016.83 x 500.91 and the artwork inside it is 501 x 500. The artwork is not
   small because it is a small file (the source is 2048 square, served here at
   1200) and not because a width was missed. product_view_stacked_columns is 2,
   .ct-stacked-gallery-container is a two column grid, and one image fills one
   cell and leaves the other empty.

   Capping the gallery alone would therefore have made this WORSE, not better:
   a 620px gallery still split in two is a 300px cover. The old max-width rule
   here would have been the wrong fix even if its selector had matched.

   One column first. Everything else follows from it. */
.product_cat-downloads .ct-stacked-gallery-container {
  grid-template-columns: 1fr;
}


/* THEN THE WIDTH, VIA THE VARIABLE BLOCKSY READS.

   .product-entry-wrapper sets --product-gallery-width: 60% and
   .woocommerce-product-gallery reads width: var(--product-gallery-width, 50%).
   Setting width or max-width on the gallery would work, and it would also be
   the second time this stylesheet fought a Blocksy custom property instead of
   setting it. See the --theme-container-width entry in the handover.

   Overriding the variable also inherits the right responsive behaviour for
   free: the width declaration only exists inside @media (min-width: 1000px),
   so below that this does nothing and the gallery goes full width on its own.

   38% of the 1735px container is roughly 660, against a 625px summary column.
   A single cover at the full 1016 would outweigh the text beside it, and a
   tracklist is the reason anyone scrolls a music product page. */
@media (min-width: 1000px) {
  .product_cat-downloads .product-entry-wrapper {
    --product-gallery-width: 38%;
  }
}


/* SQUARE, AGAINST AN INLINE STYLE.

   product_gallery_ratio is a global 3/4 and Blocksy writes it onto the img as
   an inline style="aspect-ratio: 3/4". Inline beats any selector, so this needs
   !important. That is the one place in this file where !important is not a
   shortcut: there is no specificity high enough to beat an inline style.

   contain rather than cover, because a cover is the whole artwork and cropping
   one is closer to defacing it than to framing it. */
.product_cat-downloads .woocommerce-product-gallery img {
  aspect-ratio: 1 / 1 !important;
  object-fit: contain;
}


/* The badge needed no rule.

   .liq-badge-anchor--single wraps .ct-stacked-gallery-container, not the
   figure, so it measured 1016 wide while the artwork measured 501 and the badge
   sat 435px to the right of the cover in open space. With the grid down to one
   column the anchor and the image are the same box and the badge lands back on
   the artwork.

   Left here as a note rather than deleted: the next person to see a badge
   floating beside an image should look at what the anchor wraps before writing
   a position rule. */


/* --------------------------------------------------------------------------
   ARCHIVE RULES BELOW ARE UNCHANGED AND WERE ALREADY CORRECT.
   body.tax-product_cat.term-downloads is a real body class on a taxonomy
   archive. Keep them as they are.
   -------------------------------------------------------------------------- */

/* Music archive: denser grid. Blocksy's column count is global. */
body.tax-product_cat.term-downloads .products {
  grid-template-columns: repeat(5, 1fr) !important;
  gap: var(--lq-s4) !important;
}

@media (min-width: 1600px) {
  body.tax-product_cat.term-downloads .products {
    grid-template-columns: repeat(6, 1fr) !important;
  }
}

/* Artwork is square and fills its cell edge to edge, no canvas behind it. */
body.tax-product_cat.term-downloads .products .product img {
  aspect-ratio: 1 / 1;
  background: none;
  object-fit: cover;
}


/* --------------------------------------------------------------------------
   §9  HEADER MARQUEE
   Optional. A scrolling ticker suits a drum and bass label. Static text
   reads calmer, and Tomorrowland uses static.
   -------------------------------------------------------------------------- */

.lq-marquee {
  overflow: hidden;
  background: var(--lq-ink);
  color: #fff;
  font-size: var(--lq-t-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: var(--lq-s2) 0;
}

.lq-marquee__track {
  display: inline-flex;
  gap: var(--lq-s16);
  white-space: nowrap;
  animation: lq-scroll 40s linear infinite;
}

.lq-marquee:hover .lq-marquee__track {
  animation-play-state: paused;
}

@keyframes lq-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

@media (prefers-reduced-motion: reduce) {
  .lq-marquee__track {
    animation: none;
    justify-content: center;
    width: 100%;
  }
}


/* --------------------------------------------------------------------------
   §12  DELIBERATELY SUPPRESSED
   Kept as one block so the intent is obvious and nobody re-enables them by
   accident. Wrong register for this brand, and Tomorrowland avoids them too.
   -------------------------------------------------------------------------- */

.ct-countdown,      /* [VERIFY] countdown timers */
.ct-stock-scarcity, /* [VERIFY] "only 2 left" urgency */
.woocommerce-store-notice {
  display: none !important;
}

/* No star ratings on the grid. Ratings belong on the product page. */
.products .product .star-rating {
  display: none;
}


/* --------------------------------------------------------------------------
   §5  CATEGORY CHIP CARROUSEL
   Always a scroller now, rather than a class somebody adds by hand. Whether
   the chips fit is a function of the viewport and of how many children the
   category has, and neither of those is known when the page is built.

   NO ARROWS, though the reference has them and an earlier version of this
   comment asked for them. Two reasons, and the second is the real one.

   The strip sits directly under the category title, so it already has an
   owner; the reference needs a "Discover Lifestyle Categories" heading to give
   its arrows somewhere to live, and that heading under an H1 that already says
   T-Shirts is a label for a label.

   And a partial chip at the edge is the affordance. That is the same argument
   --lq-per-view makes in P12.4 with its fractional card counts, and the same
   one PATCH 13 makes for the card swiper: the thing half in view needs no
   explaining and no control. The strip scrolls the current chip into view on
   load, which is why the reference screenshot has one cut off at the left,
   and that partial chip is doing the work an arrow would.

   proximity rather than mandatory. Chips are different widths, so mandatory
   snapping fights a drag that lands between two of them, and there is no
   page-sized unit here for it to be right about.
   -------------------------------------------------------------------------- */

.lq-chips--scroll {
  flex-wrap: nowrap;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  -ms-overflow-style: none;

  /* Vertical swipe must still scroll the page. */
  touch-action: pan-x pan-y;

  /* Room for the border of a chip that is mid-transition, and nothing more.
     A scrollbar is suppressed above, so this is not making space for one. */
  padding-bottom: var(--lq-s2);
}

.lq-chips--scroll::-webkit-scrollbar {
  display: none;
}

.lq-chips--scroll a {
  flex: 0 0 auto;
  scroll-snap-align: start;
}

/* Mouse drag comes from the shared liqDragScroll. Nothing in the native
   overflow model says a row is draggable, so the cursor says it. Unlike the
   card swiper in PATCH 13, this strip is not itself a link: the chips are, and
   the space between them is not, so a grab cursor here is not lying about what
   a click will do. */
@media (hover: hover) and (pointer: fine) {
  .lq-chips--scroll.is-draggable {
    cursor: grab;
  }

  .lq-chips--scroll.is-dragging {
    cursor: grabbing;
    user-select: none;
  }
}

/* The click that ends a drag is killed in the P12.4 list, alongside the other
   three scrollers, rather than here. */


/* --------------------------------------------------------------------------
   §4  CATEGORY INTRO
   The WooCommerce category description, set full width under the title.
   Tomorrowland uses this for real copy about the collection, which carries
   genuine SEO weight rather than reading as filler.
   -------------------------------------------------------------------------- */

.term-description,
.woocommerce-product-category-description {
  max-width: none;
  font-size: var(--lq-t-base);
  line-height: 1.6;
  margin: 0 0 var(--lq-s8);
}


/* ==========================================================================
   PDP PATCH 02
   Fixes found by comparing the staging product page against the reference
   after importing v4.
   ========================================================================== */


/* --------------------------------------------------------------------------
   VARIATION SWATCHES — moved
   An earlier version of this section styled .ct-swatch and
   .variable-items-wrapper .variable-item. Neither class exists in Blocksy's
   output, so none of it ever applied. The working rules, based on the actual
   markup, are in P7.3 further down.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   WAITLIST "SIGN UP" LINK — REVERTED
   An earlier version of this file hid .wcwl_add_to_waitlist > a globally, to
   get rid of a loose "SIGN UP" appearing under the gallery.

   That was wrong twice over. The loose text was not the waitlist plugin at
   all, it was the site's own liq-badge--signup rendering unpositioned because
   its CSS had not been ported yet. And because the rule was not scoped to
   archives, it also hid the real waitlist form on the product page, which is
   exactly the thing that is supposed to be there when a size is sold out.

   Nothing to hide here. The archive-scoped rule further up handles the card.
   -------------------------------------------------------------------------- */


/* --------------------------------------------------------------------------
   ADDITIONAL INFORMATION TABLE
   Attribute values render as accent-blue links. They are data, not
   navigation, so they should read as plain text.
   -------------------------------------------------------------------------- */

.woocommerce-product-attributes a,
.shop_attributes a {
  color: var(--lq-ink);
  text-decoration: none;
  pointer-events: none;
}

.woocommerce-product-attributes,
.shop_attributes {
  font-size: var(--lq-t-base);
}

.woocommerce-product-attributes th,
.shop_attributes th {
  font-weight: 500;
  color: var(--lq-ink);
}


/* --------------------------------------------------------------------------
   RELATED PRODUCTS
   The "SALE" label renders as loose text above the title. Make it a badge
   consistent with the rest of the grid, and mute the struck original price.
   -------------------------------------------------------------------------- */

.related.products h2,
.up-sells h2 {
  font-size: var(--lq-t-lg, 1.75rem);
  margin-bottom: var(--lq-s6);
}

.related.products .onsale,
.up-sells .onsale {
  background: var(--lq-sale) !important;
  color: #fff !important;
}


/* ==========================================================================
   PDP PATCH 03
   Container width, related products, and the two-ratio image system.
   ========================================================================== */


/* --------------------------------------------------------------------------
   RELATED PRODUCTS
   Staging showed landscape thumbnails, ragged title heights and a "SALE"
   label floating loose above each title. Tomorrowland's equivalent row is
   four identical cells.

   The aspect-ratio rule above already fixes the shape. These rules fix the
   alignment: every card gets the same internal rhythm regardless of whether
   the title wraps to one line or three.
   -------------------------------------------------------------------------- */

.related.products .products,
.up-sells .products {
  align-items: start;
}

.related.products .product,
.up-sells .product {
  display: flex;
  flex-direction: column;
}

.related.products .woocommerce-loop-product__title,
.up-sells .woocommerce-loop-product__title {
  font-size: var(--lq-t-base) !important;
  font-weight: 500 !important;
  font-family: var(--lq-font-ui, system-ui, sans-serif) !important;
  line-height: 1.35;
  margin: var(--lq-s2) 0 var(--lq-s1);
}

/* Loose "SALE" text becomes a badge inside the image, like the main grid. */
.related.products .onsale,
.up-sells .onsale {
  position: absolute;
  top: var(--lq-s3);
  left: var(--lq-s3);
  z-index: 2;
}


/* --------------------------------------------------------------------------
   CONTAINER WIDTH
   Blocksy does not size containers with max-width. It sets

     width: var(--theme-container-width)

   on the inner .ct-container / .ct-container-narrow / .is-width-constrained
   element, and that variable computed to:

     min(100%, calc(90vw - 0px * 2))

   read off body on the category page. So the container is 90vw, i.e. 5% of
   empty page on each side at every width, and maxSiteWidth never enters the
   formula below 2400px. Raising maxSiteWidth therefore did nothing on a
   1800px screen: measured 90px of margin per side against the reference's 32.

   The previous rule here set max-width on those same elements inside a
   min-width: 1700px query. It matched, and it did nothing, because a
   max-width above the computed width cannot narrow anything. It did have one
   side effect: [class*="ct-container"] scoped to a body class outranks
   .ct-container-narrow, so it silently overrode the narrow container's own
   max-width and stretched the archive title header to the full 1620 instead
   of narrowContainerWidth. Both problems disappear by setting the variable
   Blocksy actually reads.

   Fixed 32px edges rather than a percentage, which is what the reference
   does: the gutter stays the same on a laptop and on a 5K display instead of
   growing to 128px. maxSiteWidth still caps it at the top end.

   Desktop only. Below 1200px the percentage is the better behaviour, since
   32px on a phone is a wider margin than 5%.
   -------------------------------------------------------------------------- */

@media (min-width: 1200px) {
  :root {
    --theme-container-width: min(2400px, calc(100% - 64px)) !important;
  }
}


/* --------------------------------------------------------------------------
   THE GUTTER CAME OFF TWICE, AND THE RULE ABOVE IS WHY

   Confirmed in the DOM on the Sweaters & Hoodies archive:

     div.ct-container[data-vertical-spacing="bottom"]
       section
         div.hero-section.is-width-constrained[data-type="type-1"]
           header.entry-header > h1.page-title
         header.woocommerce-products-header
         ul.products.columns-4

   Blocksy's own main.min.css gives .is-width-constrained

     margin-inline: auto
     width: var(--theme-container-width)
     max-width: var(--theme-block-max-width)

   so the hero reads the same variable the container it already sits inside
   read a moment earlier. The old value was viewport based,
   min(100%, calc(90vw - 0px * 2)), and 90vw is 90vw however deep you nest it.
   The value above is parent based, so the 32px per side comes off the
   container and then off the hero again.

   Measured off a 1430px viewport: header and ul.products both run 32 to 1398,
   the hero runs 64 to 1365.5. 1366 - 64 = 1302, which is the hero to within
   half a pixel. Centred, that is invisible. It shows the moment the title is
   aligned left, which is what it was mistaken for.

   NO !important, and that is not an oversight. The declaration above is on
   :root and this one is on the hero, so they are not competing for the same
   element. An inherited value never beats a declared one, whatever importance
   it carried where it was declared.

   ONE ELEMENT, not [class*="ct-container"] .is-width-constrained. The product
   page carries the same class on .product-entry-wrapper and is very likely
   inset the same 32px for the same reason, and it has not been measured. If
   the product gallery does not line up with the logo either, this is the rule
   to widen, with the measurement written next to it.

   IF THE TITLE STILL DOES NOT REACH THE LOGO after this, the next variable is
   --theme-block-max-width in the max-width above, which was not binding at
   1302 and could become so at full width.
   -------------------------------------------------------------------------- */

.hero-section.is-width-constrained {
  --theme-container-width: 100%;
}


/* --------------------------------------------------------------------------
   HERO TYPE-2 DEFAULTS
   Blocksy's type-2 hero is the band built to hold a title over a background,
   and its inner container carries two defaults that type-1 does not:

     padding:    var(--container-padding, 50px 0)
     min-height: var(--min-height, 250px)

   Measured on the category archive while it was still type-2: the h1 is 33px
   tall and sat vertically centred inside a 250px box, which produced 125px of
   empty white above the title and 132px below it. The band was 290px tall to
   show one line of type, and no amount of padding on .hero-section could fix
   that, because the space was inside a deeper element.

   THAT PAGE IS NOW TYPE-1, set in the Customizer (woo_categories_hero_section),
   which is the better fix and the reason this rule no longer fires there. It
   stays because these are variables with fallbacks rather than hard values, so
   any template still on type-2 gets the same geometry as the rest of the site
   instead of a 250px band nobody asked for.

   If you ever want a real hero band back, with a photo or a colour behind a
   category title, this is the block to delete first.
   -------------------------------------------------------------------------- */

.hero-section[data-type="type-2"] [class*="ct-container"] {
  --min-height: 0;
  --container-padding: 0;
}


/* ==========================================================================
   FOUNDATION PATCH
   Systematic spacing, sizing and rhythm differences measured against the
   Tomorrowland product page. These are not individual bugs, they are the
   baseline the rest of the design sits on, so this block is worth reading
   before adding anything else.
   ========================================================================== */


/* --------------------------------------------------------------------------
   F1. ALIGNMENT — SOLVED WITH A SETTING, NOT WITH CSS

   Three rounds of CSS tried to line up the header with the page content by
   guessing at Blocksy's container class names. None of them fired, and the
   page kept rendering three different left edges:

     header logo / nav   85px
     breadcrumb         128px
     gallery            168px

   The cause is structural rather than cosmetic: the header rows are set to
   full width while the content sits in a container capped at maxSiteWidth and
   centred. Those two only line up by coincidence at one specific viewport.

   THE FIX IS IN THE CUSTOMIZER:
     Header > (each row) > Container width > Boxed

   Both rows inherit maxSiteWidth, Blocksy aligns everything itself, and this
   block can stay empty. maxSiteWidth (2400) then becomes the single control
   for how wide the site runs.

   IF YOU WANT TRUE FULL-BLEED INSTEAD, the reverse also works, but it needs
   the real container class from devtools rather than another guess. Inspect
   the wrapper around the header row and the one around the gallery, and both
   can take the same --lq-gutter.
   -------------------------------------------------------------------------- */

:root {
  --lq-gutter: clamp(16px, 1.8vw, 48px);
}

/* max-width: none WAS here, from when the plan was full-bleed. With the header
   set to boxed it does the opposite of what is wanted: the product wrapper
   escapes maxSiteWidth and starts ~50px to the left of the logo.

   Nothing to override. Blocksy's own container governs both. */

/* --------------------------------------------------------------------------
   F2. DEAD SPACE BELOW THE HEADER — moved to a setting
   This section guessed at .page-header and [class*="ct-hero"] on single
   products. Neither exists there, so it never did anything.

   The real cause was product_content_area_spacing set to "both", which pads
   the product content area at the top as well as the bottom. It is now
   "bottom" in the Customizer import. One setting, no CSS.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   F3. BREADCRUMBS
   Blocksy renders them uppercase and letterspaced, which makes wayfinding
   look like a heading. The reference uses plain sentence case in grey.
   -------------------------------------------------------------------------- */

.ct-breadcrumbs,
.woocommerce-breadcrumb {
  text-transform: none !important;
  letter-spacing: 0 !important;
  font-size: var(--lq-t-sm) !important;
  font-weight: 400 !important;
  /* No margin here. The hero band that wraps the breadcrumb owns the spacing
     (§4). A margin on this element stacks on top of that padding and, because
     it was declared twice with different scopes, it stacked differently per
     template. */
  margin-bottom: 0;
}


/* --------------------------------------------------------------------------
   F4. COLUMN SPLIT
   Gallery 62%, summary 38%, matching the reference. productGalleryWidth is
   60 in the Customizer; the remaining difference comes from container
   padding, which F1 already corrects.

   The indent is desktop only. Below 768px the two columns stack, so that
   padding stopped being a gutter between them and became a 24px indent on
   the text alone: measured on a 488px screen, the photo started at x=27 and
   the title at x=52. Two left edges on one screen, which is the same problem
   F1 solved for the header.
   -------------------------------------------------------------------------- */

@media (min-width: 768px) {
  /* Zero here because the two columns sit side by side and the gallery ends
     where the summary ends. Below this width they stack, and this same rule
     welded the title to the gallery: measured 4px between the slide indicator
     and the product title. */
  .single-product div.product .woocommerce-product-gallery {
    margin-bottom: 0;
  }

  .single-product div.product .summary {
    padding-left: clamp(24px, 3vw, 64px);
  }
}

@media (max-width: 767px) {
  .single-product div.product .woocommerce-product-gallery {
    margin-bottom: var(--lq-s6);
  }
}


/* --------------------------------------------------------------------------
   F5. VERTICAL RHYTHM IN THE SUMMARY
   The reference keeps a consistent 28 to 32px gap between every block.
   Staging varies, which is what makes it feel loose. Setting one gap on the
   flex container is more reliable than margins on each child.
   -------------------------------------------------------------------------- */

.single-product div.product .summary {
  display: flex;
  flex-direction: column;
  gap: var(--lq-s6);
}

.single-product div.product .summary > * {
  margin-top: 0 !important;
  margin-bottom: 0 !important;
}


/* --------------------------------------------------------------------------
   F6. TITLE AND PRICE
   Price renders bold on staging and regular in the reference. Bold pulls the
   eye away from the title, which should lead.

   THE PRICE RULE IS GONE. It set font-size, font-weight and colour on
   .single-product .summary .price with !important, which meant the price
   typography controls in the Customizer appeared to do nothing. Blocksy has
   real controls for that element, and a setting beats a rule. Set size,
   weight and colour there.

   The card price in §6 is a different element and stays in CSS: that one is
   about the relationship between title and price inside a grid cell, not
   about type scale.
   -------------------------------------------------------------------------- */

.single-product .product_title {
  font-size: clamp(1.75rem, 2.6vw, 2.5rem) !important;
  font-weight: 400 !important;
  line-height: 1.08;
  letter-spacing: -0.015em;
}


/* --------------------------------------------------------------------------
   F7. VARIATION LAYOUT
   WooCommerce renders variations as a table: label in a cell on the left,
   swatches in a cell on the right. The reference stacks them, label above.
   Unsetting the table display is the only way to get there.
   -------------------------------------------------------------------------- */

.variations,
.variations tbody,
.variations tr,
.variations td,
.variations th {
  display: block !important;
  width: 100% !important;
  padding: 0 !important;
  border: none !important;
  text-align: left !important;
}

.variations th.label,
.variations .label {
  margin-bottom: var(--lq-s3) !important;
  font-size: var(--lq-t-sm);
  font-weight: 500;
}

.variations tr + tr {
  margin-top: var(--lq-s4) !important;
}

/* Swatch row */
.variations td.value,
.variations .value {
  display: flex !important;
  flex-wrap: wrap;
  gap: var(--lq-s2);
}


/* --------------------------------------------------------------------------
   F8. ADD TO CART ROW
   Reference: full-width button at 64px, uppercase and letterspaced, with a
   square wishlist button beside it. Staging runs 72px with a quantity
   stepper taking a third of the row.
   -------------------------------------------------------------------------- */

.single-product form.cart {
  display: flex;
  flex-wrap: wrap;
  gap: var(--lq-s3);
  align-items: stretch;
  margin: 0 !important;
}

.single-product form.cart .quantity {
  flex: 0 0 auto;
}

.single-product form.cart .quantity input,
.single-product form.cart .quantity .qty {
  height: 64px;
}

.single-product .single_add_to_cart_button {
  flex: 1 1 auto;
  min-height: 64px;
  font-size: var(--lq-t-sm) !important;
  font-weight: 500 !important;
  letter-spacing: 0.08em !important;
  text-transform: uppercase !important;
}


/* --------------------------------------------------------------------------
   F9. HAIRLINE DIVIDERS
   The reference separates the CTA from the trust list with a single hairline
   and equal space above and below. Blocksy's divider element defaults to a
   heavier treatment.
   -------------------------------------------------------------------------- */

.single-product .summary hr,
.single-product .ct-divider { /* [VERIFY] */
  border: none;
  border-top: 1px solid var(--lq-rule);
  margin: 0 !important;
  height: 0;
}


/* --------------------------------------------------------------------------
   F10. HEADER
   Reference logo sits close to the edge and the whole header is compact.
   Container padding is handled in F1; this trims the row height.
   -------------------------------------------------------------------------- */

header [data-row] { /* [VERIFY] */
  --header-height: auto;
}

.site-logo img,
[data-id="logo"] img { /* [VERIFY] */
  max-height: 64px;
  width: auto;
}


/* ==========================================================================
   PATCH 04 — REAL CLASS NAMES
   Blocksy's actual markup, read from the DOM on staging:

     .product-entry-wrapper.is-width-constrained   outer wrapper
     .ct-product-gallery-container                 gallery wrapper
     .ct-stacked-gallery-container                 the image grid
     .ct-media-container                           each <figure>
     .ct-woo-badges                                badge wrapper
     .out-of-stock-badge[data-shape="type-2"]      the SOLD OUT badge
     .product.product_cat-<slug>                   category is on the product
                                                   div, not only on <body>

   THE INLINE STYLE PROBLEM
   Blocksy writes style="aspect-ratio: 3/4;" directly onto every <img>.
   Inline styles beat stylesheet rules, so every aspect-ratio rule aimed at
   product images needs !important or it silently does nothing. That is why
   the related products row stayed landscape.
   ========================================================================== */


/* --------------------------------------------------------------------------
   P4.1  ASPECT RATIO, ENFORCED
   -------------------------------------------------------------------------- */

.products .product img,
.related.products .product img,
.up-sells .product img,
.ct-media-container img {
  aspect-ratio: 4 / 5 !important;
  object-fit: cover !important;
  width: 100% !important;
  height: auto !important;
}

/* Music artwork stays square. The category class sits on the product div as
   well as the body, so both are covered. */
.product.product_cat-downloads .ct-media-container img,
body.product_cat-downloads .ct-media-container img,
.product.product_cat-downloads .products .product img,
body.tax-product_cat.term-downloads .products .product img {
  aspect-ratio: 1 / 1 !important;
  object-fit: contain !important;
  background: none;
}


/* --------------------------------------------------------------------------
   P4.2  WIDTH CONSTRAINT — merged into F1
   is-width-constrained is what makes the product page read as boxed. It is
   now handled in F1 together with the header gutter, so that both use the
   same value and cannot drift apart.
   -------------------------------------------------------------------------- */


/* --------------------------------------------------------------------------
   P4.3  BLOCKSY BADGES: OFF
   Switched off in the Customizer import because the site has its own badge
   system. This hides any that slip through, for instance on a template the
   setting does not reach.

   .ct-woo-badges alone was not enough. The out-of-stock badge sits outside
   that wrapper under its own class, which is why a second, slightly larger
   #25292E rectangle kept showing behind every SIGN UP badge, offset about
   10px down and right. It only ever appeared there because both are driven
   by the same stock condition, which is what made it look like a styling
   quirk of that one badge rather than a second element.

   Still worth switching off properly at Blocksy > WooCommerce > badges, on
   both the archive and the single product. Hiding leaves it in the DOM.
   -------------------------------------------------------------------------- */

.ct-woo-badges,
.out-of-stock-badge,
[class*="ct-woo-badge-"] {
  display: none !important;
}


/* ==========================================================================
   PATCH 05 — RELATED PRODUCTS AS A FULL-BLEED SCROLLER
   Portrait images, edge to edge, with the next card deliberately half cut
   off at the right so it is obvious the row scrolls.

   This is the same pattern Tomorrowland uses for "Similar Products", except
   theirs has arrows. Native scrolling plus scroll-snap needs no JavaScript,
   works with touch, trackpad, shift-scroll and keyboard, and degrades
   gracefully.

   NOTE: leave Blocksy's own related-products slideshow OFF. Two scroll
   mechanisms on the same element fight each other.
   ========================================================================== */

/* The scroller used to break out to 100vw, which put its heading and first
   card at the viewport edge while everything above them sat inside the boxed
   container. That is a third left edge on the same page.

   It now stays in the container. The peek at the right-hand edge still works:
   it happens at the container boundary instead of the screen boundary, which
   is what the reference does too. */
.related.products,
.up-sells.products {
  overflow: hidden;
}

/* Heading stays aligned with the rest of the page content. */
.related.products > h2,
.up-sells.products > h2 {
  margin-bottom: var(--lq-s6);
}

.related.products > .products,
.up-sells.products > .products {
  display: flex !important;
  grid-template-columns: none !important;
  flex-wrap: nowrap !important;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scroll-padding-left: 0;
  gap: var(--lq-s2) !important;
  padding: 0 0 var(--lq-s4);
  margin: 0;

  /* Thin scrollbar rather than none: a hidden scrollbar removes the only
     affordance a mouse-only visitor has. */
  scrollbar-width: thin;
  scrollbar-color: var(--lq-rule) transparent;

  /* Horizontal panning only, so a vertical swipe still scrolls the page
     instead of getting swallowed by the carousel. */
  touch-action: pan-x pan-y;
  -webkit-overflow-scrolling: touch;
}

/* Mouse and pen get drag-to-scroll from functions.php. The cursor is the only
   hint that this is possible, so it matters. */
.related.products > .products.is-draggable,
.up-sells.products > .products.is-draggable {
  cursor: grab;
}

.related.products > .products.is-dragging,
.up-sells.products > .products.is-dragging {
  cursor: grabbing;
  scroll-snap-type: none;   /* snapping mid-drag feels like the row is fighting back */
  user-select: none;
}

/* Kills the click that ends a drag, so releasing the mouse over a product
   does not open it.

   is-dragging is added by functions.php only AFTER the pointer has actually
   moved past the threshold, never on pointerdown. That distinction is the
   whole reason links work: the click target is worked out from the hit test
   at press and release, so an anchor with pointer-events: none at the moment
   of pressing never becomes the target and the link silently stops working
   on every click, drag or not.

   The gallery is in this list because it shares the same drag helper. Listed
   explicitly rather than as a bare .is-dragging, so switching a third row to
   drag-scroll is a deliberate line here and not something that happens by
   accident to any element that borrows the class name. */
.related.products > .products.is-dragging a,
.up-sells.products > .products.is-dragging a,
.ct-stacked-gallery-container.is-dragging a {
  pointer-events: none;
}

/* Never let the browser drag the image itself. */
.related.products img,
.up-sells.products img {
  -webkit-user-drag: none;
  user-select: none;
}

.related.products > .products::-webkit-scrollbar,
.up-sells.products > .products::-webkit-scrollbar {
  height: 4px;
}

.related.products > .products::-webkit-scrollbar-thumb,
.up-sells.products > .products::-webkit-scrollbar-thumb {
  background: var(--lq-rule);
}

/* Card width tuned so roughly 4.5 fit on a wide screen, which leaves the
   fifth visibly cut off. */
.related.products > .products > .product,
.up-sells.products > .products > .product {
  flex: 0 0 clamp(200px, 21vw, 360px) !important;
  width: auto !important;
  max-width: none !important;
  scroll-snap-align: start;
  margin: 0 !important;
}

/* Mobile: one card at roughly 70% with the next peeking at 30%.
   Two equal columns fill the screen exactly and give no hint that the row
   scrolls, which is the trap the Blocksy "2 columns" setting falls into.
   Note that the Customizer column count does not apply here at all, since
   flex-basis overrides the grid. */
@media (max-width: 767px) {
  .related.products > .products > .product,
  .up-sells.products > .products > .product {
    flex: 0 0 70vw !important;
  }
}

/* Tablet: two full cards plus a peek. */
@media (min-width: 768px) and (max-width: 1099px) {
  .related.products > .products > .product,
  .up-sells.products > .products > .product {
    flex: 0 0 40vw !important;
  }
}

/* NO FADE ON THE RIGHT EDGE.
   There was a 48px linear-gradient ::after here, meant to reinforce that the
   row continues past the container. It reads as a soft blur against the
   photography rather than as an edge, and the half-cut card already says
   "there is more" without dressing it up. A hard cut is also honest about
   where the container ends, which is the same edge everything else on the
   page lines up with.

   If it ever comes back, note that it needs pointer-events: none, and that
   position: relative on the parent below is what it was anchored to. */

.related.products,
.up-sells.products {
  position: relative;
}

/* Keyboard users need the scroller itself to be focusable. Paired with
   tabindex="0" added in functions.php. */
.related.products > .products:focus-visible,
.up-sells.products > .products:focus-visible {
  outline: 2px solid var(--lq-accent);
  outline-offset: 4px;
}


/* ==========================================================================
   PATCH 06 — NATIVE BADGE SYSTEM
   Styling for liq_get_product_badges(), the snippet written to replace YITH
   Badge Management. The markup is:

     <span class="liq-badge-anchor">
       …product image…
       <span class="liq-badges">
         <span class="liq-badge liq-badge--signup">SIGN UP</span>
       </span>
     </span>

   Both wrappers are <span>. The snippet already returns a <span> for
   .liq-badges; the previous version of this comment still described a <div>
   and asked for it to be changed, which had already happened.

   REFERENCE CHANGED: Tomorrowland to Patta.

   Measured off four 2x screenshots, converted to CSS px. The Tomorrowland
   badge is a label placed on a photograph. The Patta badge is a marker in the
   corner of one. Everything below follows from that one difference.

                        WAS (Tomorrowland)   NOW (Patta)
     height                    23px             16px
     font-size                 11px             10px
     padding                   6px 10px         3px 6px
     letter-spacing            0.08em           0.02em
     corner radius             0                2px
     position                  top right 16px   top right 8px

   The height is the number to hold on to. Patta renders NEW, SOLD OUT and
   SALE at exactly 16px each, and the widths follow from the text: SOLD OUT
   measured 58px against 46.5px of type plus 2x6px of padding. One badge
   height across every state is most of what makes the system read as a
   system rather than as three separate decisions.

   TOP RIGHT, AND THIS HAS NOW BEEN BOTH

   V7 moved these to the left. The reason given was that a wishlist control
   conventionally sits in the right-hand corner, so putting the badge on the
   left settled a collision in advance rather than deferring it.

   Reversed on the owner's call. That reasoning was pre-empting a control that
   does not exist and is not scoped: the wishlist on the open list is the one
   beside the CTA on the PRODUCT PAGE, not one on the card. Choosing a corner
   today against a card control nobody has drawn is a guess dressed as a
   decision. If a card wishlist ever lands, it takes the left corner or it
   sits under the badge, and that is a decision for the day it is drawn.

   The arrows in P13.2 are the only other thing living in this corner. They
   are off by default, they span the full height of the media box and centre
   their chevron vertically, so a 16px tall badge at the top does not cover
   one, and .liq-badges takes no pointer events at any time.

   ORDER IS UNCHANGED and still reads left to right, SIGN UP then NEW, which
   puts NEW in the corner. To put the primary badge against the edge instead,
   add flex-direction: row-reverse to .liq-badges. That is safe: the inversion
   in .liq-badge + .liq-badge--new is a DOM sibling selector and row-reverse
   changes only the visual order, so the white badge stays the white one.

   BADGE COLOURS are unchanged. The measured Patta red came out at #B82A24
   against #B82B35 for ours in the same screenshot pipeline, which is the same
   colour once the profile shift is taken off both. There was nothing to fix.
   ========================================================================== */

.liq-badge-anchor {
  display: block;
  position: relative;
}

.liq-badges {
  position: absolute;
  top: var(--lq-s2);
  /* Right. See the note above: this was left in v7 and is back on the right
     on the owner's call. One value, and the product page rule below is the
     only place that needs anything more than this. */
  right: var(--lq-s2);
  z-index: 3;
  display: flex;
  /* Row, not column. Patta puts a second marker BESIDE the first rather than
     under it (NEW followed by IN-STORE EXCLUSIVE, 10px apart). The snippet
     still returns at most one badge, so this changes nothing today; it means
     that if it ever returns two, the default is the one the reference uses. */
  flex-direction: row;
  align-items: center;

  /* 4px, measured between the reference pair. --lq-s2 (8px) was a guess
     made before there was a two-badge screenshot to measure. */
  gap: var(--lq-s1);

  pointer-events: none;
}

.liq-badge {
  display: inline-block;
  background: var(--lq-ink);
  color: #fff;
  font-family: var(--lq-font-ui, system-ui, sans-serif);

  /* 10px rather than --lq-t-xs (11px). Deliberately not a token change:
     --lq-t-xs is used elsewhere in this stylesheet and this is the only place
     that wants 10. */
  font-size: 10px;

  /* 600, up from 500. At 10px the badge loses weight faster than it loses
     size, and the measured reference is heavier than it looks. */
  font-weight: 600;

  /* Was 0.08em. Patta tracks at roughly 0.02em: "NEW" measured 19.5px wide at
     10px, which is close to untracked. 0.08em at this size pushes SOLD OUT
     past 60px and the badge stops reading as a marker. */
  letter-spacing: 0.02em;

  text-transform: uppercase;
  line-height: 1;

  /* 3px 6px, with line-height 1, gives exactly the measured 16px. */
  padding: 3px 6px;

  /* THE ONE EXCEPTION TO §3.
     §3 squares everything, on the reasoning that Blocksy exposes radius per
     element and the design has no rounded corners in it. That is still true
     of buttons, forms and the gallery. It is not true of the badge: the
     reference rounds all four corners by a measured 2px, on every badge, and
     at 16px tall a hard corner is the difference between a marker and a
     sticker.

     2px, not more. This is a hairline, not a pill. Set to 0 to fold the badge
     back under §3 and nothing else in the file needs touching. */
  border-radius: 2px;

  white-space: nowrap;
}

/* Sold out is a dead end, so it reads quieter than the others.

   NOT the reference value. Patta renders this at #B0B0B0 with #E3E3E3 text,
   which measures 1.6:1 and is below any threshold worth naming. The intent is
   right and the execution is not legible, so the intent is what gets copied:
   --lq-muted on white is 4.7:1 and still visibly quieter than the ink badges
   next to it. */
.liq-badge--soldout {
  background: var(--lq-muted);
}

/* Sign up is an invitation, not a refusal. Full weight. */
.liq-badge--signup {
  background: var(--lq-ink);
}

/* One red, used here and for the sale price. */
.liq-badge--sale {
  background: var(--lq-sale);
}

/* NEW ON ITS OWN is a primary badge and stays ink.

   A white badge over a photograph has no edge: the fill disappears into a
   bright sky and only the letters survive. Every card on this store sits on a
   festival photograph rather than on the flat grey the reference uses, so
   white can never be the only badge. */
.liq-badge--new {
  background: var(--lq-ink);
}

/* NEW ALONGSIDE ANOTHER BADGE inverts.

   The reference pairs a filled black marker with a filled white one. Two ink
   badges side by side are two identical boxes and the reader has to compare
   the words to work out which one matters; swapping the fill answers that
   before anything is read.

   Which one gives way is not arbitrary. The primary badge says whether you
   can buy this and at what price. NEW says when it arrived, which is context
   rather than a decision, so NEW is the one that goes quiet.

   Scoped with the sibling combinator rather than a modifier class, so the
   snippet stays free to decide only WHICH badges apply and never how they are
   ranked. Order in the DOM is the ranking, and that already comes out of
   liq_get_product_badges() with the primary first. */
.liq-badge + .liq-badge--new {
  background: var(--lq-paper);
  color: var(--lq-ink);

  /* NO BORDER, AND THAT IS DELIBERATE.

     A 1px ink border was here briefly and was taken out on looks. Do not add
     it back thinking you have found a bug, because there is a real observation
     underneath it: over a bright photograph the white fill and the picture are
     the same white, so the badge loses its box and only the letters survive.
     Measured on the Solarsystem product page, where at x=330 the white ran
     55px tall against a 16px badge, because the mirror behind it was
     reflecting sky.

     That was judged the better trade. The badge is the quiet half of a pair and
     the black one beside it already carries the shape; an outline next to a
     filled marker reads as two competing objects rather than one statement with
     a footnote. Letters alone over a bright frame is a softer failure than a
     boxed badge that is always slightly too loud.

     If it ever does need an edge, use border rather than box-shadow: §3 kills
     shadows globally with !important, which is how the first POPULAR badge
     disappeared. And carve the 1px out of the padding, 2px 5px instead of
     3px 6px, so the box stays 16px and still lines up with the black one. */
}

/* Its own colour rather than a variation on one of the others. Black is
   availability, red is price; a recommendation is a third kind of thing and
   borrowing either would say something untrue about it. The violet is a
   deliberate sibling of --lq-accent rather than a new hue from nowhere.

   The reference has no equivalent: Patta's palette here is black, grey and
   red only, because Patta has no recommendation badge to place. That is an
   absence of a decision rather than a decision against, so the violet stays.

   Filled, like the rest. The first version drew an outline with an inset
   box-shadow, which §3 kills globally:

     *, *::before, *::after { box-shadow: none !important; }

   So it rendered as bare letterspaced text with no box at all. White on this
   violet measures about 6.3:1, comfortably past AA at this size, and it holds
   up over a photograph the way a light badge would not.

   Inside the Popular row this badge repeats the heading on every card. It is
   left on because the row is not where it earns its place, archives and search
   are. To drop it there:
     .liq-product-row .liq-badge--popular { display: none; } */
.liq-badge--popular {
  background: var(--lq-popular);
}

/* Product page: same badge, further in, and NOT simply right: 16px.

   The previous rule scaled the badge up here, on the reasoning that there was
   room for it. The reference does not: one badge size across archive, search,
   rows and the product page, and only the inset changes with the size of the
   image it sits on. A badge that grows with its container is measuring the
   container rather than saying anything about the product.

   THE ANCHOR IS THE WHOLE GRID, WHICH IS WHY THIS IS A CALC.

   liq_wrap_gallery_image_with_badges() wraps .ct-stacked-gallery-container,
   not the figure. product_view_stacked_columns is 2, so on desktop that
   element is a two column grid measuring the full gallery width, and the note
   in §2 records what a plain inset did there: the badge sat 435px away from
   the artwork in open space. On the left that never showed, because column
   one starts at the container's left edge. On the right it lands on the
   SECOND photograph, or in the gap beside it on a product with one photo.

   So the badge is pushed back past the midpoint. With two equal columns and a
   gap g, column one ends at 50% - g/2 from the left, which is 50% + g/2 from
   the right. Setting right: calc(50% + 16px) therefore lands the badge inside
   column one with 16 - g/2 of inset, which is between 6 and 11px for any gap
   Blocksy ships. Close enough to the intended 16 not to need g measured, and
   it self-corrects if the gallery width changes, which a fixed value would
   not.

   IF THE GALLERY EVER GOES TO THREE COLUMNS this is wrong by a third of the
   width and the 50% becomes 66.6%. That is the one thing to change here, and
   product_view_stacked_columns is where to look first. */
.liq-badge-anchor--single .liq-badges {
  top: var(--lq-s4);
  right: calc(50% + var(--lq-s4));
}

/* One column, so the plain inset is correct again.

   Downloads is forced to a single column in §2, on the reasoning that an
   album cover is one image and not a set. */
.product_cat-downloads .liq-badge-anchor--single .liq-badges {
  right: var(--lq-s4);
}

/* Below 768px PATCH 11 turns the same grid into a full width scroller, so
   every slide is the whole container and the midpoint calc would push the
   badge into the middle of the photograph. Last in the file on purpose: this
   has to win over both rules above. */
@media (max-width: 767px) {
  .liq-badge-anchor--single .liq-badges {
    right: var(--lq-s4);
  }
}

/* The anchor must not interfere with the aspect-ratio rules in P4.1. */
.liq-badge-anchor img {
  display: block;
}


/* ==========================================================================
   PATCH 07 — ADD TO CART AREA
   Five fixes measured against the Tomorrowland product page.
   ========================================================================== */


/* --------------------------------------------------------------------------
   P7.1  NO QUANTITY STEPPER
   Tomorrowland has none. Almost everyone buys one of a garment, and someone
   who wants two clicks the button twice, which is fewer interactions than
   finding a stepper, incrementing it and then clicking.

   Hidden rather than removed: the input still exists and still submits
   value 1, so nothing in the cart logic changes and reverting is one line.
   The button then takes the full row width, because F8 gives it flex: 1 1 auto.
   -------------------------------------------------------------------------- */

.single-product form.cart .quantity {
  display: none !important;
}


/* --------------------------------------------------------------------------
   P7.2  "IN STOCK" IS NOISE
   If the button is active, the item is in stock. Saying so adds a line above
   the primary action for no information.

   Out of stock DOES stay: that one changes what the customer can do.
   -------------------------------------------------------------------------- */

.single-product .stock.in-stock {
  display: none !important;
}

.single-product .stock.out-of-stock {
  font-size: var(--lq-t-sm);
  color: var(--lq-muted);
  margin: 0;
}


/* --------------------------------------------------------------------------
   P7.3  SOLD-OUT SIZES NEED TO BE DISTINGUISHABLE
   When every size is unavailable, muting them all makes the row look like a
   style choice rather than a stock state. A hairline through the swatch reads
   as "this one specifically is gone" at a glance, without shouting like the
   red cross Blocksy draws by default.
   -------------------------------------------------------------------------- */

/* EXACT SELECTORS, read from the DOM. Three earlier attempts guessed at class
   names containing "disabled"; Blocksy uses ct-out-of-stock instead, so none
   of them could ever have matched.

   Real markup:
     <div class="ct-variation-swatches" data-swatches-type="button"
                                        data-swatches-shape="square">
       <button class="ct-swatch-container ct-out-of-stock"
               data-value="s" aria-label="S" aria-pressed="false">
*/

/* MINIMAL, on purpose. Two earlier versions of this block imposed their own
   border, padding and min-width, first on the <button> and then on its child.
   Both were wrong: the visible box is drawn deeper still, so every size I set
   stacked on top of Blocksy's and inflated the swatches.

   Blocksy's own sizing was fine. All that actually needed changing:

   1. The active fill. Blocksy paints it with --theme-palette-color-1, which
      IS the Liquicity accent blue, because that is what the palette sets it
      to. Remapping the variable inside the swatch group turns every active
      state to ink at once, without touching a single element's layout.

   2. A hairline for sold-out sizes, drawn on the button so it matches
      whatever dimensions Blocksy gives it. */

.ct-variation-swatches {
  --theme-palette-color-1: var(--lq-ink);
  --theme-palette-color-2: var(--lq-ink);
}

.ct-variation-swatches .ct-swatch-container {
  position: relative;
  cursor: pointer;
}

.ct-variation-swatches .ct-swatch-container.ct-out-of-stock::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
    to top right,
    transparent calc(50% - 0.5px),
    var(--lq-rule) calc(50% - 0.5px),
    var(--lq-rule) calc(50% + 0.5px),
    transparent calc(50% + 0.5px)
  );
}

/* Sold out AND selected: the fill is ink, so a grey hairline would vanish. */
.ct-variation-swatches .ct-swatch-container.ct-out-of-stock[aria-pressed="true"]::after {
  background: linear-gradient(
    to top right,
    transparent calc(50% - 0.5px),
    rgba(255, 255, 255, 0.45) calc(50% - 0.5px),
    rgba(255, 255, 255, 0.45) calc(50% + 0.5px),
    transparent calc(50% + 0.5px)
  );
}

/* The <select> Blocksy keeps for accessibility is visually redundant once
   swatches render, and it carries its own out-of-stock option styling. */
.ct-variation-swatches > select {
  display: none;
}


/* --------------------------------------------------------------------------
   P7.4  VARIATION LOADING OVERLAY
   Selecting a size triggers jQuery blockUI, which paints a dark panel over
   the whole form for a fraction of a second. Against a white page that reads
   as a glitch. Same mechanism, white instead of near-black.
   -------------------------------------------------------------------------- */

.blockUI.blockOverlay,
.single-product form.cart .blockUI.blockOverlay,
.woocommerce-variation-add-to-cart .blockUI.blockOverlay {
  background: var(--lq-paper) !important;
  opacity: 0.65 !important;
}

/* The spinner blockUI draws is a ::before on the overlay. Keep it subtle. */
.blockUI.blockOverlay::before {
  border-top-color: var(--lq-ink) !important;
}


/* --------------------------------------------------------------------------
   P7.5  WAITLIST
   The store runs TWO waitlist systems. WooCommerce Waitlist (wcwl_ prefix)
   holds the real subscribers and stays. Blocksy Pro's own waitlist module is
   switched off at Blocksy > Extensions, since there is no theme_mod for it.

   The rules below style the WooCommerce Waitlist form, and hide Blocksy's as
   a safety net in case the extension is ever re-enabled by accident. Two
   forms on one product page would mean two subscriber lists diverging
   silently, which is worse than either being absent.

   [VERIFY] the Blocksy selector against a page where its module is active.
   -------------------------------------------------------------------------- */

/* Belt and braces: Blocksy's waitlist stays out even if the module returns. */
.ct-waitlist,
.ct-waitlist-form,
[class*="ct-waitlist"] { /* [VERIFY] */
  display: none !important;
}

.wcwl_wrap,
.wcwl_add_to_waitlist {
  margin-top: var(--lq-s4);
}

.wcwl_wrap p,
.wcwl_add_to_waitlist p {
  font-size: var(--lq-t-sm);
  color: var(--lq-muted);
  margin: 0 0 var(--lq-s3);
}

.wcwl_wrap input[type="email"],
.wcwl_add_to_waitlist input[type="email"] {
  width: 100%;
  border: 1px solid var(--lq-rule);
  padding: var(--lq-s3) var(--lq-s4);
  margin-bottom: var(--lq-s2);
}


/* ==========================================================================
   PATCH 08 — MORE EXACT SELECTORS FROM THE DOM
   Two more guesses replaced by what Blocksy actually outputs.
   ========================================================================== */


/* --------------------------------------------------------------------------
   P8.1  CART ACTIONS ROW
   Real markup: <div class="ct-cart-actions" data-add-to-cart="ajax">
   This is the flex row holding the quantity stepper and the button, so it is
   the right place to control that row rather than form.cart.
   -------------------------------------------------------------------------- */

.ct-cart-actions {
  display: flex;
  gap: var(--lq-s3);
  align-items: stretch;
  width: 100%;
}

.ct-cart-actions .quantity {
  display: none !important;
}

.ct-cart-actions .single_add_to_cart_button {
  flex: 1 1 auto;
  min-height: 64px;
}


/* --------------------------------------------------------------------------
   P8.2  DISABLED ADD-TO-CART STATE
   Real class: .woocommerce-variation-add-to-cart-disabled on the wrapper.
   Cleaner than styling :disabled on the button, because the wrapper class is
   what WooCommerce actually toggles when a variation goes out of stock.
   -------------------------------------------------------------------------- */

.woocommerce-variation-add-to-cart-disabled .single_add_to_cart_button {
  background: var(--lq-rule) !important;
  color: var(--lq-muted) !important;
  border-color: var(--lq-rule) !important;
  cursor: not-allowed;
}


/* --------------------------------------------------------------------------
   P8.3  THE SIZE LABEL
   Real markup: <label for="pa_size">Size<span>: M</span></label>

   Tomorrowland shows just "Size" with the chosen value implied by the
   pressed swatch. The ": M" span duplicates what the black border already
   says, so it goes, leaving the label short and the row calm.

   Remove this rule if you would rather keep the explicit confirmation.
   -------------------------------------------------------------------------- */

.variations .label label span {
  display: none;
}


/* ==========================================================================
   PATCH 08 — SOLD-OUT UX
   Two call-to-actions were stacked on an unavailable product: a waitlist
   button and a greyed ADD TO CART directly beneath it. The dead one is
   larger and lower, which is where the eye lands.

   Principle: at any moment there is exactly ONE primary action. In stock it
   is Add to cart. Out of stock it is the waitlist.
   ========================================================================== */


/* --------------------------------------------------------------------------
   P8.1  SELECTED SIZE — merged into P7.3
   This section duplicated the swatch rules at container level, which is what
   produced the double box and the blue square inside the black one. The
   working rules target the inner element and live in P7.3.
   -------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------
   P8.2  ONE ACTION AT A TIME
   When the waitlist form is on the page, the product cannot be bought, so
   the add-to-cart button is noise. :has() lets CSS ask "is a waitlist form
   present in this summary" without touching PHP.

   The swatches stay: they live inside form.cart and the customer still needs
   them to pick which size to wait for.
   -------------------------------------------------------------------------- */

/* WooCommerce marks an unbuyable variation on the wrapper itself:

     <div class="woocommerce-variation-add-to-cart variations_button
                 woocommerce-variation-add-to-cart-disabled">

   and a fully sold-out product carries "outofstock" on the product div. Both
   come from the DOM rather than from guesswork, unlike the :has() rule that
   was here before and matched nothing.

   Hiding the wrapper does not break the form: the hidden add-to-cart,
   product_id and variation_id inputs inside it still submit. */

.woocommerce-variation-add-to-cart-disabled,
.product.outofstock .single_add_to_cart_button {
  display: none !important;
}

/* The rule above was not enough: the button stayed visible in both the empty
   and the confirmed waitlist state. WooCommerce puts the stock line and the
   waitlist inside .woocommerce-variation-availability, and the add-to-cart
   wrapper is a sibling further down .single_variation_wrap, so :has() can
   reach from one to the other.

   Both selectors are kept. The disabled class is the cheaper match when it is
   present; this catches the cases where it is not. */
.single_variation_wrap:has(.stock.out-of-stock) .woocommerce-variation-add-to-cart,
.single_variation_wrap:has(.wcwl_elements) .woocommerce-variation-add-to-cart,
.summary:has(.wcwl_elements) .single_add_to_cart_button,
.entry-summary:has(.wcwl_elements) .single_add_to_cart_button {
  display: none !important;
}

/* Belt and braces for Blocksy's own ajax cart wrapper, which renders inside
   the variation button area and could otherwise survive on its own. */
.single_variation_wrap:has(.wcwl_elements) .ct-cart-actions {
  display: none !important;
}

/* "Out of stock" sitting directly above "Email me when this size is back in
   stock" says the same thing twice. The diagonal on the swatch already
   carries the state, so the sentence that offers an action wins. */
.single-product .stock.out-of-stock {
  display: none !important;
}


/* --------------------------------------------------------------------------
   P8.3  WAITLIST AS THE PRIMARY ACTION
   Since it is now the only thing to click, it gets the treatment the
   add-to-cart button would have had: full width, ink, uppercase, 64px.
   -------------------------------------------------------------------------- */

/* Real markup, from the DOM:

     <div class="woocommerce-variation-availability">
       <p class="stock out-of-stock">Out of stock</p>
       <div class="wcwl_elements bound">
         <div class="wcwl_intro">…</div>
         <div class="wcwl_notice woocommerce-message">
           <div aria-live="polite"><p>You have been added…</p></div>
           <button class="wcwl_notice_dismiss">
         </div>
         <div class="wcwl_optin">…checkbox…</div>
         <div class="wcwl_email_elements">
           <label class="wcwl_email_label wcwl_visually_hidden">
           <input class="wcwl_email" name="wcwl_email">
         </div>
         <a class="wcwl_control" href="#" data-context="join">Join Waitlist</a>
       </div>

   Two things that broke earlier attempts: the submit is an <a>, not a
   <button>, and there is a deliberately hidden <label> that a generic
   "label { display: flex }" rule would have made visible to everyone.
   -------------------------------------------------------------------------- */

/* The whole thing becomes one bordered box, so it reads as a single offer
   rather than four loose elements stacked under a dead button. */
.wcwl_elements {
  border: 1px solid var(--lq-rule);
  padding: var(--lq-s4);
  display: flex;
  flex-direction: column;
  gap: var(--lq-s3);
  margin: 0;
}

.wcwl_intro,
.wcwl_intro p {
  font-size: var(--lq-t-sm) !important;
  color: var(--lq-ink) !important;
  margin: 0 !important;
  line-height: 1.5;
}

/* THE CHECKBOX IS NOT HIDDEN HERE ANY MORE, and that was a real bug.
   WooCommerce Waitlist can require the opt-in before it will accept a
   submission. Hiding a required field with CSS does not remove the
   requirement: the validation just fails with nothing on screen to explain
   why, which is why both Join Waitlist and Leave Waitlist stopped working.

   To remove it properly, switch it off in the plugin:
     WooCommerce > Settings > Products > Waitlist > opt-in / consent
   Then it never renders and there is nothing to hide.

   Until then it stays visible, styled as the footnote it is. */
.wcwl_optin,
.wcwl_optin label {
  font-size: var(--lq-t-xs) !important;
  line-height: 1.5;
  color: var(--lq-muted) !important;
  margin: 0;
}

.wcwl_optin label {
  display: flex;
  align-items: flex-start;
  gap: var(--lq-s2);
}

.wcwl_optin input[type="checkbox"] {
  margin: 2px 0 0;
  flex: 0 0 auto;
}

.wcwl_email_elements {
  margin: 0;
}

/* Leave the visually hidden label exactly as it is: it is what a screen
   reader announces for the input, which has only a placeholder otherwise. */
.wcwl_visually_hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

input.wcwl_email {
  width: 100%;
  min-height: 52px;
  box-sizing: border-box;
  border: 1px solid var(--lq-rule);
  background: var(--lq-paper);
  padding: 0 var(--lq-s4);
  font-size: var(--lq-t-base);
  margin: 0;
}

input.wcwl_email:focus {
  border-color: var(--lq-ink);
  outline: none;
}

/* The submit control. Styling lives in P10.1: the anchor turned out to wrap a
   real <button>, so treating the anchor itself as the button produced two
   stacked buttons. Nothing here on purpose. */


/* --------------------------------------------------------------------------
   WAITLIST SUCCESS STATE
   Confirming that someone is on the list is the one moment in this flow that
   deserves to feel finished. Default is a grey panel with a bell and a faint
   close cross floating over the text.
   -------------------------------------------------------------------------- */

.wcwl_notice.woocommerce-message {
  position: relative;
  /* display is deliberately NOT set here: the plugin toggles it, and
     overriding that is what made an empty confirmation box appear before
     anyone had submitted anything. P9.1 sets it conditionally instead. */
  align-items: flex-start;
  gap: var(--lq-s3);
  background: var(--lq-canvas) !important;
  border: none !important;
  padding: var(--lq-s4) var(--lq-s12) var(--lq-s4) var(--lq-s4);
  margin: 0 !important;
  font-size: var(--lq-t-sm);
  color: var(--lq-ink);
}

.wcwl_notice.woocommerce-message p {
  margin: 0 !important;
  line-height: 1.5;
}

/* Replace the bell with a check: the message is a confirmation, and a bell
   reads as "something is about to happen" rather than "this is done". */
.wcwl_notice.woocommerce-message::before {
  content: "" !important;
  flex: 0 0 18px;
  width: 18px;
  height: 18px;
  margin: 1px 0 0 !important;
  background: var(--lq-ink);
  -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path d='M20 6L9 17l-5-5'/></svg>") center / 18px no-repeat;
  mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><path d='M20 6L9 17l-5-5'/></svg>") center / 18px no-repeat;
}

.wcwl_notice.woocommerce-message::after {
  content: none !important;
}

/* Dismiss: out of the text, into the corner, and actually visible. */
button.wcwl_notice_dismiss {
  position: absolute;
  top: var(--lq-s2);
  right: var(--lq-s2);
  width: 28px;
  height: 28px;
  min-height: 0;
  padding: 0;
  background: transparent !important;
  border: none !important;
  color: var(--lq-muted) !important;
  opacity: 1 !important;
  cursor: pointer;
}

button.wcwl_notice_dismiss:hover {
  color: var(--lq-ink) !important;
  background: transparent !important;
}


/* --------------------------------------------------------------------------
   P8.4  SIZE LABEL ROW
   Tomorrowland puts "Size" left and "Size guide" right on one line, then the
   swatches underneath. F7 already stacks the label above the swatches; this
   makes room for a size-guide link on the same row.

   The link itself comes from Blocksy Pro's Size Guide element, which is not
   enabled yet. Enabling it is a Customizer change, not a code one.
   -------------------------------------------------------------------------- */

.variations th.label,
.variations .label {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--lq-s4);
}

/* WooCommerce appends ": M" to the label. Tomorrowland shows the chosen size
   in the swatch fill instead, which is where the eye already is. */
.variations .label span {
  color: var(--lq-muted);
  font-weight: 400;
}


/* ==========================================================================
   PATCH 09 — WAITLIST FIXES
   Five issues from staging, three of which trace back to one mistake:
   putting display:flex on .wcwl_notice forced an empty confirmation box to
   render before anyone had done anything.
   ========================================================================== */


/* --------------------------------------------------------------------------
   P9.1  EMPTY NOTICE MUST STAY HIDDEN
   The plugin leaves .wcwl_notice in the DOM and toggles it. Giving it a
   display value in CSS overrode that, so the grey panel appeared with nothing
   in it but the checkmark and the dismiss cross.

   :has() lets the rule say "only lay this out when it actually contains a
   non-empty paragraph", which is the real condition.
   -------------------------------------------------------------------------- */

.wcwl_notice.woocommerce-message:not(:has(p:not(:empty))) {
  display: none !important;
}

.wcwl_notice.woocommerce-message:has(p:not(:empty)) {
  display: flex;
}


/* --------------------------------------------------------------------------
   P9.2  CHECKMARK OVERLAPPED THE TEXT
   WooCommerce sets .woocommerce-message::before to position:absolute with its
   own top/left. Replacing the glyph without also releasing the positioning
   left it sitting on top of the first word.
   -------------------------------------------------------------------------- */

.wcwl_notice.woocommerce-message::before {
  position: static !important;
  top: auto !important;
  left: auto !important;
}

.wcwl_notice.woocommerce-message > div {
  flex: 1 1 auto;
  min-width: 0;
}


/* --------------------------------------------------------------------------
   P9.3  BUTTON HEIGHT AND THE LOADING COLLAPSE
   min-height let the anchor grow with whatever the plugin puts inside it,
   which is why it rendered three times taller than intended. During submit
   the plugin swaps the label for a spinner and the whole box shrank to that
   spinner's width.

   A fixed height plus a stretched container fixes both: the button keeps its
   size whatever it contains, and the box keeps its width.
   -------------------------------------------------------------------------- */

.wcwl_elements {
  width: 100%;
  align-items: stretch;
  box-sizing: border-box;
}

/* Sizing also moved to P10.1, onto the inner <button> where it belongs. */
a.wcwl_control {
  align-self: stretch;
}

/* The spinner the plugin injects on submit. Centred, in ink, sized to fit. */
a.wcwl_control img,
a.wcwl_control svg,
a.wcwl_control .wcwl_loader { /* [VERIFY] */
  width: 20px;
  height: 20px;
  margin: 0 auto;
  filter: invert(1) brightness(2);
}


/* --------------------------------------------------------------------------
   P9.4  SOLD-OUT LABELS LOOKED BLURRY
   Blocksy's "faded" out-of-stock style lowers opacity on the whole swatch.
   Text at reduced opacity antialiases softly and reads as out of focus rather
   than as unavailable.

   Same visual weight, achieved with colour instead of transparency, which
   keeps the glyph crisp.
   -------------------------------------------------------------------------- */

/* NO UNIVERSAL SELECTOR HERE. An earlier version used
   ".ct-out-of-stock * { opacity: 1 !important }" to undo Blocksy's faded
   look. Blocksy hides the swatch tooltip with opacity, so that rule made
   every tooltip permanently visible, stacked across the whole row.

   Setting opacity on the container only leaves the tooltip's own opacity
   alone. */

.ct-variation-swatches .ct-swatch-container.ct-out-of-stock {
  opacity: 1 !important;
}

/* Colour on the container; children inherit unless Blocksy sets their own.
   Deliberately not using * so the tooltip is untouched. */
.ct-variation-swatches .ct-swatch-container.ct-out-of-stock:not([aria-pressed="true"]) {
  color: var(--lq-muted) !important;
}


/* ==========================================================================
   PATCH 10 — WAITLIST CONTROL, AND VERTICAL RHYTHM
   ========================================================================== */


/* --------------------------------------------------------------------------
   P10.1  THE CONTROL IS AN <a> WRAPPING A <button>
   Real markup:

     <a class="wcwl_control" data-context="leave">
       <button type="button" class="woocommerce_waitlist button">Leave Waitlist</button>
       <div aria-live="polite" class="wcwl_visually_hidden"></div>
       <div class="spinner"></div>
     </a>

   Earlier rules styled the anchor as the button. Blocksy also styles the inner
   <button>, so two buttons rendered on top of each other: a full-width black
   anchor with a narrower black button floating in it. The plugin's own CSS
   adds margin: 10px 0 to that inner button as well.

   Correct division: the anchor is a plain full-width wrapper, the button is
   the button.
   -------------------------------------------------------------------------- */

a.wcwl_control {
  display: block;
  width: 100%;
  height: auto;
  min-height: 0;
  padding: 0;
  margin: 0;
  background: none !important;
  border: none !important;
  overflow: visible;
  text-decoration: none;
}

a.wcwl_control:hover {
  background: none !important;
}

.wcwl_elements a.wcwl_control button,
a.wcwl_control button.woocommerce_waitlist {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 52px;
  min-height: 0;
  margin: 0 !important;          /* the plugin sets 10px 0 */
  padding: 0 var(--lq-s4);
  box-sizing: border-box;
  background: var(--lq-ink) !important;
  color: #fff !important;
  border: 1px solid var(--lq-ink) !important;
  font-size: var(--lq-t-xs) !important;
  font-weight: 500 !important;
  letter-spacing: 0.08em !important;
  text-transform: uppercase !important;
}

.wcwl_elements a.wcwl_control button:hover {
  background: #2b2b2b !important;
  border-color: #2b2b2b !important;
}

/* Leaving is a reversal, so it reads as secondary. */
a.wcwl_control[data-context="leave"] button {
  background: transparent !important;
  color: var(--lq-ink) !important;
  border-color: var(--lq-rule) !important;
}

a.wcwl_control[data-context="leave"] button:hover {
  background: transparent !important;
  border-color: var(--lq-ink) !important;
}

/* The plugin hides the email field with .wcwl_hide once someone is on the
   list. It was still occupying a bordered row. */
.wcwl_email_elements.wcwl_hide,
.wcwl_hide {
  display: none !important;
}

/* Its spinner, which sits beside the button rather than inside it. */
a.wcwl_control .spinner {
  display: none;
}

a.wcwl_control.loading .spinner { /* [VERIFY] loading class */
  display: block;
  margin: var(--lq-s2) auto 0;
}


/* --------------------------------------------------------------------------
   P10.2  VERTICAL RHYTHM
   Measured against the reference at the same viewport width:

                              Tomorrowland   staging
     header bottom to breadcrumb     44px      87px
     breadcrumb to gallery           61px     141px

   product_content_area_spacing = "bottom" in the Customizer import removes
   most of the second gap. These rules take out what is left, which comes
   from margins on the breadcrumb and the product wrapper.
   -------------------------------------------------------------------------- */

/* The breadcrumb margins were declared here a second time, scoped to
   .single-product. That made the product page different from every other
   template by construction, and it stacked on the hero band's own padding.
   §4 now sets the band height for all templates and F3 zeroes the margin.
   Do not reintroduce a scoped version of either. */

.single-product .product-entry-wrapper,
.single-product div.product {
  margin-top: 0 !important;
  padding-top: 0 !important;
}

/* Blocksy's header rows default to a generous height. The reference fits two
   rows into roughly the height of this one.

   There is a proper control for this at Header > (row) > Height, and using it
   is better than this rule. [VERIFY] the row attribute name first. */
header [data-row="middle"] { /* [VERIFY] */
  --height: 76px;
}

header [data-row="bottom"] {
  --height: 56px;
}


/* ==========================================================================
   PATCH 11 — MOBILE GALLERY AS A SWIPER
   On desktop the stacked gallery is right: two images side by side, the whole
   set visible without interaction. On a phone that same setting renders every
   image full width, one under the other, so a product with six photos pushes
   the title, the price and the add-to-cart button roughly three screens down.
   The reference shows one image at a time and swipes sideways.

   Blocksy's gallery type is a single global setting with no responsive
   variant, so this cannot be a Customizer decision. It is CSS, and it is the
   same pattern PATCH 05 uses for related products: native horizontal
   overflow plus scroll-snap. No JavaScript, no library, and touch swipe,
   trackpad and keyboard all work because the browser is doing the scrolling.

   Real class names, from PATCH 04:
     .ct-product-gallery-container   gallery wrapper
     .ct-stacked-gallery-container   the image grid
     .ct-media-container             each figure

   Deliberately NOT touched: the aspect ratio. P4.1 already pins it to 4/5,
   which at 390px wide is a 487px tall image. That is close to the reference
   and leaves the summary starting within the first screen.
   ========================================================================== */

@media (max-width: 767px) {

  /* Nothing above the scroller may clip it. The first attempt set overflow-x
     without !important and the row rendered correctly at one image wide but
     would not scroll at all: the slides were there, laid out, and cut off.
     A clip higher up looks exactly like a scroller that does not work. */
  .single-product .ct-product-gallery-container,
  .single-product .woocommerce-product-gallery {
    overflow: visible !important;
  }

  /* The grid becomes a row. grid-template-columns has to be unset explicitly:
     leaving it in place means the flex items inherit column sizing from a
     grid that no longer applies, which produces cards at half width. */
  .single-product .ct-stacked-gallery-container {
    display: flex !important;
    grid-template-columns: none !important;
    flex-wrap: nowrap !important;
    overflow-x: auto !important;
    overflow-y: hidden !important;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    gap: 0 !important;
    margin: 0;

    /* Vertical swipe must still scroll the page. Without pan-y a finger that
       starts on the image and moves down gets swallowed by the carousel. */
    touch-action: pan-x pan-y;
    -webkit-overflow-scrolling: touch;

    /* No scrollbar here, unlike the related-products row. That one is on
       desktop where a mouse has no other affordance; this one is touch-only,
       a mobile scrollbar is drawn over the photo, and the progress bar below
       does the job properly. */
    scrollbar-width: none;
  }

  .single-product .ct-stacked-gallery-container::-webkit-scrollbar {
    display: none;
  }

  /* Every direct child is one full-width slide.
     Using > * rather than .ct-media-container on purpose: the first image is
     wrapped in span.liq-badge-anchor--single by the badge snippet, so the
     first child is not the same element as the rest. */
  .single-product .ct-stacked-gallery-container > * {
    flex: 0 0 100%;
    width: 100%;
    max-width: 100%;
    margin: 0;
    scroll-snap-align: start;
  }

  /* mandatory snapping plus a drag that ends between two slides can otherwise
     leave the row parked halfway. */
  .single-product .ct-stacked-gallery-container > *:last-child {
    scroll-snap-align: end;
  }

  /* The two columns stack here, and a flex column with no row-gap put the
     title 5px under the photo while the title and price sat 36px apart. The
     caption looked welded to the image and detached from its own price.

     This did not survive on its own: F4 was zeroing the gallery's own
     margin-bottom at every width, which is what actually closed the gap. That
     rule is now desktop-scoped and the mobile margin lives there. */
  .single-product .product-entry-wrapper {
    row-gap: var(--lq-s8);
  }

  /* Click and hold is the obvious gesture when the mobile layout is open on a
     desktop. Nothing in the native overflow model says the row is draggable,
     so say it. */
  .single-product .ct-stacked-gallery-container {
    cursor: grab;
  }

  .single-product .ct-stacked-gallery-container.is-dragging {
    cursor: grabbing;
    user-select: none;
  }

  /* Snap and drag cannot both be in charge. With mandatory snapping the
     browser re-aligns the row on every frame of a drag, so it moves in whole
     slides: you never see two photos at once and it does not feel like it is
     following your hand. functions.php adds this class while the pointer is
     down and removes it after settling on the nearest slide. */
  .single-product .ct-stacked-gallery-container.is-snap-off {
    scroll-snap-type: none;
  }
}


/* --------------------------------------------------------------------------
   P11.1  SLIDE INDICATOR
   Built by functions.php, which knows how many slides there are and which one
   is in view. Without it the swiper is invisible: one photo, edge to edge,
   with nothing saying there are five more.

   A segmented bar rather than dots, because it says both things at once: how
   many photos exist and where you are. Two pixels tall and in the hairline
   grey, so it reads as a scrollbar rather than as a control, which is honest,
   since it is not tappable.
   -------------------------------------------------------------------------- */

.lq-gallery-progress {
  display: flex;
  gap: 2px;
  margin-top: var(--lq-s3);
}

.lq-gallery-progress > span {
  flex: 1 1 0;
  height: 2px;
  background: var(--lq-rule);
  transition: background 0.2s ease;
}

.lq-gallery-progress > span.is-active {
  background: var(--lq-ink);
}

/* Desktop shows every image at once, so there is nothing to indicate. The
   script removes it as well; this covers the moment before that runs. */
@media (min-width: 768px) {
  .lq-gallery-progress {
    display: none;
  }
}


/* ==========================================================================
   PATCH 12 — HOMEPAGE COMPONENTS
   Everything here supports blocks placed in the editor. No new markup is
   invented: the product rows are [products] and [liq_signup_products], both
   of which render ul.products with li.product, so §6 already styles the
   cards. This section only adds what the homepage needs on top.
   ========================================================================== */

/* --------------------------------------------------------------------------
   P12.1  SECTION HEADINGS
   The reference sets a section title, then the row, with nothing between them
   but air. Same type as the archive title one step down.
   -------------------------------------------------------------------------- */

.home .wp-block-heading {
  margin-bottom: var(--lq-s6);
}

.liq-product-row,
.home .woocommerce .products {
  margin-bottom: var(--lq-s16);
}

/* The row has to fill its Group, not sit centred inside it.
   Two different width systems meet here and neither knows about the other:

     Blocksy   .is-layout-constrained > *   width: var(--theme-block-width)
     core      .is-layout-constrained > *   max-width: var(--wp--style--global--content-size)
                                            margin-left / right: auto !important

   The Group takes Blocksy's width. Its child, this row, then takes core's
   narrower content-size and the auto margins centre what is left over. Result:
   the heading starts at the container edge and the products start about 25px
   further in, on both sides, so nothing on the page lines up with anything.

   The !important is not optional: core writes the auto margins with one.
   This is not a container breakout. The row is being pulled back OUT to its
   parent's width, never past it, so the one-container rule still holds. */
.liq-product-row {
  width: 100%;
  max-width: none;
  margin-left: 0 !important;
  margin-right: 0 !important;
}


/* --------------------------------------------------------------------------
   P12.2  PRODUCT ROWS PLACED BY HAND
   Everything here hangs off .liq-product-row, the wrapper [liq_products]
   writes. Category archives are untouched: those are a grid you scroll down
   through, and turning them sideways would hide most of the catalogue behind
   a gesture.

   BELOW 768px A ROW IS ALWAYS A SCROLLER. Not opt in. A row placed on a page
   is a selection, and a selection stacked vertically on a phone stops being
   one: eight cards is eight screens, the section below it is unreachable, and
   the page reads as an archive that will not end. Sideways keeps the whole
   selection in one gesture and keeps the next section within reach.

   Above 768px the default is Blocksy's grid, wrapping onto as many rows as
   the limit needs. Add is-scroller to the Group block wrapping the shortcode
   to force one row there too. (The Shortcode block has no Additional CSS
   class field; core declares className: false on it. Hence the Group.)

   THE PEEK IS THE WHOLE MECHANISM. --lq-per-view is deliberately fractional:
   at 4.5 the fifth card is cut in half by the container edge, and a half card
   is the only affordance that needs no explaining. Whole numbers look like a
   grid that happens to end, which is the wrong signal.

   Native overflow, so touch swipe and trackpad come free. functions.php adds
   mouse drag through the shared liqDragScroll. No scroll-snap: these are
   browsable rather than page-sized, and snapping fights a drag meant to be
   loose.

   To change the count on one row, add a class next to is-scroller and set
   --lq-per-view on it.
   -------------------------------------------------------------------------- */

.liq-product-row .products {
  --lq-per-view: 4.5;
  --lq-scroller-gap: var(--lq-s6);
}

/* The scroller itself. Two selectors, one body: the class turns it on at any
   width, the media query turns it on for every hand-placed row on a phone. */
.is-scroller .products,
.liq-product-row .products.is-scrolling {
  display: flex !important;
  grid-template-columns: none !important;
  flex-wrap: nowrap !important;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  gap: var(--lq-scroller-gap) !important;
  scrollbar-width: none;
  cursor: grab;
}

.is-scroller .products::-webkit-scrollbar,
.liq-product-row .products.is-scrolling::-webkit-scrollbar {
  display: none;
}

.is-scroller .products.is-dragging,
.liq-product-row .products.is-scrolling.is-dragging {
  cursor: grabbing;
  user-select: none;
}

/* (100% minus the gaps that fall between the visible cards) divided by how
   many should be visible. The gap count is per-view minus one, so at 4.5 that
   is 3.5 gaps, which is what actually sits inside the viewport. */
.is-scroller .products > .product,
.liq-product-row .products.is-scrolling > .product {
  flex: 0 0 calc(
    (100% - (var(--lq-per-view) - 1) * var(--lq-scroller-gap)) / var(--lq-per-view)
  );
  max-width: none;
}

/* Kills the click that ends a drag, so releasing the mouse over a product does
   not open it. */
.is-scroller .products.is-dragging a,
.liq-product-row .products.is-scrolling.is-dragging a {
  pointer-events: none;
}

@media (max-width: 1199px) {
  .liq-product-row .products {
    --lq-per-view: 3.5;
  }
}

@media (max-width: 899px) {
  .liq-product-row .products {
    --lq-per-view: 2.5;
    --lq-scroller-gap: var(--lq-s4);
  }
}

/* is-scrolling is added by functions.php below 768px. A class rather than a
   media query around the block above, because the same declarations then serve
   both the opt-in and the automatic case without being written twice, and
   because the drag script needs to agree with the stylesheet about which rows
   are scrollers. One source of truth, checked on resize. */
@media (max-width: 767px) {
  .liq-product-row .products {
    --lq-per-view: 1.5;
  }
}





/* --------------------------------------------------------------------------
   P12.3  CATEGORY TILES
   Sweaters, Jackets, T-Shirts, Accessories, Caps & Hats.

   These are the tiles from the production site: PNGs with the category name
   set into the artwork, a coloured gradient panel behind the model, and
   transparency above it so the head breaks out of the panel.

   THAT CHANGES THE COMPONENT. An earlier version of this block built them from
   Cover blocks with a CSS gradient and a text label on top. Wrong for this
   artwork three times over: Cover crops to a fixed ratio and would cut the
   heads off, its overlay would sit over a panel that already has one, and a
   text label would print the category name a second time.

   So they are plain linked Image blocks. No ratio is imposed: the PNGs come
   from one batch and already agree with each other, and forcing a ratio on an
   image with transparent margins crops exactly the part that makes it work.

   Five tiles do not divide into two columns, so mobile scrolls sideways rather
   than leaving a lone tile on a third row. Same model as the other scrollers
   on this site, and it takes any number of categories without a rethink.
   -------------------------------------------------------------------------- */

.lq-cat-tiles {
  display: grid !important;
  grid-template-columns: repeat(5, 1fr);
  gap: var(--lq-s6);
  align-items: end;
  margin-bottom: var(--lq-s16);
}

/* The Columns block writes an inline flex-basis on every column. Neutralised
   so the grid above owns the sizing rather than fighting it. */
.lq-cat-tiles > .wp-block-column {
  flex-basis: auto !important;
  margin: 0 !important;
  min-width: 0;
}

.lq-cat-tiles .wp-block-image,
.lq-cat-tiles figure {
  margin: 0;
}

.lq-cat-tiles img {
  display: block;
  width: 100%;
  height: auto;
}

/* No hover lift and no shadow. The artwork carries its own colour panel, and
   anything added on top of five of those at once turns into noise. A quiet
   fade is enough to say it is clickable. */
.lq-cat-tiles a {
  display: block;
  transition: opacity 0.2s ease;
}

.lq-cat-tiles a:hover,
.lq-cat-tiles a:focus-visible {
  opacity: 0.85;
}

@media (max-width: 900px) {
  .lq-cat-tiles {
    grid-template-columns: none !important;
    display: flex !important;
    flex-wrap: nowrap !important;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x proximity;
    gap: var(--lq-s4);
    scrollbar-width: none;
  }

  .lq-cat-tiles::-webkit-scrollbar {
    display: none;
  }

  /* 42% shows two tiles and a slice of the third, which is what says the row
     continues. Snap is proximity rather than mandatory: these are browsable
     rather than page-sized, so fighting the scroll to align them is wrong. */
  .lq-cat-tiles > .wp-block-column {
    flex: 0 0 42%;
    scroll-snap-align: start;
  }

  /* Mouse drag is bound by functions.php. Nothing in the native overflow model
     says the row is draggable, so say it. */
  .lq-cat-tiles {
    cursor: grab;
  }

  .lq-cat-tiles.is-dragging {
    cursor: grabbing;
    user-select: none;
  }
}

/* Kills the click that ends a drag, so releasing the mouse over a tile does not
   open that category. Listed explicitly alongside the other scrollers rather
   than as a bare .is-dragging: adding a fourth row to this list should be a
   deliberate line here. */
.lq-cat-tiles.is-dragging a,
.lq-chips--scroll.is-dragging a {
  pointer-events: none;
}

/* FIXED. This rule had no declaration block: the selector ended in a comma and
   the next thing in the file was a comment, so it joined the swatch-hiding rule
   in P12.5 below. Two things followed from that, and neither was visible in a
   brace count.

   The tiles got display: none the moment a drag started, so dragging the
   category row on the homepage blanked the tiles under the cursor.

   And pointer-events: none was never applied anywhere, so the belt half of
   belt-and-braces was missing. The braces half still worked: liqDragScroll
   cancels the click in a capture listener, which is what actually stops a drag
   from opening a category.

   The chips strip is the fourth entry, added deliberately as the comment above
   asks. */


/* --------------------------------------------------------------------------
   P12.5  SIZE AVAILABILITY ON THE CARD
   Rendered by functions.php on every variable product with more than one size,
   wherever a card appears: archives, search, homepage rows, related products.

   On every card rather than only the ones with a gap, for two reasons. A card
   carrying this line is taller than one without, so a mixed row put its
   captions at different heights and started the next row ragged. And a struck
   through XL reads as missing because its neighbours are complete; when the
   line itself is the exception you have to notice it before you can read it.

   Deliberately not a hover reveal. The reference puts its sizes behind
   mouse-over, which hides them on touch, on the half of the traffic where a
   dead click is most annoying.

   Quiet on purpose. This sits under a title and a price that are already
   competing, so it runs at the smallest step in the scale and in the muted
   grey. It is a footnote to the caption, not a third line of it.
   -------------------------------------------------------------------------- */

/* Blocksy's card swatch container renders whether or not it has anything to
   draw. With product_attributes_source on "custom" and no attribute chosen it
   draws nothing at all, and still takes a full grid row plus two gaps with it:
   measured 42px between the caption and this line where the row-gap is 8.

   Hiding is the smaller half of the fix. The element carries the entire
   variation dataset as JSON in a data attribute, images and all, and CSS does
   not stop that being sent. Switch the Swatches layer off at
   Customizer > WooCommerce > Product Archives > Product Card to actually drop
   the weight; this rule then becomes a no-op and can stay as a guard. */
.products .product .ct-card-variation-swatches {
  display: none;
}

.liq-card-sizes {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: var(--lq-s2);
  margin: 0;
  font-size: var(--lq-t-xs);
  line-height: 1.4;
  letter-spacing: 0.04em;
  color: var(--lq-muted);
}

/* Struck rather than hidden. Removing the sold-out sizes would leave "L XL"
   with no way to tell whether the small sizes ever existed, which reads as a
   product that only comes in two sizes rather than one that is selling out. */
.liq-card-sizes__item.is-out {
  text-decoration: line-through;
  opacity: 0.45;
}


/* ==========================================================================
   PATCH 13 — CARD IMAGE SWIPER
   Styling for liq_wrap_image_with_swiper() in functions.php. The markup is:

     <span class="liq-card-media">
       <span class="liq-card-swiper">
         <span class="liq-card-swiper__slide">…Blocksy's card image…</span>
         <span class="liq-card-swiper__slide"><img …></span>
       </span>
       <span class="liq-card-progress"><span class="…__thumb"></span></span>
     </span>

   All spans. The badge snippet wraps this in span.liq-badge-anchor, and a
   <div> inside a <span> is invalid phrasing content, which is a bug that file
   has already had once.

   NO ARROWS, BY DECISION
   The reference draws them. They would have to live inside whatever wraps the
   card image, and if that turns out to be the product link then a <button>
   inside an <a> is invalid interactive nesting. Swipe plus the indicator says
   the same thing without the question, and native overflow means trackpad and
   keyboard already work.

   NO cursor: grab EITHER. The mobile gallery in PATCH 11 sets it because that
   image is not a link. A card image is, and a grab cursor over a link tells
   someone the wrong thing about what a click will do. Mouse drag still works
   for anyone who tries it.

   MEASURED OFF THE REFERENCE, at 3x, converted to CSS px:

     indicator height            2px
     filled colour               #000000
     track colour                #BCBCBC
     position                    in flow, flush under the photo

   THE INDICATOR IS A SCROLLBAR, WHICH IS NOT WHAT IT LOOKS LIKE
   It reads as two segments, and it is not. On the reference the filled block
   measured 171px of a 512px card on one product and 128px of 511px on another:
   one third and one quarter, for three and four photographs. Caught mid-swipe
   the block is the same width and has moved half a step, so it tracks the
   finger rather than snapping between states.

   That is worth copying rather than approximating, because it means nothing
   here knows how many slides there are. PATCH 11's segmented bar has to be
   rebuilt when the count changes; this does not. Raising
   liq_card_swiper_slides to 3 needs no change in this file.

   IN FLOW, NOT ABSOLUTE. The reference appends it: the image box ends and the
   bar begins on the next pixel. Overlaying it on the photo would cost nothing
   in layout and would disappear over a dark frame, which is the failure the
   inverted NEW badge in PATCH 06 already documents. Two pixels of caption
   rhythm is the cheaper price.
   ========================================================================== */

.liq-card-media {
  display: block;
  position: relative;
}

.liq-card-swiper {
  display: flex;
  flex-wrap: nowrap;
  overflow-x: auto;
  overflow-y: hidden;

  /* No chaining. Without this a swipe that runs past the last photo starts
     moving whatever is behind the card. */
  overscroll-behavior-x: contain;

  scroll-snap-type: x mandatory;

  /* pan-y is not optional. Without it a finger that starts on the photo and
     moves down is swallowed by the carousel and the page will not scroll. */
  touch-action: pan-x pan-y;

  -webkit-overflow-scrolling: touch;

  /* The indicator is the scrollbar. A second one under it would be two. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.liq-card-swiper::-webkit-scrollbar {
  display: none;
}

/* Snap and drag cannot both be in charge. liqDragScroll adds this while the
   pointer is down and removes it after settling, and P11 relies on it.

   Kept although this track is no longer drag-scrolled: the class is part of
   the helper's contract, and the day somebody binds it here again this is what
   stops the snap from fighting the drag. It costs one selector. */
.liq-card-swiper.is-snap-off {
  scroll-snap-type: none;
}

.liq-card-swiper__slide {
  display: block;
  flex: 0 0 100%;
  width: 100%;
  max-width: 100%;
  margin: 0;
  scroll-snap-align: start;
}

/* THERE IS NO scroll-snap-align: end ON THE LAST SLIDE, AND THAT IS A FIX.

   It was here, copied from P11, and it put two of the three swipers on a
   staging archive at slide 2 on page load. Measured off the screenshot: the
   thumb was exactly 50% wide, which is right for two slides, and sitting hard
   against the right edge, which means scrollLeft equalled clientWidth before
   anyone had touched it. Card one was at zero and cards two and three were
   not, so it was not deterministic either.

   With scroll-snap-type: mandatory the browser re-runs snapping whenever the
   content resizes, and the second image is lazy, so it resizes after first
   paint. An end-aligned last slide makes the far end a valid snap target at
   exactly that moment, and which card jumps comes down to when its image
   happens to arrive.

   P11 needs the rule because that gallery has however many photos a product
   has and a drag can end past the last one. This does not: the slides are
   equal width, so the start-aligned position of the final slide already IS the
   end of the scroll range, and the rule was buying nothing.

   The script also pins the track to zero until the first interaction, which
   covers the same failure arriving from somewhere else. */

/* CONFIRMED AGAINST THE LIVE DOM, so the marker that was here is gone.
   Written out rather than deleted, because the next person to look at this
   rule should not have to re-measure it. Read off a T-Shirts card on staging,
   from the image upwards:

     img.wp-post-image          h=520   block   aspect-ratio 4/5
     a.ct-media-container       h=520   block   aspect-ratio auto
     span.liq-card-swiper__slide h=520
     span.liq-card-swiper       h=520   flex

   Blocksy's wrapper is an <a>, it is already block, it has no ratio of its
   own and it comes out exactly as tall as the photograph inside it. So this
   rule changes nothing today and is kept as a guard: an inline wrapper in a
   flex slide would collapse to its content width and the first photo would
   come out narrower than the second.

   Worth knowing for anything added later: THE PRODUCT LINK IS INSIDE THE
   SLIDE, not around the media. Anything appended to .liq-card-media is a
   sibling of that anchor and never a descendant of it, which is why the
   arrows in P13.2 are valid interactive content wherever they are switched
   on. */
.liq-card-swiper__slide > * {
  display: block;
  width: 100%;
}

/* The ratio is not set here. P4.1 already pins every card image to 4/5 with
   !important, against Blocksy's inline aspect-ratio, and the second slide is
   an <img> inside .products .product like any other. */


/* --------------------------------------------------------------------------
   P13.1  INDICATOR
   -------------------------------------------------------------------------- */

.liq-card-progress {
  display: block;
  height: 2px;
  overflow: hidden;
  background: #BCBCBC;
}

/* Not a token, and deliberately so. The palette has --lq-rule at #E2E2E2,
   which over two pixels on white is invisible and leaves the thumb looking
   like a bar that starts nowhere, and --lq-muted at #767676, which is dark
   enough to read as a second filled block. The measured reference value sits
   between them and there is nothing else in the file that wants it. */

.liq-card-progress__thumb {
  display: block;
  height: 100%;

  /* Both replaced by the script on the first frame. These are the two-slide
     case, so the bar is not wrong before JavaScript runs. */
  width: 50%;
  transform: translateX(0);

  background: var(--lq-ink);

  /* NO TRANSITION. The thumb is driven from the scroll position on every
     frame, so easing it would put it behind the finger rather than under it,
     which is the one thing this element exists to avoid. */
}


/* --------------------------------------------------------------------------
   P13.2  ON HOVER, WHERE THERE IS A HOVER

   The indicator earns its place on a phone: it is the only thing saying a
   second photograph exists, and there is no other way to find that out. On a
   desktop archive it is a full width black rule under every card in the grid,
   permanently, and at four cards across that reads as a heavy horizontal band
   running through the page rather than as a scrollbar belonging to one image.

   So it fades in with the pointer, and the arrows come with it. The reference
   does the same and for the same reason.

   NOT hidden, faded. The two pixels stay reserved, because a bar that appears
   on hover and pushes the caption down would make every card in the row twitch
   as the mouse crossed it.

   (hover: hover) and (pointer: fine) rather than a width breakpoint. What
   matters is whether there is a pointer that can hover, not how wide the
   screen is: a touch laptop and a phone both need this visible, and both are
   the wrong side of any breakpoint you could pick.

   THE ARROWS ARE BUILT IN JAVASCRIPT, not in PHP, and only inside this same
   media query. Two reasons. They are worth nothing without a pointer, so on a
   phone they should not be in the markup at all. And the script can check
   whether the card image sits inside the product link before adding them: a
   <button> inside an <a> is invalid interactive nesting, the same class of
   mistake as the <div> inside a <span> the badge snippet already fixed, and
   this is one thing that cannot be decided from a stylesheet.

   They carry tabindex="-1" and aria-hidden. Forty buttons between the first
   product and the second is the tab order problem the main docblock in
   functions.php refuses for the swiper itself, and the answer has to be the
   same here: these are a mouse affordance over a decorative alternate shot,
   and the full gallery is one click away on the product page.
   -------------------------------------------------------------------------- */

.liq-card-nav {
  position: absolute;
  top: 0;

  /* Stops short of the indicator, which is the last two pixels of the media
     box. An arrow sitting on top of it would cover the only thing that says
     where you are. */
  bottom: 2px;

  width: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  background: none;
  color: var(--lq-ink);
  cursor: pointer;
  z-index: 2;
}

.liq-card-nav--prev { left: 0; }
.liq-card-nav--next { right: 0; }

/* Measured off the reference at 3x: the chevron is 4 x 6.7 CSS px with a
   1.2px stroke, on a 170px card on a phone. Drawn larger here because these
   only ever exist on a desktop card, which is around 416px wide, and the
   measured size disappears on one. The proportions are the reference's. */
.liq-card-nav svg {
  display: block;
  width: 7px;
  height: 12px;
}

/* Colour rather than opacity, so this does not have to fight the reveal
   below for the same property. A grey chevron also reads better than a faded
   black one: it is still a chevron, it just cannot go anywhere. */
.liq-card-nav[disabled] {
  color: var(--lq-rule);
  cursor: default;
}

@media (hover: hover) and (pointer: fine) {

  /* THE INDICATOR IS MOBILE ONLY.
     It was faded in on hover, which was the reference behaviour. With the
     hover advance below it no longer has a job: the second photograph arrives
     on its own, so a bar saying which one you are looking at is answering a
     question nobody got to ask. On a phone it is still the only thing that
     says a second photograph exists at all.

     display: none rather than opacity: 0, so it gives its two pixels back to
     the caption as well. Nothing here shifts on hover any more, so there is
     no layout twitch to protect against. */
  .liq-card-progress {
    display: none;
  }

  /* And with the bar gone the arrows run the full height of the image. */
  .liq-card-nav {
    bottom: 0;
    opacity: 0;
    transition: opacity 0.15s ease;
  }

  .liq-card-media:hover .liq-card-nav {
    opacity: 1;
  }
}


/* --------------------------------------------------------------------------
   P13.3  INSIDE A ROW THAT SCROLLS SIDEWAYS

   The homepage rows scroll sideways below 768px and the related-products row
   does at every width. A card swiper inside one of those is two horizontal
   scrollers competing for the same gesture, and overscroll-behavior means the
   inner one wins and never hands over, so the row underneath could only be
   moved by starting the swipe on a caption.

   THE SPLIT IS BY INPUT, NOT BY WIDTH, and that is the whole idea.

   On a pointer that can hover, the interaction is the hover advance in
   functions.php, and hover is not a gesture. It cannot compete with a swipe
   because it never asks for one. So the second photograph works inside these
   rows exactly as it does in the grid, and the one way of MOVING the track by
   hand is taken away: overflow is closed, so a trackpad swipe goes to the row,
   where it belongs.

   Mouse drag is not a factor anywhere any more. The card track is the one
   scroller on this site that is never bound to liqDragScroll, because it is
   also the thing people click. See the footer script in functions.php.

   On touch there is nothing but gestures, so the second slide is hidden
   outright and the card is a single photograph again.

   This is a difference in behaviour between devices, which an earlier version
   of this comment argued against and moved into PHP to avoid. It is defensible
   now because it follows from the input rather than from a breakpoint: the
   desktop interaction cannot conflict and the touch one can. What is not
   defensible is the same card behaving differently at two widths on the same
   device, and that does not happen here.

   overflow: hidden, NOT overflow: clip. A hidden box is still a scroll
   container, so scrollTo keeps working and the hover advance still moves it.
   clip would take that away as well and the card would freeze on the first
   photograph.

   display: none on touch rather than overflow: hidden, for a second reason
   besides the gesture: it leaves scrollWidth equal to clientWidth, which is
   the condition liqDragScroll already tests on pointerdown, so the drag
   script disables itself without being told. And a lazy image inside a
   display: none slide is never requested, so the phone does not pay for a
   photograph it will not be shown.
   -------------------------------------------------------------------------- */

@media (hover: hover) and (pointer: fine) {

  /* Reachable by the hover advance, not by hand. */
  .products.is-scrolling .liq-card-swiper,
  .related.products .liq-card-swiper,
  .up-sells.products .liq-card-swiper {
    overflow: hidden;

    /* Mandatory snapping re-runs on every content resize, and with no way to
       scroll by hand there is nothing for it to be right about. It is also
       what put two cards on slide two at page load once already. */
    scroll-snap-type: none;

    /* Nothing is contained here any more, so let a stray gesture chain out to
       the row rather than dying on a track that cannot move. */
    overscroll-behavior-x: auto;
  }
}

@media (hover: none), (pointer: coarse) {
  .products.is-scrolling .liq-card-swiper__slide + .liq-card-swiper__slide,
  .related.products .liq-card-swiper__slide + .liq-card-swiper__slide,
  .up-sells.products .liq-card-swiper__slide + .liq-card-swiper__slide {
    display: none;
  }
}

/* No indicator in these rows on any device. On touch there is nothing left to
   indicate, and on a pointer P13.2 has already hidden it everywhere. */
.products.is-scrolling .liq-card-progress,
.related.products .liq-card-progress,
.up-sells.products .liq-card-progress {
  display: none;
}


/* --------------------------------------------------------------------------
   P13.4  WHAT IS DECIDED IN PHP AND NOT HERE

   Three exclusions, none of them in this file, and the reason is the same for
   all three: a rule here would leave the second photograph in the markup, and
   the markup is the part that costs something.

     Downloads          liq_card_swiper_extra_image_ids(). A release has one
                        square cover and there is no second angle of a piece
                        of artwork.
     [liq_products]     the shortcode, default off, swiper="yes" to opt in.
     Up-sells, related  woocommerce_after_single_product_summary at 14.

   So the swiper is a category-page, shop-page and search thing. That is what
   the reference does, and it is also the only place a card has to sell itself
   without a heading above it saying what the row is for.
   -------------------------------------------------------------------------- */
