/**
 * Theme Name: Blocksy Child
 * Description: Blocksy Child theme
 * Author: Creative Themes
 * Template: blocksy
 * Text Domain: blocksy
 * * Version: 1.0.7
 */

/* ==========================================================
   PASTE THIS INTO YOUR CHILD THEME'S style.css
   (this is the full file - replace the whole thing)
   ========================================================== */
/* ============================================
   SWATCH SELECTION & HOVER STATES
   (uses the plugin's own CSS variables - see notes)
   ============================================ */
html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

/* Removes checkmark from selected swatch in color selector */
:root {
    --wvs-tick: none;
}

/* Changes the color border on selected swatch in color selector */
:root {
    --wvs-selected-item-box-shadow: 0 0 0 1px #131313;
}

/* Changes the hover state in color selector */
:root {
    --wvs-hover-item-background-color: #e5e5e5;
    --wvs-hover-item-text-color: #2c3e50;
    --wvs-hover-item-box-shadow: none;
}

/* ============================================
   COLOR CAROUSEL
   ============================================
   The scrollable row of color swatches that sits directly under the main
   product image. Includes hiding the native (non-carousel) swatch/select
   controls that this display copy replaces, the single-row viewport, hover
   arrows, and the hover-reveal breakpoint (desktop-with-mouse only - touch
   devices stay swipe-only). */

/* Stacks the main image and swatch row vertically as one unit,
   so they sit together next to the product info column */
.ls-gallery-and-color-wrapper {
    display: flex;
    flex-direction: column;
    align-items: center;
}

/* === Hide the gallery's own thumbnail strip ===
   The main gallery image already has its own scroll/arrow navigation,
   so this separate thumbnail strip is redundant - customers can still
   reach every gallery image through the main image's built-in controls. */
.woocommerce-product-gallery .flexy-pills {
    display: none !important;
}

/* Closes the empty space the thumbnails used to reserve, so the main
   image doesn't have an awkward leftover gap where they used to sit */
.woocommerce-product-gallery .flexy {
    margin-inline-start: 0 !important;
}

/* EDITED 8/27/2026 - hides Blocksy's own main gallery prev/next arrow
   buttons (.flexy-arrow-prev/.flexy-arrow-next, Blocksy core - not a
   child theme component) on mobile/tablet only. Touch devices scroll the
   gallery by swipe already, so the buttons are redundant screen space
   below 1000px; desktop (mouse-driven, no swipe) keeps them untouched. */
@media (max-width: 999px) {
    .woocommerce-product-gallery .flexy-arrow-prev,
    .woocommerce-product-gallery .flexy-arrow-next {
        display: none !important;
    }
}

/* === Carousel Step 1: Single row layout, no wrapping ===
   Forces the swatches into one line instead of the plugin's default
   wrapping grid layout. Required for the Step 2 viewport (below) to work,
   since a viewport can only scroll a row that's actually a single line. */

/* The swatch row container itself */
.ls-color-display-copy {
    display: flex !important;
    /* lay children out in a row instead of stacking */
    flex-wrap: nowrap !important;
    /* force everything onto ONE line - never wrap into a grid */
    gap: 12px;
    /* spacing between swatches - the ONLY source of spacing (see next rule) */
}

/* Individual swatch items inside the row */
.ls-color-display-copy .variable-item {
    margin: 0 !important;
    /* remove the plugin's own default 4px margin on each swatch - without this, the plugin's margin would ADD to our gap above,
     double-spacing everything and breaking scroll math later */
    flex: 0 0 90px;
    /* lock each swatch at a fixed 80px width - won't grow or shrink,so spacing/scroll math stays predictable */
}

.ls-color-display-copy .variable-item img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}

/* Controls the actual viewing area for the carousel.
   (Consolidated 2026-08-25: merged with a second, separate
   ".ls-swatch-carousel-viewport" block that only hid the scrollbar - same
   selector, same unconditional scope, no overlapping properties, so
   combining them changes nothing about which declarations apply.) */
.ls-swatch-carousel-viewport {
    width: calc((90px * 5) + (12px * 4));
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    /* NEW: buffer so the selected-outline doesn't get clipped by overflow-x above.
   box-sizing: content-box makes this padding ADD to the width instead of
   shrinking the visible swatch area - so all 4 swatches stay fully visible. */
    padding: 4px;
    box-sizing: content-box;
    /* Hide the scrollbar on the swatch carousel viewport. The carousel is
       still horizontally scrollable via JS/touch/arrows - we're just
       removing the visible scrollbar UI, since arrow buttons and swipe are
       the intended way to navigate it. */
    scrollbar-width: none;
    /* Firefox */
    -ms-overflow-style: none;
    /* old Edge / IE (harmless elsewhere) */
}

/* Chrome, Safari, and other WebKit browsers use a pseudo-element
   instead of a scrollbar-width property, so we hide it separately. */
.ls-swatch-carousel-viewport::-webkit-scrollbar {
    display: none;
}

/* Wrapper around the viewport so the arrow buttons can be positioned
   absolutely over it without being affected by the viewport's own
   horizontal scrolling - the buttons are SIBLINGS of the scrolling
   content, not children of it, so they never move when it scrolls. */
.ls-swatch-carousel-wrapper {
    position: relative;
    width: calc((90px * 5) + (12px * 4));
    /* must match the viewport's width below */
}

/* ============================================
   COLOR CAROUSEL WIDTH (desktop, >=1000px) - Job H
   ============================================
   The base rules above hardcode this row to exactly 5 items wide
   (498px) - correct only as long as the info column stays at least
   that wide. Job F's later column-width change, plus a subsequent
   image-size Customizer change (both change --product-gallery-width,
   and so how much width is left for the info column), can each
   independently break that assumption - confirmed live at 1280px with
   today's 50% image setting: the info column's own content width is
   436px, but this row still forced itself to 498px, overflowing 62px
   past the column's right edge. That's also why the section dividers
   (Job F, ".ct-product-divider") looked "too short" by comparison - the
   dividers were never hardcoded and already correctly filled the real
   436px column width; this row was the one actually wrong, sticking out
   past them rather than the dividers falling short.
   Fix: width:100%, so this row always matches whatever the column's
   ACTUAL width is (shrinking to fit; needs no future edit if the column
   width changes again), with the old fixed number kept only as a
   max-width - so on a column wide enough to fit all 5 90px items with
   room to spare, this still stops at their natural size instead of
   stretching into empty space. The scroll/arrow/clone-loop JS
   (functions.php) already measures each item's width from the real
   rendered DOM rather than assuming this fixed number, so fewer items
   fitting on screen at once doesn't change how scrolling behaves - the
   rest stay reachable exactly as before.
   Same technique mobile/tablet already uses for the identical problem
   at their own widths (search "Job 3 (Task 1)" in the MOBILE LAYOUT
   section below) - not merged into that shared rule since it's scoped
   to the opposite media query.
   box-sizing:border-box on the viewport specifically (not the wrapper,
   which carries no padding of its own): the viewport's own 4px padding
   is content-box by default (see its base rule above), so width:100%
   would otherwise add that padding ON TOP of 100%, overflowing the
   column by 8px whenever the fluid width actually engages - same fix
   mobile/tablet's own copy of this rule already needs and has, for the
   same reason. */
@media (min-width: 1000px) {
    .ls-swatch-carousel-viewport {
        width: 100%;
        /* +2px: intentional slack (1px per edge) so each swatch's 1px
           selection-ring box-shadow isn't clipped by the scrollport edge
           even when the real tile content sits pixel-flush against it -
           see the matching "- 1" offsets in functions.php. Self-limited
           by width:100% above, same as the base formula already was - on
           a column too tight to spare the full 2px, this simply claims
           whatever's actually available instead of overflowing it. */
        max-width: calc(((90px * 4) + (12px * 3)) + 5px);
        box-sizing: border-box;
    }

    .ls-swatch-carousel-wrapper {
        width: 100%;
        max-width: calc(((90px * 4) + (12px * 3)) + 5px);
    }
}

/* Arrow buttons: circular, hidden until hover */
.ls-carousel-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 32px;
    height: 32px;
    border-radius: 50%;
    border: none;
    background: rgba(255, 255, 255, 0.9);
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
    padding: 0;
    z-index: 2;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.15s ease;
}

.ls-carousel-arrow-left {
    left: -4px;
}

.ls-carousel-arrow-right {
    right: -4px;
}

/* Only reveal arrows on devices with true hover AND a precise pointer -
   i.e. desktop with a mouse. Touch devices never match this, so they
   stay swipe-only and never see the buttons at all. */
@media (hover: hover) and (pointer: fine) {
    .ls-swatch-carousel-wrapper:hover .ls-carousel-arrow {
        opacity: 1;
        visibility: visible;
    }
}

@media (max-width: 999px) {
    .woocommerce-product-gallery {
        margin-bottom: 10px !important;
    }
}

/* --- Section labels: "Choose Your Color" / "Our Packaging" ---
   Reuses the exact same small-caps/muted-gray typography already used for
   the KNIT/SIZE attribute labels inside the purchase-info card (see
   ".entry-summary-items table.variations th.label label" further below) so
   these two headings read as the same visual "label" language, just outside
   the card, above the Color carousel and the Box gallery respectively. */
.ls-section-label {
    display: block;
    margin: 20px 0 8px;
    /* EDITED 8/27/2026 - 18px to 16px, matched to the Knit/Size pill text
       size for consistent typography across the card. */
    font-size: 16px;
    font-weight: 500;
    font-family: 'Playfair Display', serif;
    letter-spacing: normal;
    text-transform: none;
    color: #686868;
}

/* --- Even out the color-section's bottom divider (desktop, >=1000px) ---
   The ".ct-product-divider" that sits between the color tiles and the
   "Our Packaging" heading had 10px above it (the tile carousel wrapper's
   own margin-bottom, left untouched) but 20px below it - that 20px is the
   "Our Packaging" heading's own margin-top (base ".ls-section-label" rule
   above, "margin: 20px 0 8px") winning the margin-collapse over the
   divider's own 10px margin-bottom. That 10-vs-20 lopsidedness read as the
   divider being "squished" up against the tiles. Dropping the heading's
   top margin to 10px here makes the divider 10px/10px - evenly balanced,
   and consistent with the first (description->Color) divider's own ~8-10px
   rhythm.
   Scoped two ways so it can't affect the finalized mobile layout: the
   "min-width: 1000px" query, AND the "+ .ls-section-label" adjacency,
   which only matches a heading directly preceded by a divider - a pairing
   that only exists at >=1000px (below that, "Our Packaging" follows the
   box gallery, with no divider between them). The tile wrapper's own
   spacing is not touched. */
@media (min-width: 1000px) {
    .ct-product-divider + .ls-section-label {
        margin-top: 10px;
    }
}

/* ============================================
   TWO-COLUMN LAYOUT
   ============================================
   Controls how the gallery column and the purchase-info card column relate
   to each other as a pair - their relative widths, and (eventually) their
   relative heights. Anything about the CARD'S OWN internal styling lives in
   PURCHASE-INFO CARD STYLING below instead - this section is only about the
   two columns as a unit. */

/* --- Sticky main image (desktop, >=1000px only) ---
   ".product-entry-wrapper" is a plain flex row (Blocksy's own layout, see
   the "checked the computed display" note in functions.php's "Mobile
   layout reorder" doc comment) with its default, un-overridden
   align-items - which computes to "stretch". That single fact is what
   makes this work with zero extra markup or JS: ".ls-gallery-and-color-
   wrapper" (the left/image column) and ".summary.entry-summary-items"
   (the right/info column) are both direct children of that row, so the
   shorter one is automatically stretched to exactly the taller one's
   height - confirmed live (2026-08-17, product 95 at 1280px): both
   columns measured 1445.2px tall, to the decimal, with no manual height
   rule anywhere forcing that. Since Job F moved everything except the
   main image itself out of the left column, ".ls-gallery-and-color-
   wrapper" is now effectively "the info column's own height, holding
   just one short image" - precisely the container a sticky child needs:
   position:sticky below constrains itself to its containing block (this
   wrapper), so the image sticks while scrolling and is released by the
   browser's own native sticky algorithm the instant it reaches this
   wrapper's bottom edge - which, thanks to the stretch above, always
   lands exactly at the info column's own bottom edge, never shorter
   (cutting the image off early) or taller (letting it overlap the
   footer). No JS height-matching needed; if the info column's content
   ever changes length, the stretch recalculates automatically and the
   release point moves with it.
   top:0, not some larger offset: the site header (".ct-header") is
   position:relative, not fixed/sticky, so it scrolls away normally -
   there's nothing else fixed at the top of the viewport for the image to
   clear, so it sticks flush to the top the moment its normal position
   would otherwise scroll above the fold.
   Sizing is untouched - width/height come entirely from the 50% Customizer
   setting already in place (per request), this only adds position/top. */
@media (min-width: 1000px) {
    .ls-gallery-and-color-wrapper .woocommerce-product-gallery {
        position: sticky;
        /* 20px breathing room above the pinned image (2026-08-25) - was
           top:0, which pins the image flush against the viewport's top
           edge with zero gap once scrolled. top:20px is the standard
           position:sticky way to add space specifically WHILE pinned
           (before the sticky threshold, the image sits at its normal
           in-flow position, untouched by this); it also delays the
           release point (where the image reaches this wrapper's bottom
           edge and stops following scroll) by the same ~20px of extra
           scroll distance, confirmed live via getBoundingClientRect()
           scans - starting value per request, adjust if 20px reads
           wrong in practice. */
        top: 20px;
    }
}

/* ============================================
   PURCHASE-INFO CARD LAYOUT
   ============================================
   Root cause of the narrow squished column:
   Blocksy's desktop 2-column layout (gallery-common.scss) expects
   .woocommerce-product-gallery to be a DIRECT flex child of
   .product-entry-wrapper, so its "width: 50%" resolves against that row's
   definite width. The carousel-repositioning JS above wraps the gallery one
   level deeper inside .ls-gallery-and-color-wrapper, which is now the real
   flex item in the row - but that wrapper has no width of its own, so it's
   sized by its content instead. Percentages resolve to nothing against an
   indefinite (content-sized) parent, so the gallery's "50%" collapses to
   "auto", and the flex item falls back to its automatic minimum size - for
   an unconstrained image, that's roughly the image's full intrinsic width.
   The wrapper ends up hogging the row and .summary (which never moved, so
   its own width rule is still technically correct) gets crushed down to
   whatever's left: a ~130px vertical strip.
   Fix: give the wrapper the column width Blocksy used to put on the gallery
   directly, and let the gallery fill 100% of that wrapper instead of the
   50%-of-its-old-parent rule that no longer applies now that it's nested. */
@media (min-width: 1000px) {
    .product-entry-wrapper {
        /* Blocksy's own gallery/summary split variable - reused here rather
           than reinventing it, so both columns stay driven by one number.
           Biased to ~58/42 (image/info) per the approved mockup, instead
           of Blocksy's 50/50 default. */
        --product-gallery-width: 50%;
    }

    .ls-gallery-and-color-wrapper {
        width: var(--product-gallery-width, 50%);
        /* Without this, flexbox's automatic minimum-size floor (driven by
           the unconstrained gallery image's intrinsic width) would still
           refuse to let the wrapper shrink to the width above. */
        min-width: 0;
    }

    .ls-gallery-and-color-wrapper .woocommerce-product-gallery {
        /* Blocksy's own rule ("width: var(--product-gallery-width)") still
           matches this element too, but now resolves against the wrapper
           above (already correctly sized) instead of the row, so it needs
           to fill 100% of ITS parent, not 58% of it. !important is needed
           to beat that rule's higher selector specificity
           (".product-entry-wrapper:has(...) .woocommerce-product-gallery"). */
        width: 100% !important;
    }
}

/* --- Desktop only (1000px+): column height balancing ---
   RESOLVED (2026-08-05): the gap between the gallery column and the info
   card was traced to the Knit/Size reference photos rendering far larger
   than needed (was ~933.89px gallery-content height vs. 1144.52px card
   height, entirely explained by two oversized photo elements). Fixed by
   capping those photos to match the Color carousel's 80x80 swatch size -
   see the "Match Knit/Size reference photos to carousel size" rule at the
   end of the KNIT / SIZE SELECTOR + REFERENCE PHOTOS section below, not
   here, since it's really a Knit/Size-feature fix, not a two-column-layout
   rule. This note stays as a pointer so anyone reading top-to-bottom
   understands why nothing else changed in this section. --- */

/* ============================================
   PURCHASE-INFO CARD STYLING
   ============================================
   Turns .summary.entry-summary-items (WooCommerce's title/price/variations/
   add-to-cart column) into the light card shown in the approved mockup.
   Deliberately content-agnostic throughout: selectors like ":not(:last-child)"
   style however many fields/rows happen to exist, so adding fields later
   doesn't require touching this CSS. No fixed heights anywhere - the card's
   height is left to natural flow so it can grow with future content.

   --- Hide native attribute controls the card replaces ---
   Color's real swatch/select is replaced visually by the Color Carousel
   above; Material has no control at all (it's fully derived from Color).
   Both rows' label + selected-value text stay visible/live, styled the
   same as every other row below, just never clickable. */

/* Hides just the original swatch/select controls in the Color row (still
   functional, just invisible - the carousel copy is what's clickable). The
   row's label + selected-value text are left alone so they can render as
   the "Color Name" field in the purchase-info card (see PURCHASE-INFO CARD
   STYLING below). */
.ls-hide-original-color-value {
    display: none !important;
}

/* Hides the Material row's ENTIRE <tr> (still functional under the hood -
   Material is fully derived from Color, see functions.php). Unlike Color
   above (only its swatch/select controls are hidden, keeping the row's
   label+value visible as "Color Name"), Material no longer appears as a
   field in the card at all - it now only feeds the short-description
   sentence near the title (.ls-short-description below). */
.ls-hide-material-row {
    display: none !important;
}

.summary.entry-summary-items {
    padding: 28px 32px;
    box-sizing: border-box;
}

/* --- Short description sentence (replaces the old standalone Material
   field) ---
   Printed right after the title (functions.php,
   woocommerce_single_product_summary at priority 6) and kept in sync with
   the selected variation's Material via the found_variation handler in the
   same file (search "ls-short-description"). Plain body copy, not another
   small-caps label - .ls-short-description-material is just a plain inline
   span so swapping its text doesn't need any special styling of its own. */
.ls-short-description {
    font-size: 13px;
    color: #4a4a45;
    padding: 8px;
}

@media (max-width: 999px) {

    /* Below the desktop breakpoint .product-entry-wrapper stacks the
       gallery and summary vertically (Blocksy's own default behavior) - the
       card already fills that full-width slot naturally, it just needs
       slightly tighter padding and a gap from the gallery above it. */
    .summary.entry-summary-items {
        padding: 20px;
        margin-top: 24px;
        /* Job 7 (Item 2): drop the top edge of the card's own border (set as
           part of the "border: 1px solid #e3e0da" shorthand, PURCHASE-INFO
           CARD STYLING above) - it reads as an unnecessary divider line
           sitting right above the Size row, now that Size is the first
           visible thing in the card below 1000px (Color's own row is
           hidden here - see "Mobile layout reorder" in functions.php). The
           left/right/bottom edges of the card (and the border on every
           other breakpoint) are untouched - only this top edge, only here. */
        border-top: none;
    }

    /* Job 10: fixes the Size row's bottom margin (the gap down into Knit
       below it), traced to Blocksy's own bundled woocommerce.min.css - see
       the long root-cause comment on the base ".variations
       tr:not(:last-child)" rule above (PURCHASE-INFO CARD STYLING) for the
       full trace. Short version: that Blocksy rule's higher-specificity
       selector was silently overriding this file's own margin-bottom on
       every row it targets, at every width - on mobile/tablet specifically,
       the only row this is ever visually noticeable on is Size (the
       "Mobile layout reorder" script in functions.php moves Knit after
       Size here, making KNIT the DOM's actual last row below 1000px, so
       Size is the one that still needs a bottom margin - unlike desktop,
       where Size stays last and Knit is the one that needs it, already
       correctly overridden via its own "!important" further below).
       10px (not this file's usual 16px) specifically to match divider_1's
       own 10px margin-bottom above "Size: Regular", so the gap above and
       below it reads as symmetric - scoped to just the Size row, not the
       shared base rule, since every other row's spacing is correct as-is
       and out of scope for this fix. */
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_size"]) {
        margin-bottom: 10px !important;
    }

    /* Size row's description-to-divider gap: the shared base rule
       (PURCHASE-INFO CARD STYLING, ".variations tr:not(:last-child)")
       gives every row a 16px padding-bottom as part of its divider - on
       mobile/tablet, that puts 16px between Size's description text and
       the line below it. Scoped down to 5px here to match the target gap
       the user wants under both descriptions - same scoping/reasoning as
       the margin-bottom fix just above (Size only, since Knit gets its
       own separate fix below rather than this shared rule). */
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_size"]) {
        padding-bottom: 5px !important;
    }

    /* Knit row's description-to-Price gap: Knit is the actual last DOM
       row on mobile/tablet (see "Mobile layout reorder" in functions.php,
       referenced in the Job 10 comment above) so it skips the
       tr:not(:last-child) rule above entirely - its own bottom spacing
       instead comes from the whole table's own margin-bottom. That rule
       lives further below in this file (search "Zero out default") and
       is ALSO !important, unscoped (reaches every width) - so this fix
       is placed immediately after it, not here, since two equally-
       specific !important rules resolve by source order and this one
       needs to win. See that later location for the actual override. */
}

/* --- Price: add a small-caps "Price" label to match the mockup ---
   There's no separate label element for price today, so this uses generated
   content rather than new markup. Purely decorative: the price text itself
   ("$115.00") is still read by screen readers as normal, so nothing depends
   on the generated label for accessibility.
   Selector is a descendant (not ">") match: WooCommerce's own native
   ".single_variation_wrap .woocommerce-variation-price" (live, per-variation
   price - see functions.php) already renders inside the add-to-cart form,
   after the attribute rows, without any repositioning needed. */
.entry-summary-items .price {
    display: flex !important;
    flex-direction: column;
    gap: 20px;
    /* Divider below price, separating it from quantity/add-to-cart. */
    padding-bottom: 20px;
    margin-bottom: 20px;
    border-bottom: 1px solid #e3e0da;
}

.entry-summary-items .price::before {
    content: "Price";
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #767268;
    /* EDITED - Playfair Display so the "Price:" label matches the card's
       serif type language (title, Size/Knit labels, color name, tile prices)
       instead of the theme's default system font. Unscoped = both breakpoints. */
    font-family: 'Playfair Display', serif;
}

.entry-summary-items .price .woocommerce-Price-amount {
    font-size: 22px;
    font-weight: 700;
    color: #1a1a1a;
    /* EDITED - Playfair Display for the main $115.00 amount, matching the rest
       of the card's serif type. Unscoped = both breakpoints. */
    font-family: 'Playfair Display', serif;
}

/* --- Variation attribute rows (Color, Knit, Length, and any added later) ---
   Woo Variation Swatches already renders each row as
   "display:flex; flex-direction:column" (label above value) once its
   "show label" option is on, which it already is site-wide - no table/flex
   restructuring needed here, just typography and spacing. Applies to Color
   too now that only its swatch cell is hidden (.ls-hide-original-color-value
   above), not the whole row. */
.entry-summary-items table.variations th.label label {
    /* EDITED 8/27/2026 - 18px to 16px, matched to the Knit/Size pill text
       size for consistent typography across the card. */
    font-size: 16px;
    font-weight: 500;
    font-family: 'Playfair Display', serif;
    letter-spacing: normal;
    text-transform: none;
    color: #686868;
}

.entry-summary-items table.variations th.label .woo-selected-variation-item-name,
.entry-summary-items table.variations td .woo-selected-variation-item-name {
    font-size: 16px;
    font-weight: 700;
    color: #1a1a1a;
}

/* REMOVED (2026-08-25): ".ls-optional-label" typography, scoped to
   th.label - see the base ".ls-optional-label" rule's own removal note
   (KNIT / SIZE SELECTOR + REFERENCE PHOTOS section) for why: the class is
   unconditionally hidden elsewhere, so this narrower duplicate never
   rendered either. */

/* Divider between attribute rows - counts whatever rows are actually
   visible, so it keeps working whether there's 1 attribute or 5.

   Job 10 root-cause note: this rule's own margin-bottom (16px here) does
   NOT win everywhere it applies - Blocksy's bundled woocommerce.min.css
   ships its own "form.variations_form table.variations tr:not(:last-child)
   { margin-bottom: 1.5em }" (1.5em = 24px at the default 16px root
   font-size). That selector's specificity (3 classes/attrs, 3 elements -
   form, table, tr) is HIGHER than this one's (3 classes, only 2 elements -
   table, tr; ".entry-summary-items"/".variations" are classes, not
   elements), so Blocksy's 24px silently wins the cascade here, with no
   !important on either side - this rule's 16px never actually applies to
   any row it targets, at any width. Never noticed before because the only
   place it was ever visually checked against something else (the gap above
   "Size: Regular", from divider_1's own 10px margin) is a Job 10 fix - see
   the Size-specific override in the below-1000px block above (search "Job
   10") for the actual applied fix; this base rule is left as-is since
   fixing it generally is out of that job's scope. Desktop's own version of
   this rule (min-width:1000px block below) already carries "!important",
   which is exactly why IT correctly wins over Blocksy's 24px already. */
.entry-summary-items table.variations tr:not(:last-child) {
    padding-bottom: 16px;
    margin-bottom: 16px;
    border-bottom: 1px solid #e3e0da;
}

/* Knit row's ACTUAL description-to-divider gap (mobile/tablet only) - the
   space BETWEEN Knit's description text and the divider line itself,
   i.e. the table's own padding-bottom (set to 20px in the base rule just
   below - no !important there, so this override doesn't need the
   source-order gymnastics the margin-bottom fix further below required). This is the
   correct property for this gap: margin-bottom (further below) instead
   controls the DIFFERENT gap after the line, down to Price - the two are
   easy to mix up since they sit on opposite sides of the same divider.
   Matches Size's own text-to-divider gap (its own row's padding-bottom,
   PURCHASE-INFO CARD STYLING above) at the same 5px target. */
@media (max-width: 999px) {
    .entry-summary-items table.variations {
        padding-bottom: 5px !important;
    }
}

.entry-summary-items table.variations {
    /* Divider after the last attribute row, separating attributes from the quantity/add-to-cart section below. */
    margin-bottom: 20px;
    padding-bottom: 20px;
    border-bottom: 1px solid #e3e0da;
}

/* --- "No products matched" message ---
   Relocated here (right after the variations table, before Price) by the
   MutationObserver in functions.php, which is also what adds this class -
   WooCommerce's own .woocommerce-info notice styling still applies, this
   just adds breathing room now that Price sits directly below it instead
   of the Add to Cart button. */
.entry-summary-items .ls-no-match-message {
    margin-bottom: 20px;
}

/* --- Quantity: small-caps "Quantity" label above Blocksy's own +/- stepper
   (.quantity[data-type="type-2"], already fully functional - no new stepper
   markup needed here, just a label and some vertical rhythm).
   Both rules are scoped to ".entry-summary-items" - .ct-cart-actions is
   also used elsewhere (mini-cart, cart page), and an unscoped rule here
   would have changed quantity steppers site-wide, not just this card. */
.entry-summary-items .ct-cart-actions {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

/* The label lives on .ct-cart-actions itself, NOT on .quantity: .quantity
   has a hardcoded height (55px, set by Blocksy) that doesn't grow to fit
   extra content, and its +/- buttons are positioned absolutely within that
   fixed box. Putting the label inside .quantity pushed the number input
   down without the box growing to match, leaving the +/- buttons - still
   centered on the original, un-pushed-down box - overlapping the shifted
   input. Placing the label before .ct-cart-actions instead (still directly
   above the stepper, since .quantity is its first child) achieves the same
   look without touching .quantity's internal box at all. */
.entry-summary-items .ct-cart-actions::before {
    content: "Quantity";
    display: block;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: #767268;
    /* EDITED - Playfair Display so the "Quantity" label matches the "Price:"
       label and the rest of the card's serif type. Unscoped = both breakpoints. */
    font-family: 'Playfair Display', serif;
}

/* --- Add to Cart: full-width button at the bottom of the card --- */
.entry-summary-items .single_add_to_cart_button {
    width: 100%;
    justify-content: center;
}

/* --- Desktop only (1000px+): tightened spacing to close the remaining
   column-height gap (was 187.66px after the reference-photo fix - see
   chat) --- */
@media (min-width: 1000px) {
    .summary.entry-summary-items {
        padding: 10px 32px;
    }

    .entry-summary-items table.variations tr:not(:last-child) {
        padding-bottom: 4px;
        margin-bottom: 10px !important;
    }

    /* Job G: removes the "Divider after the last attribute row" line (base
       rule, PURCHASE-INFO CARD STYLING above, "border-bottom: 1px solid
       #e3e0da") that used to separate Size/Knit from Price - confirmed via
       live DOM inspection this is a plain border on the table itself, NOT
       the ".ct-product-divider" component used elsewhere on this card (see
       functions.php "Job F" for that one). padding/margin-bottom below are
       intentionally left exactly as they already were: with the 1px line
       gone, that's still real, non-collapsed space between the table and
       Price - removing the line alone doesn't leave a gap or compress the
       layout, so no other value needed adjusting. Scoped to >=1000px only,
       matching where the mockup this fixes applies - mobile/tablet keep
       today's divider untouched. */
    .entry-summary-items table.variations {
        margin-bottom: 6px;
        padding-bottom: 6px;
        border-bottom: none;
    }

    /* Job G: removes the second divider - ".price"'s own "border-bottom:
       1px solid #e3e0da" (base rule above), which used to separate Price
       from Quantity/Add to Cart/Buy Now. Same reasoning as the table rule
       just above: only the line is removed, padding/margin-bottom are left
       as-is since they already provide real spacing on their own. Together
       with the table rule above, Price/Quantity/Add to Cart/Buy Now now
       read as one unbroken section, matching the approved mockup.
       Job J: flex-direction/align-items/gap added - desktop was still
       using the base rule's "flex-direction: column" (PURCHASE-INFO CARD
       STYLING above), stacking "PRICE" above "$115.00" on two lines.
       Mobile/tablet already solved this exact request (search "PRICE +
       QUANTITY + BUTTONS: ONE COMBINED SECTION (Job 3, Task 4)") - this
       is the same row/baseline/gap treatment, just scoped to >=1000px
       instead, so both breakpoints now read "Price: $115.00" on one
       line. flex-wrap:wrap kept (matches mobile) as a plain safety net
       if a future currency/price format is ever too wide for one line;
       harmless at today's normal widths. */
    .entry-summary-items .price {
        flex-direction: row;
        flex-wrap: wrap;
        align-items: baseline;
        gap: 5px;
        padding-bottom: 6px;
        margin-bottom: 6px;
        border-bottom: none;
    }

    /* Job J: "Price" -> "Price:" - same inline "Label: Value" punctuation
       already used for Color/Material/Size/Knit at this breakpoint (search
       "esc_js( __( 'Material:'" and similar), and the same change mobile/
       tablet's own copy of this rule already makes (search "Price:" in
       the MOBILE LAYOUT section below). Font-size/weight/color are
       untouched - inherited from the shared, unscoped ".price::before"
       rule above (PURCHASE-INFO CARD STYLING). */
    .entry-summary-items .price::before {
        content: "Price:";
    }

    .entry-summary-items .ct-cart-actions {
        gap: 6px;
    }
}

/* Zero out default browser/theme spacing that our earlier rule never
   reached: our tr:not(:last-child) rule intentionally skips the LAST row
   (Size) since it doesn't need a divider after it - but that also meant
   it kept its default 1.5em margin-bottom untouched until now. Table's
   own default bottom margin zeroed too, in case a more specific default
   rule was beating our existing margin-bottom setting on it. */
.entry-summary-items table.variations {
    margin-bottom: 10px !important;
}

/* Knit row's description-to-Price gap (mobile/tablet only): the rule
   just above sets the table's own margin-bottom to 10px, unscoped, so it
   also reaches desktop - on mobile/tablet specifically, Knit is the
   table's actual last row (see "Mobile layout reorder" in functions.php),
   so this table-level margin is the ONLY thing creating Knit's gap down
   to Price (Knit's own <tr> contributes 0px, see the tr:last-child rule
   just below). Overriding it here brings that gap to the same 5px target
   as Size's fix above (search "Job 10"). Placed directly after the rule
   it needs to beat - both are !important with equal selector specificity,
   so source order decides the winner; a copy of this same fix placed
   earlier in the file (where Size's fix lives) was silently losing this
   exact tie-break, which is why it moved here instead. Desktop's own
   separate 1000px+ override (PURCHASE-INFO CARD STYLING further up) is
   unaffected - this is scoped to mobile/tablet only. */
@media (max-width: 999px) {
    .entry-summary-items table.variations {
        margin-bottom: 5px !important;
    }
}

.entry-summary-items table.variations tr:last-child {
    margin-bottom: 0px !important;
}

@media (max-width: 999px) {
    .entry-summary-items table.variations tr:last-child {
        margin-bottom: 6px !important;
    }
}

/* ============================================
   RIGHT COLUMN RESTRUCTURE (desktop, >=1000px) - Job F
   ============================================
   functions.php ("Job F") inserts a new divider <tr> directly after the
   Size row and after the Knit row - which means neither row is ever the
   table's actual :last-child at this width anymore, so the "Divider
   between attribute rows" rule above (".variations tr:not(:last-child)")
   would otherwise ALSO draw its own border-bottom on both of them (a
   different color from ".ct-product-divider" - #e3e0da vs. this card's
   own divider color) - doubling the line under Size (which already had
   that border today) and putting an unwanted extra one under Knit (which
   doesn't have it today, only becomes "not last child" once Job F's row
   is inserted after it). The new ".ls-divider-row" itself is a plain
   <tr> too, so the SAME base rule would also draw a border-bottom on IT
   whenever it isn't the table's last row (confirmed live: the Size
   divider row sits before Knit, so it isn't last) - stacking a second
   line directly under its own ".ct-product-divider" child. All three are
   suppressed here so only Job F's own ".ct-product-divider" elements
   draw a line - padding/margin from that same base rule are left exactly
   as they already were, since they still give Job F's divider rows the
   correct amount of space above them. Scoped to >=1000px only, matching
   where Job F's JS actually runs - below 1000px, Size/Knit keep today's
   existing divider behavior untouched. */
@media (min-width: 1000px) {
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_knit"]),
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_size"]),
    .entry-summary-items table.variations tr.ls-divider-row {
        border-bottom: none !important;
    }
}

/* Job F's Size/Knit divider rows live inside table.variations, so they
   don't get the ~10px bottom spacing every other direct child of
   ".summary.entry-summary-items" already gets for free (Blocksy's own
   entry-summary.scss, "> * { margin-bottom: var(--product-element-
   spacing, 10px) }") - this sets the same 10px explicitly, just for
   these two, so all 5 dividers read as evenly spaced. Unscoped (no media
   query) is safe here: ".ls-divider-row" only ever exists in the DOM at
   >=1000px, since Job F's JS is matchMedia-gated to that width and
   removes these rows entirely below it. */
.ls-divider-row .ct-product-divider {
    margin-bottom: 10px;
}

/* ============================================
   SIZE / KNIT / PRICE SECTION: GAP CONSISTENCY (desktop, >=1000px)
   ============================================
   Three related spacing fixes so the run from Size down to Quantity reads
   as one visually consistent rhythm. All desktop-scoped; none of these
   selectors can touch mobile - the ".ls-divider-row" elements only exist
   at >=1000px (Job F's JS is matchMedia-gated), and the .price override
   below is inside the min-width query with mobile keeping its own separate
   .price rule. */
@media (min-width: 1000px) {

    /* GAP #3 - space ABOVE the Knit row, matched to the space above Size.
       The divider row AFTER Size gets padding-bottom:4 + margin-bottom:10
       from the shared "table.variations tr:not(:last-child)" desktop rule,
       stacked ON TOP of its own divider's 10px margin-bottom - 24px below
       the line. The divider above the SIZE row is a plain
       ".ct-product-divider" (not a divider ROW), so it only has ~10px below
       it - making Knit's gap (37px to its label) 14px larger than Size's
       identical gap (23px). Zeroing this row's own padding/margin-bottom
       drops it back to just the divider's 10px, so both attribute rows now
       have a matching ~23px gap above them. ":not(:last-child)" targets
       only the after-Size divider row; the after-Knit divider row is the
       table's last row (handled just below). The full
       ".entry-summary-items table.variations tr.ls-divider-row" prefix is
       required (not just ".ls-divider-row"): the shared
       ".entry-summary-items table.variations tr:not(:last-child)" rule ALSO
       carries "margin-bottom: 10px !important", and both being !important,
       the winner is decided by specificity - so this selector has to be at
       least as specific as that one (it adds the ".ls-divider-row" class on
       top, so it wins). padding-bottom needs no such bump (the rule it
       beats there is not !important). */
    .entry-summary-items table.variations tr.ls-divider-row:not(:last-child) {
        padding-bottom: 0 !important;
        margin-bottom: 0 !important;
    }

    /* GAP #5 - space ABOVE Price, 20px -> 14px. That 20px is the after-Knit
       divider's own 10px margin-bottom + the variations table's 10px
       margin-bottom. Trimming this last divider's margin-bottom to 4px
       yields 14px. Scoped to ".ls-divider-row:last-child" (the after-Knit
       row) so it doesn't disturb the after-Size divider handled above. */
    .ls-divider-row:last-child .ct-product-divider {
        margin-bottom: 4px;
    }

    /* PRICE -> QUANTITY - 20px -> 8px, the tightest gap in the section per
       request. The live 20px comes from Blocksy's own
       "form.variations_form .single_variation :is(p, .price)" rule
       (margin-bottom: 20px), which outranks this theme's plain desktop
       .price rule by specificity - so !important is required to win. */
    .entry-summary-items .price {
        margin-bottom: 8px !important;
    }

    /* GAPS #2 & #4 - space BELOW the Size and Knit descriptions, 28px -> 10px,
       to match the section-ending gaps below the color tiles and the box
       gallery (both a clean 10px). That 28px was: the ".ls-photos-and-
       description" wrapper's own 14px margin-bottom + the attribute row's
       4px padding-bottom + the attribute row's 10px margin-bottom. Zeroing
       the first two leaves just the row's existing 10px margin as the gap -
       the same kind of 10px margin that already produces the tiles/box
       reference gaps. Scoped to the Size and Knit rows specifically (via the
       same :has() attribute-name technique used elsewhere) and to desktop;
       mobile keeps its own separate Size/Knit spacing untouched. The row's
       10px margin-bottom (from "tr:not(:last-child)") is intentionally left
       as-is - it IS the target gap. */
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_size"]),
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_knit"]) {
        padding-bottom: 0 !important;
    }

    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_size"]) .ls-photos-and-description,
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_knit"]) .ls-photos-and-description {
        margin-bottom: 0 !important;
    }
}

/* ============================================
   KNIT / SIZE SELECTOR + REFERENCE PHOTOS
   ============================================
   Restyles the Knit and Length ("Size") swatch selectors - still the same
   Woo Variation Swatches "button" markup, wired to the real hidden <select>,
   see functions.php - into a single on/off toggle for the non-default
   option, and lays it out next to its static reference photo(s) (also
   added via functions.php, appended as a plain sibling in the same table
   cell). Scoped to Knit/Length only via
   [data-attribute_name="attribute_pa_knit"/"attribute_pa_size"], the same
   technique already used above for the Color carousel - Color's own swatch
   styling is untouched. */

/* --- Current-value row (KNIT / Conventional, SIZE / Regular) ---
   Forces <th class="label"> to stack its two children (the attribute
   name <label> and the plugin's own live-updating
   ".woo-selected-variation-item-name" span) vertically, label above value -
   matching the existing Color Name / Off-White row exactly, since both use
   the same th.label/.woo-selected-variation-item-name styling further
   below (see PURCHASE-INFO CARD STYLING). This row is native to the
   plugin - functions.php only relabels "Length" to "Size" via JS and
   otherwise leaves it alone. */
.entry-summary-items table.variations th.label {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

/* Desktop only (1000px+): collapses Color's label+value onto one inline
   line ("Color Name: Off-White") instead of the stacked small-caps label /
   bold value on two lines the base rules above still produce - request:
   bring desktop in line with the inline treatment mobile/tablet already
   uses (see ".ls-color-name-display" further below in the max-width:999px
   MOBILE LAYOUT section).
   Rescoped to Color only (2026-08-25, via ":has()", same technique already
   used for Knit/Size elsewhere in this file) - Knit/Size used to be
   included here too ("Knit: Conventional", "Size: Regular"), but the
   approved wireframe now shows Knit/Size as label + pills with no separate
   current-value text at all, handled entirely by "KNIT / SIZE ROW: GRID
   LAYOUT" further below instead. Material's row is unaffected either way,
   since its whole <tr> is already hidden (.ls-hide-material-row above).
   No explicit gap needed between label and value - the Woo Variation
   Swatches plugin's own base CSS already adds "margin: 0 2px" to both the
   <label> and ".woo-selected-variation-item-name" at every width, the same
   spacing the mobile inline rows already rely on. */
@media (min-width: 1000px) {
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_color"]) th.label {
        flex-direction: row;
        flex-wrap: wrap;
        align-items: baseline;
        gap: 0px;
    }

    /* Label switches from the small-caps uppercase field-tag style to a
       plain "Label:" phrase - matching the mobile inline rows' typography
       (15px, sentence case) instead of the stacked style further above. */
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_color"]) th.label label {
        font-size: 15px;
        font-weight: 600;
        letter-spacing: normal;
        text-transform: none;
        color: #4a4a45;
    }

    /* Adds the ":" - the label's own text content is just "Color Name",
       same as before. */
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_color"]) th.label label::after {
        content: ":";
    }

    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_color"]) th.label .woo-selected-variation-item-name {
        font-size: 15px;
        font-weight: 700;
        color: #1a1a1a;
    }
}

/* --- Shared row layout: selector column next to photos column ---
   <td class="value"> now contains, as plain siblings: the real (hidden)
   <select>, the swatch <ul>, and the reference-photos <div> - laying the
   cell itself out as a flex row puts the toggle next to the photos without
   an extra wrapper element (a wrapper here would break the plugin's own
   $(ul).parent().prev() lookup used to populate the current-value row
   above - see the long comment in functions.php). Safe to apply to every
   attribute row's <td class="value">, including Color's: Color's is fully
   hidden (.ls-hide-original-color-value, !important) regardless of its
   own display value. */
.entry-summary-items table.variations td.value {
    display: flex;
    align-items: flex-start;
    /* Wrap is needed so the "Clear" link (flex-basis: 100% below) can drop
       to its own line under the toggle+photos. Flexbox decides whether to
       wrap a line using each item's un-shrunk flex-basis, not its shrunk
       size - so the toggle's and photos' combined flex-basis has to stay
       comfortably under the card's real rendered width on its own (an
       earlier version used 220px + 260px, which was already wider than the
       ~450px rendered width and wrapped every time instead of sharing a
       line, even though both items had plenty of room to shrink into). */
    flex-wrap: wrap;
    gap: 5px !important;
}

.entry-summary-items table.variations td.value ul[data-attribute_name] {
    /* Selector column: a modest flex-basis (it only ever holds one compact
       toggle switch now) so the photos column reliably gets the rest of
       the row's width. */
    flex: 1 1 200px;
    min-width: 130px;
}

/* .ls-photos-and-description wraps .ls-reference-photos + the moved
   .ls-option-description together (see functions.php) - it's the actual
   flex item td.value lays out here, not .ls-reference-photos directly
   anymore, so the photos and the description below them share this one
   flex-basis/min-width and stack together as a single unit. */
.entry-summary-items table.variations td.value .ls-photos-and-description {
    /* adjust this to your target thumbnail size */
    min-width: 160px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.ls-reference-photos {
    display: flex;
    gap: 12px;
}

/* Job 7 (Item 3): static "Size Comparison"/"Knit Comparison" heading
   between the pills and the reference photo(s) - see functions.php,
   ls_render_static_option_label(). Hidden here by default; switched on and
   positioned by "KNIT / SIZE ROW: GRID LAYOUT" further down, which is
   unscoped (2026-08-25) to apply at every breakpoint - desktop no longer
   uses a separate control that has no room for it. */
.ls-static-option-label {
    display: none;
}

/* The "Clear Options" link always renders as its own full-width bar,
   instead of becoming a cramped extra flex item squeezed in next to the
   toggle+photos (desktop/tablet, 1000px+) or looking like a plain unstyled
   link (below 1000px).
   Selector deliberately does NOT require "table.variations td.value" as an
   ancestor (it did originally) - the mobile-reorder wp_footer script in
   functions.php (search "Mobile layout reorder") physically moves this
   link, below 1000px, from inside Size's <td class="value"> to a plain
   sibling of <table class="variations"> (so "Clear Options" can render
   after Knit instead of staying stuck inside Size's cell) - the old,
   narrower selector stopped matching at that point since the link is no
   longer a descendant of any td.value there, which is exactly why the
   styling was disappearing below 1000px. Scoping only to ".entry-summary-items"
   (still true at every breakpoint, since the link always stays somewhere
   inside the purchase-info card either way) matches it correctly in BOTH
   of its possible DOM positions, at every width, with one rule instead of
   needing a separate desktop-only and mobile-only copy.
   flex-basis:100% is now only meaningful at 1000px+ (td.value is a flex
   row there); below 1000px the link's new parent is a plain block <div>,
   where flex-basis has no effect and is harmlessly ignored - display:block
   alone already makes it a full-width bar there, so no separate mobile
   override is needed for sizing. */
.entry-summary-items .reset_variations {
    flex-basis: 100%;
    display: block;
    text-align: center;
    padding: 10px 0;
    margin-top: 0px;
    background-color: #cececa; /* placeholder - swap for your actual brand/link blue */
    color: #408df3 !important;
    text-decoration: none !important;
    border-radius: 4px;
    font-weight: 600;
}

.entry-summary-items .reset_variations:hover {
    opacity: 0.9;
}

/* Desktop only (1000px+): hides the "Clear Options" button - request: remove
   it from the desktop product page. Mobile/tablet already hide this same
   link for their own reason (Knit/Size become pill buttons with no default
   to "clear" back to - see ".entry-summary-items .reset_variations" inside
   the max-width:999px MOBILE LAYOUT section further below); this is a
   separate, desktop-only rule so that block stays untouched. !important
   beats the unconditional "display: block" the base rule above gives this
   link at every width. */
@media (min-width: 1000px) {
    .entry-summary-items .reset_variations {
        display: none !important;
    }
}

/* Below the desktop breakpoint, stack the toggle above the photos -
   matches the rest of the card stacking vertically on mobile (see
   PURCHASE-INFO CARD LAYOUT above, same 999px breakpoint). */
@media (max-width: 999px) {
    .entry-summary-items table.variations td.value .ls-photos-and-description {
        width: 100%;
    }
}

/* --- The two swatch <li>s Woo Variation Swatches renders per attribute ---
   One is flagged "ls-default-option" (its value is already shown in the
   current-value row above, so it's hidden here - not removed, just
   invisible, so it stays clickable via JS when switching back to it) and
   the other "ls-alt-toggle" (the single visible control). Both flags are
   added server-side via the woo_variation_swatches_variable_item_css_class
   filter in functions.php. */
ul[data-attribute_name="attribute_pa_knit"] .variable-item.ls-default-option,
ul[data-attribute_name="attribute_pa_size"] .variable-item.ls-default-option {
    /* !important needed here: the plugin's own frontend.css rule
       (".woo-variation-swatches .variable-items-wrapper .variable-item:not(...)")
       sets display:flex with higher specificity than this selector. */
    display: none !important;
}

ul[data-attribute_name="attribute_pa_knit"],
ul[data-attribute_name="attribute_pa_size"] {
    list-style: none;
    margin: 0;
    padding: 0;
}

ul[data-attribute_name="attribute_pa_knit"] .variable-item.ls-alt-toggle,
ul[data-attribute_name="attribute_pa_size"] .variable-item.ls-alt-toggle {
    margin: 0 !important;
    cursor: pointer;
    border: none !important;
    padding: 0 !important;
    background: none !important;
    box-shadow: none !important;
}

/* .variable-item-contents becomes a flex column of two lines - see
   functions.php: line 1 is "Optional" + the option's heading (e.g.
   "Shaker"), line 2 is "Select" + the switch indicator. The description
   is NOT here - it's moved out to sit with the reference photo(s)
   instead (.ls-photos-and-description below).

   align-items/justify-content need !important: the plugin's own frontend.css
   rule (".woo-variation-swatches .variable-items-wrapper .variable-item
   .variable-item-contents") sets align-items:center + justify-content:center
   at higher specificity (4 classes vs. this rule's 2) - without !important
   here the plugin's centering won even though this rule declares
   flex-direction:column below it, leaving both lines horizontally AND
   vertically centered within the switch column instead of stacked
   left-aligned top-to-bottom (verified directly via the CSSOM: both
   declarations were present, the plugin's just won the cascade). */
ul[data-attribute_name="attribute_pa_knit"] .variable-item-contents,
ul[data-attribute_name="attribute_pa_size"] .variable-item-contents {
    display: flex;
    flex-direction: column;
    align-items: flex-start !important;
    justify-content: flex-start !important;
    gap: 4px;
    height: auto !important;
}

/* WooCommerce's own form.variations_form table.variations tr rule sets
   gap: 10px (flex-column) - only visibly affects Knit/Size since those are
   the only rows whose td.value currently wraps into two flex lines
   (toggle row + photos/description); confirmed via console that Material/
   Color compute the same gap but have nothing to visibly apply it to. */
form.variations_form table.variations tr:has(ul[data-attribute_name="attribute_pa_knit"]),
form.variations_form table.variations tr:has(ul[data-attribute_name="attribute_pa_size"]) {
    gap: 0 !important;
}

/* REMOVED (2026-08-25): ".ls-toggle-line1"/".ls-toggle-line2" flex-row rules
   (display/flex-direction/align-items/gap for the old two-line switch UI).
   Both selectors get "display: contents" from the later, equal-specificity
   ".ls-toggle-line1, .ls-toggle-line2" rule (search "makes their remaining
   visible child" below) at every breakpoint (unscoped 2026-08-25), which
   always wins by appearing later in the file - so these flex rules could
   never actually apply and were dead on arrival for both selectors, not
   just line2. */

/* REMOVED (2026-08-25): ".ls-optional-label"/".ls-choose-label" typography
   (font-size/weight/color/white-space) for the old two-line switch UI's
   "Optional"/"Select" text, plus the narrower duplicate of the same
   ".ls-optional-label" rule that used to live under "Variation attribute
   rows" above (search "Optional" label typography only"). Both classes are
   unconditionally hidden by the shared ".ls-optional-label, .ls-choose-label,
   .ls-toggle-switch { display: none }" rule (search "the switch box: all
   specific to the old two-line switch design" below), which is unscoped
   (2026-08-25) to apply at every breakpoint - so this styling never renders
   anywhere. That shared hide rule itself is left alone, since it also
   covers ".ls-toggle-switch", which still needs to stay hidden. */

ul[data-attribute_name="attribute_pa_knit"] .variable-item-span,
ul[data-attribute_name="attribute_pa_size"] .variable-item-span {
    font-weight: 600;
    font-family: 'Playfair Display', serif;
    line-height: 20px;
    color: #1a1a1a;
}

/* REMOVED (2026-08-25): ".ls-toggle-switch" track/thumb styling (position,
   size, border, background) and its ".variable-item.selected" background-
   color change - the switch indicator for the old two-line switch UI.
   ".ls-toggle-switch" is unconditionally hidden by the same shared
   "display: none" rule as ".ls-optional-label"/".ls-choose-label" (see
   their own removal note above), so none of this treatment ever rendered. */

/* .ls-option-description is added by the wp_footer script in functions.php
   from each option's data-ls-description attribute, itself sourced from
   that attribute term's own Description field (Products > Attributes >
   Knit/Length > Configure terms) - then moved (same script) to sit below
   the reference photo(s), inside .ls-photos-and-description. Stays
   empty/absent - and so invisible - until that field is filled in for a
   given term. */
.ls-option-description {
    font-size: 13px;
    line-height: 1.4;
    font-weight: 400;
    color: #767268;
}

/* ============================================
   RETIRED (2026-08-25): PILL SELECTORS (desktop, >=1000px) - Job B
   ============================================
   Used to give desktop its own copy of the mobile/tablet pill treatment,
   since desktop otherwise still used the old two-line switch UI. Now that
   "KNIT / SIZE ROW: GRID LAYOUT" and "PILL SELECTORS (Job 2 - Knit/Size)"
   further below are unscoped to apply at every breakpoint (2026-08-25),
   that single shared rule set already covers desktop too - this separate
   desktop-only copy would just be a second, redundant override doing the
   same thing, so it's removed rather than left stacked on top. */

/* --- Reference photos ---
   Static images appended next to the selector (functions.php) - they do NOT
   change when a different Knit/Length option is selected, by design. */
.ls-lightbox-trigger {
    display: block;
    flex: 1 1 0;
    min-width: 0;
    /* EDITED - 8px -> 2px to match the color swatches and box thumbs (both
       already 2px), plus a 1px #d8d3c7 outline to match the Knit/Size
       selector pills' border. box-sizing: border-box so the 1px border
       doesn't enlarge this flex item past its sized width at any breakpoint. */
    border-radius: 2px;
    border: 1px solid #d8d3c7;
    box-sizing: border-box;
    overflow: hidden;
    cursor: zoom-in;
}

/* Caps the Size row's single reference photo so it matches the width of
   each individual Knit-row photo, instead of stretching to fill the
   whole container width when there's only one image to share it with.
   .ls-reference-photos is no longer a direct sibling of the <ul> - it's
   nested inside .ls-photos-and-description now (see functions.php) - so
   the sibling combinator targets the wrapper instead. */
ul[data-attribute_name="attribute_pa_size"]~.ls-photos-and-description .ls-lightbox-trigger {
    flex: 0 0 calc(60% - 6px);
}

.ls-reference-photo {
    /* Shared square aspect ratio across all three placeholder photos (all
       1200x1200 today) keeps the full-body and texture-comparison photos
       the same size next to each other, so the section reads as
       intentional rather than mismatched even though they're two visually
       different kinds of photo. */
    display: block;
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    /* EDITED - 8px -> 2px to match the tiles/boxes. The wrapper's
       overflow:hidden clip is what's actually visible, but keep these in sync. */
    border-radius: 2px;
}

/* NOTE: a flat 90px override was tried here first, but measurement showed
   the box gallery itself isn't fixed at 90px on mobile/tablet - it uses a
   fluid percentage formula (divides available width by 4 items on tablet,
   3 on phone, see BOX GALLERY / PHONE SUB-BREAKPOINT sections) that keeps
   shifting size as viewport width changes. A flat number here could only
   ever match at one specific width and drifts everywhere else - which is
   exactly the mismatch that showed up live (box images measured
   115.45px vs. reference photos' fixed 90px). The real matching rules
   live further down, sized with the same fluid formula as the box
   gallery at each tier, instead of a fixed number here. */

/* Match Knit/Size reference photos to the same 80x80 size as the Color
   carousel swatches, instead of stretching to fill their row - this is
   what closes the desktop column-height gap (was ~211px overshoot,
   almost entirely driven by these oversized photos - see chat). Desktop
   only: tablet/mobile keep their own separate 90px rule above, since
   there's no column-height concern once the layout stacks vertically. */
@media (min-width: 1000px) {
    .ls-reference-photo {
        width: 80px;
        height: 100px;
        aspect-ratio: auto; /* fixed dimensions above replace the ratio-driven sizing */
    }

    .entry-summary-items table.variations td.value .ls-lightbox-trigger {
        flex: 0 0 80px;
    }

    /* The Size row's single photo has its own higher-specificity override
       (see KNIT / SIZE SELECTOR section) that would otherwise still force
       it to 60% width - matching that here too so both rows land on the
       same fixed 80px. */
    ul[data-attribute_name="attribute_pa_size"] ~ .ls-photos-and-description .ls-lightbox-trigger {
        flex: 0 0 80px;
    }
}

/* ============================================
   RETIRED (2026-08-25): KNIT / SIZE INDEPENDENT COLUMNS (Job E)
   ============================================
   Used to give desktop its own ".ls-left-column"/".ls-right-column" split
   (label+pills next to static label+photo+description) - the approved
   wireframe now wants Size/Knit stacked the same way at every breakpoint,
   so this desktop-only column layout no longer applies. functions.php's
   Job E script (which built .ls-left-column/.ls-right-column) is retired
   too - see that file, search "RETIRED (2026-08-25)". The shared stacked
   layout both breakpoints now use lives in "KNIT / SIZE ROW: GRID LAYOUT"
   further below (unscoped 2026-08-25 to apply everywhere, not just below
   1000px). */

/* ============================================
   BOX GALLERY (packaging photos)
   ============================================
   Same viewport/scroll-row treatment as the Color Carousel above -
   reimplemented with its own classes/measurements rather than reusing the
   carousel's, since the two rows are independent and can have different
   item sizes. */

.ls-box-gallery-list {
    display: flex;
    flex-wrap: nowrap;
    gap: 12px;
    list-style: none;
    margin: 0;
    padding: 0 4px;
}

.ls-box-item {
    flex: 0 0 90px;
    cursor: zoom-in;
}

.ls-box-thumb {
    display: block;
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    border-radius: 2px;
}

.ls-box-gallery-viewport {
    width: calc((90px * 5) + (12px * 4));
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    padding: 4px 0;
    box-sizing: content-box;
    scrollbar-width: none;
    -ms-overflow-style: none;
    margin-top: 5px;
    /* breathing room below the Color carousel above it */
     background-color: #f0f0f0;
}

.ls-box-gallery-viewport::-webkit-scrollbar {
    display: none;
}

.ls-box-gallery-wrapper {
    position: relative;
    width: calc((90px * 5) + (12px * 4));
}

/* ============================================
   BOX GALLERY WIDTH (desktop, >=1000px) - Job H
   ============================================
   Same fix, same reason, as "COLOR CAROUSEL WIDTH (desktop, >=1000px) -
   Job H" above (search that heading for the full explanation, including
   why the viewport specifically also needs box-sizing:border-box) - this
   row hardcoded itself to the same 498px 5-item width, which now
   overflows the info column's real 436px content width at today's 50%
   image setting. width:100%, capped at the old fixed number as a
   max-width, so it always matches the column's actual width without
   needing a future edit if that width changes again, while still
   stopping at its natural 5-item size on a column with room to spare. */
@media (min-width: 1000px) {
    .ls-box-gallery-viewport {
        width: 100%;
        max-width: calc((90px * 4) + (12px * 3) + 8px);
        box-sizing: border-box;
    }

    .ls-box-gallery-wrapper {
        width: 100%;
        max-width: calc((90px * 4) + (12px * 3) + 8px);
    }
}

/* Reveal the hover arrows over the box gallery, same rule as the Color
   carousel's own (".ls-swatch-carousel-wrapper:hover .ls-carousel-arrow"
   above) - kept as a separate rule scoped to ".ls-box-gallery-wrapper"
   rather than editing that one, since the carousel's arrows share the
   ".ls-carousel-arrow" class but are built as their own DOM nodes per row
   (see functions.php), so each row's wrapper needs its own :hover trigger. */
@media (hover: hover) and (pointer: fine) {
    .ls-box-gallery-wrapper:hover .ls-carousel-arrow {
        opacity: 1;
        visibility: visible;
    }
}

/* ============================================
   LIGHTBOX (SHARED)
   ============================================
   One shared overlay used by both the Knit/Size reference-photo triggers
   above and the Box Gallery below. Deliberately separate from Blocksy's own
   PhotoSwipe gallery lightbox - see functions.php for why. Opened/closed via
   .is-open, toggled by the wp_footer script in functions.php.

   --- Base overlay (used by both Knit/Size and Box Gallery) --- */

.ls-lightbox {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: rgba(0, 0, 0, 0.85);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.2s ease;
    padding: 24px;
    box-sizing: border-box;
}

.ls-lightbox.is-open {
    opacity: 1;
    visibility: visible;
}

.ls-lightbox-image {
    max-width: 100%;
    max-height: 100%;
    border-radius: 4px;
    box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4);
}

.ls-lightbox-close {
    position: absolute;
    top: 20px;
    right: 24px;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    border: none;
    background-color: rgba(255, 255, 255, 0.9);
    font-size: 24px;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
}

/* --- Caption + prev/next (Box Gallery only) ---
   Hidden by default - only shown when the lightbox is opened WITH a
   sequence attached (the "has-sequence" class, added in functions.php's
   shared lightbox script). The Knit/Size reference-photo triggers never
   pass a sequence, so this never affects their existing single-image
   lightbox behavior. --- */

.ls-lightbox-prev,
.ls-lightbox-next,
.ls-lightbox-caption {
    display: none;
}

.ls-lightbox.has-sequence .ls-lightbox-prev,
.ls-lightbox.has-sequence .ls-lightbox-next {
    display: flex;
}

.ls-lightbox.has-sequence .ls-lightbox-caption {
    display: block;
}

.ls-lightbox-prev,
.ls-lightbox-next {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: none;
    background: rgba(255, 255, 255, 0.9);
    align-items: center;
    justify-content: center;
    font-size: 24px;
    line-height: 1;
    cursor: pointer;
    z-index: 2;
}

.ls-lightbox-prev {
    left: 20px;
}

.ls-lightbox-next {
    right: 20px;
}

.ls-lightbox-caption {
    position: absolute;
    bottom: 24px;
    left: 50%;
    transform: translateX(-50%);
    background: rgba(0, 0, 0, 0.6);
    color: #fff;
    padding: 8px 20px;
    border-radius: 20px;
    font-size: 14px;
    font-weight: 600;
    max-width: 80%;
    text-align: center;
}

/* ============================================
   CART PAGE: "CONTINUE SHOPPING" LINK
   ============================================
   Inserted directly before the cart block itself (functions.php) - simple
   inline-block link styling, matching the card's own muted/orange palette
   used throughout the rest of this file, without touching any of the cart
   block's own layout. */
.ls-continue-shopping {
    display: inline-block;
    margin-bottom: 16px;
    font-size: 14px;
    font-weight: 600;
    color: #767268;
    text-decoration: underline;
}

.ls-continue-shopping:hover {
    color: #FF8200;
}

/* ============================================
   MOBILE LAYOUT (below 1000px only)
   ============================================
   Everything in this section is scoped to max-width:999px - the same
   breakpoint Blocksy's own CSS already uses to stack .product-entry-wrapper
   into a single column (see the "Below the desktop breakpoint" note in
   PURCHASE-INFO CARD LAYOUT above) - so none of it can affect the
   desktop/tablet (1000px+) layout, which is governed entirely by the rules
   above and is left completely untouched. Pairs with the DOM-reordering
   wp_footer script in functions.php (search "Mobile layout reorder") -
   that script moves Title/short-description/Color Name/Knit/Size/Clear
   Options to the positions required on mobile; this section
   handles the Color Carousel / Box Gallery sizing (no DOM change needed,
   just a different width formula) plus the styling for those relocated/new
   elements once they're in their mobile position. The "Buy Now" button
   (search "ls-buy-now-button" below) is unrelated to that reorder script -
   its own wp_footer hook builds it directly in place, this section only
   styles it. */

/* --- New mobile-only elements: hidden by default here, at every width ---
   All genuinely NEW markup (not existing fields being moved) - unlike the
   rest of this file, these default to invisible, and only the
   max-width:999px block below ever turns them on, which keeps 1000px+ safe
   even if some other rule elsewhere ever changed cascade order. */
.ls-color-name-display {
    display: none;
}

/* The " | " joiner between the Color and Material halves - plain
   punctuation, not translatable label text, so it's kept out of both
   __() strings in functions.php and given its own span instead. Same
   font-size as the two labels it sits between, at every breakpoint (both
   the >=1000px and <1000px ".ls-color-name-display" blocks below use the
   identical 15px/#4a4a45-family values already), so this one shared,
   unscoped rule covers it everywhere rather than duplicating it per
   breakpoint. */
.ls-color-name-display .ls-color-name-separator {
    font-size: 15px;
    color: #767268;
}

.ls-color-card-info {
    display: none;
}

/* !important + the ".ls-cart-buttons-row" ancestor (raising specificity
   beyond a single class) - Blocksy's own core button reset
   (main.min.css, the "[type=\"submit\"]" rule among a long selector list)
   also targets this element unconditionally, via its "type" attribute, at
   EQUAL specificity to a lone ".ls-buy-now-button" class - too close a tie
   to trust cascade/source order alone to keep this element hidden outside
   its mobile-only window. */
.ls-cart-buttons-row .ls-buy-now-button {
    display: none !important;
}

/* Plain, full-width single-item column at every width by default -
   matches Add to Cart's own pre-existing width:100% rule exactly, so this
   new wrapper (see functions.php, search "ls-cart-buttons-row") renders as
   just Add to Cart alone until Buy Now is turned on for a given width
   (max-width:999px below, and the min-width:1000px Job B block further
   down for desktop). */
.ls-cart-buttons-row {
    display: flex;
    flex-direction: column;
    width: 100%;
}

/* ============================================
   "BUY NOW" (desktop, >=1000px) - Job B
   ============================================
   Shows the same Buy Now button mobile/tablet already has - same markup
   and redirect/validation logic, built once in functions.php (search
   "ls-buy-now-button"); nothing is duplicated there for desktop, this only
   adds the CSS needed to reveal and place it at desktop widths. Per
   request, stacked directly below Add to Cart - the same full-width-rows-
   in-a-column layout mobile/tablet already uses (search "Job 6" below),
   rather than a side-by-side row. The base ".ls-cart-buttons-row" rule
   just above is already "flex-direction: column" unconditionally, so only
   the gap between the two rows needs adding here. */
@media (min-width: 1000px) {
    .ls-cart-buttons-row {
        gap: 12px;
    }

    /* Un-hides the button (base rule above is "display:none!important" at
       every width by default) and gives it the same outlined "secondary
       action" look as the mobile/tablet version just below - values copied
       as-is from that block, nothing new introduced. */
    .ls-cart-buttons-row .ls-buy-now-button {
        display: flex !important;
        align-items: center;
        justify-content: center;
        padding: 5px 20px;
        border-radius: 3px;
        font-size: 15px;
        font-weight: 500;
        border: 2px solid #2872fa;
        background-color: transparent;
        color: #2872fa;
        cursor: pointer;
    }

    .ls-buy-now-button:hover {
        background-color: rgba(40, 114, 250, 0.08);
    }

    /* ============================================
       "VIEW CART" (desktop, >=1000px) - Job B
       ============================================
       Once an item's been added to cart, "View cart" now relocates next to
       the Quantity stepper at desktop widths too, matching mobile/tablet's
       existing Job 6 behavior exactly (search "VIEW CART (Job 6" below) -
       requested so it doesn't land squeezed inside ".ls-cart-buttons-row"
       between Add to Cart and Buy Now now that Buy Now also renders on
       desktop. The relocation itself is a JS change (functions.php, search
       "Job B" near "moveViewCartToDesktopPosition"); this is just
       ".ls-quantity-row"'s desktop styling, an exact copy of its
       mobile/tablet rule (search "ls-quantity-row" below) - a plain flex
       row, centered so the link's own text sits level with the stepper. */
    .ls-quantity-row {
        display: flex;
        align-items: center;
        gap: 12px;
    }
}

/* ============================================
   COLOR NAME + INFO CARDS (desktop, >=1000px) - Job C
   ============================================
   Desktop counterpart to the "COLOR NAME FIELD" / "COLOR INFO CARDS"
   rules inside the max-width:999px MOBILE LAYOUT block below. Neither
   ".ls-color-name-display" (the Color Name mirror element) nor
   ".ls-color-card-info" (the per-swatch info card markup) is new or
   rebuilt here - functions.php already builds both, at every breakpoint,
   already positioned exactly where this job wants them (the mirror sits
   right after the main image; the card markup is already appended into
   every ".ls-color-display-copy .variable-item"). Below 1000px they're
   the only place turning that existing markup on; this block just turns
   the SAME markup on for >=1000px too, with the SAME values, so desktop
   ends up visually identical to mobile/tablet's own treatment.
   1000px is this project's only breakpoint (every other rule in this
   file pairs a max-width:999px block with a min-width:1000px one, see
   PURCHASE-INFO CARD LAYOUT and "BUY NOW (desktop..." above) - there is
   no separate "large tablet" tier anywhere in this codebase, so this one
   min-width:1000px block already covers every iPad width at or above
   1000px the client asked about; nothing narrower is touched. */
@media (min-width: 1000px) {
    /* --- Color Name: hide the real row, show the mirror instead ---
       Exact copy of ".ls-color-row" / ".ls-color-name-display" below
       (search "COLOR NAME FIELD") - the real th.label/td.value pair
       stays in table.variations (still required there for variation
       matching/Add to Cart, see functions.php), just hidden here the
       same way mobile already hides it; ".ls-color-name-display" mirrors
       its live value next to the main image instead. */
    .ls-color-row {
        display: none !important;
    }

    .ls-color-name-display {
        display: flex;
        flex-direction: row;
        align-items: baseline;
        gap: 4px;
        margin: 0px 0 6px;
    }

    /* Material reuses the exact same rule as Color's own label/value
       (added to these selector lists rather than duplicated as new rules)
       so the two halves are guaranteed to render identically at this
       breakpoint, matching Color's own font-size/weight/color exactly. */
    .ls-color-name-display .ls-color-name-label,
    .ls-color-name-display .ls-material-name-label {
        font-size: 18px;
        font-weight: 500;
        font-family: 'Playfair Display', serif;
        color: #4a4a45;
    }

    .ls-color-name-display .ls-color-name-value,
    .ls-color-name-display .ls-material-name-value {
        font-size: 18px;
        font-weight: 500;
        font-family: 'Playfair Display', serif;
        color: #4a4a45;
    }

    /* --- Color info cards: same swap mobile/tablet already uses ---
       Exact copy of the "COLOR INFO CARDS" rules below (search that
       heading) - turns each carousel item from a plain swatch into a
       swatch plus stacked Name/Price/Material/Shipping card. The
       carousel's own scroll/arrow/clone-loop/click-to-select JS
       (functions.php, the color-carousel script) is completely
       untouched - only the item's own content and height change; it's
       still the same scrollable row, at the same fixed 90px item width
       already used at this breakpoint (top of file, "COLOR CAROUSEL"). */
    .ls-color-display-copy .variable-item {
        height: auto !important;
        display: flex;
        flex-direction: column;
    }

    .ls-color-display-copy .variable-item img {
        /* EDITED 8/27/2026 - !important added: the Woo Variation Swatches
           plugin's own frontend.css sets ".variable-items-wrapper
           .variable-item img { height: 100% }" at equal-or-higher
           specificity, and was inconsistently winning this tie for some
           swatch images (confirmed live: most images rendered ~99.3px
           tall vs 77.9px wide - taller than the intended square - while a
           couple rendered as a true 77.9x77.9 square). !important forces
           this rule to always win, for every image, closing the tie. */
        aspect-ratio: 1 / 1 !important;
        height: auto !important;
    }

    .ls-color-card-info {
        display: flex;
        flex-direction: column;
        gap: 5px;
        width: 100%;
        margin-top: 10px;
        padding: 10px 6px 4px;
        border-top: 1px solid #e3e0da;
        text-align: left;
    }

    .ls-color-card-name {
        font-size: 13px;
        font-weight: 700;
        font-family: 'Playfair Display', serif;
        color: #1a1a1a;
    }

    .ls-color-card-price {
        font-size: 13px;
        /* EDITED - 700 -> 600 to lighten the price and match the Knit/Size
           selector-button weight; mobile's copy of this rule (max-width:999px)
           gets the same change so both breakpoints stay identical. */
        font-weight: 600;
        font-family: 'Playfair Display', serif;
        color: #1a1a1a;
    }

    .ls-color-card-price .woocommerce-Price-amount {
        font-size: inherit;
        font-weight: inherit;
        color: inherit;
    }

    .ls-color-card-material,
    .ls-color-card-shipping {
        font-size: 11px;
        font-weight: 400;
        color: #767268;
    }
}

@media (max-width: 999px) {
    /* --- Color carousel / Box gallery: exactly 4 full items, no
       partial/clipped item at the screen edge ---
       Both rows hardcode a 5-item desktop width
       (calc((90px * 5) + (12px * 4)) = 498px - see COLOR CAROUSEL and BOX
       GALLERY above), sized for the ~58%-wide desktop gallery column.
       .ls-gallery-and-color-wrapper centers its children
       (align-items:center), but centering a fixed 498px-wide box inside a
       narrower phone screen just overflows it equally off BOTH edges -
       that reads as swatches getting clipped, not "centered". Switching to
       a fluid, percentage-based width (100% of the available column,
       divided evenly across exactly 4 items + 3 gaps) makes the row always
       exactly fill its container with zero overflow, at any phone width -
       the existing scroll/swipe/arrow-button JS (untouched) still reveals
       the remaining items beyond the first 4 exactly as it does today.
       No !important needed: these are plain "width"/"flex" declarations in
       this same file (not the plugin's own CSS), and this media-query
       block sits later in the file than COLOR CAROUSEL/BOX GALLERY above,
       so normal cascade order alone is enough to win at equal
       specificity - same reasoning already used successfully for the
       existing 90px flex-basis at desktop. */
    .ls-swatch-carousel-wrapper,
    .ls-box-gallery-wrapper {
        width: 100%;
    }

    /* box-sizing: border-box is needed here specifically because these two
       elements carry their own 4px padding (content-box by default - see
       COLOR CAROUSEL above). width:100% on a content-box element adds that
       padding ON TOP of 100%, overflowing the container by 8px and
       reintroducing the exact clipping this rule exists to fix - switching
       to border-box makes width:100% include the padding instead. */
    .ls-swatch-carousel-viewport,
    .ls-box-gallery-viewport {
        width: 100%;
        box-sizing: border-box;
    }

    /* Job 3 (Task 1): split from the previously-shared rule (both selectors
       used the same 12px-gap formula because both rows previously shared
       the same 12px gap) - Color cards now use a slightly wider gap (see
       ".ls-color-display-copy" gap rule just below) so each selector needs
       its own formula matching its own row's actual gap; Box Gallery's gap
       is untouched, still 12px (see BOX GALLERY above). */
    .ls-color-display-copy .variable-item {
        flex: 0 0 calc((100% - (14px * 3)) / 4);
    }

    .ls-box-item {
        flex: 0 0 calc((100% - (12px * 3)) / 4);
    }

    /* Reference photos (Knit/Size), tablet tier: sized relative to their
       OWN column's width (the "photos" grid area from the two-column
       split just below) instead of chasing the box gallery's separate
       formula - measured live, exact parity with the box gallery isn't
       achievable for the Knit row's two-photo pair inside this column at
       every tablet width, so the priority shifts to keeping Size's single
       photo and each of Knit's two photos sized consistently WITH EACH
       OTHER instead. Knit's pair splits its column width in half (minus
       the gap between them); Size's single photo matches that same
       per-photo size rather than stretching to fill its whole column, so
       the two rows read as visually matched. */
    .ls-lightbox-trigger {
        flex: 0 0 calc((100% - 12px) / 2);
    }

    /* The Size row's single photo has its own higher-specificity rule
       (line ~802) that would otherwise keep overriding the plain rule
       just above - matching it here too, sized to the same per-photo
       width Knit's pair uses (see comment above) rather than its own
       previous box-gallery-matching formula. */
    ul[data-attribute_name="attribute_pa_size"]~.ls-photos-and-description .ls-lightbox-trigger {
        flex: 0 0 calc((100% - 12px) / 2);
    }

    /* Job 3 (Task 1): a bit more breathing room between color cards now
       that they carry taller stacked text content below the swatch image -
       desktop's own 12px gap (top of file, ".ls-color-display-copy") is
       untouched, this only widens it below 1000px. */
    .ls-color-display-copy {
        gap: 14px;
    }

    /* --- Color swatches: self-adjust height to match the new fluid width ---
       Box Gallery items were never affected by this because .ls-box-thumb
       already sets "aspect-ratio: 1/1" on the <img> unconditionally (see BOX
       GALLERY above) - height always follows width there, at every
       breakpoint, so the width change above was enough on its own.
       Color swatches have no such rule: their height instead comes from the
       Woo Variation Swatches plugin's own CSS
       (".woo-variation-swatches .variable-items-wrapper .variable-item:not(.radio-variable-item)",
       frontend.min.css), which sets a FIXED
       "height: var(--wvs-single-product-item-height, 30px)" - resolved on
       this page to a flat 80px (an admin-configured plugin setting, not
       something this theme sets). That 80px never changes by breakpoint,
       while the swatch's WIDTH above now does - at 768px (iPad Mini) the
       4-per-row fluid width comes out to roughly 162px, so an 80px-tall
       swatch reads as an obviously squashed 2:1 rectangle. (The same
       mismatch technically exists at every width below 1000px, including
       phone widths - 768px is just where it's most visually obvious, since
       phone widths happen to land closer to 80px wide already.)
       Fix: match the Box Gallery's own approach - aspect-ratio:1/1 so
       height always follows the fluid width, same technique, same result,
       kept as its own separate rule (not merged into the shared flex-basis
       rule above) since it's swatch-specific.
       !important is required on "height": the plugin's own rule above has
       more compound classes than our ".ls-color-display-copy .variable-item"
       (".woo-variation-swatches .variable-items-wrapper .variable-item:not(.radio-variable-item)"
       carries 3 classes worth of specificity via :not(), ours carries 2) -
       normal cascade order isn't enough to beat a HIGHER-specificity rule
       regardless of which stylesheet loads later, so without !important the
       plugin's fixed 80px would keep winning even inside this block. */
    /* Job 2 update: aspect-ratio now applies to the IMAGE specifically, not
       the whole card - the info cards (below, search "COLOR INFO CARDS")
       add text content below the image, and the card needs to grow taller
       to fit it (no fixed height, per that task's own scope note) instead
       of staying locked to a square. The swatch image itself keeps the
       exact same square sizing as before - this only changes how the
       extra height for the card's text gets distributed. */
    .ls-color-display-copy .variable-item {
        height: auto !important;
        display: flex;
        flex-direction: column;
    }

    .ls-color-display-copy .variable-item img {
        /* EDITED 8/27/2026 - !important added: see the matching desktop
           rule above (search "EDITED 8/27/2026") for why - same plugin
           tie, same fix, mobile/tablet copy of that rule. */
        aspect-ratio: 1 / 1 !important;
        height: auto !important;
    }

    /* ============================================
       COLOR INFO CARDS (Job 2 - see functions.php, search "ls-color-card-info")
       ============================================
       Stacked text below each swatch image: Color name, Price, Material,
       then "Free shipping" - built server-side/JS per swatch, this section
       only handles layout/typography. Selection styling (the orange
       selected-border) is untouched - it's the plugin's own shared
       --wvs-selected-item-box-shadow variable applied to the whole
       ".variable-item" (see SWATCH SELECTION & HOVER STATES at the top of
       this file), which already covers the new taller card shape
       automatically without any changes here. Border-top acts as the
       "divider line" between the image and the text block, matching the
       reference screenshots' general card shape (image, divider, stacked
       text) without copying their exact visual style. No fixed height/
       truncation anywhere here, by design (see Task 1's own scope note) -
       the card and the row around it just grow to fit. */
    /* Job 3 (Task 1): padding/gap widened - was gap:2px and margin-top/
       padding-top:8px with NO horizontal padding at all, so the text sat
       flush against the card's own left/right edges. Padding is now a
       single shorthand (top/sides/bottom) instead of just padding-top, and
       the line gap is bigger so the four lines read as a clean stacked
       block instead of clumping together. The border-top "divider" between
       image and text, and the selection styling on the whole
       ".variable-item" (the orange --wvs-selected-item-box-shadow border,
       see SWATCH SELECTION & HOVER STATES at the top of this file), are
       both untouched by this - they already scale with however tall/wide
       the card ends up. */
    .ls-color-card-info {
        display: flex;
        flex-direction: column;
        gap: 5px;
        width: 100%;
        margin-top: 10px;
        padding: 10px 6px 4px;
        border-top: 1px solid #e3e0da;
        text-align: left;
        /* EDITED 8/27/2026 - reserved height so every card in the row ends
           up the same total height regardless of a 1-word vs 2-word color
           name (e.g. "Pink" vs "Kelly Green") - content stays top-aligned
           (no justify-content/align-items change here), so a shorter name
           just leaves extra blank space at the BOTTOM of this block
           instead of the whole card shrinking. Estimated from this block's
           own font-size/line-height math for a worst case of a 2-line
           name + 1-line price + 2-line material + 1-line shipping - may
           need a small follow-up nudge once checked against the actual
           longest name/material text live. Bumped 124px -> 134px
           (2026-08-27) - confirmed live that the 2-line-name case
           ("Light Purple") was rendering with ~0px of the intended bottom
           slack; +10px restores a small consistent gap even there.
           Bumped again 134px -> 137px (2026-08-27) - a little more
           breathing room for the 2-line-name case specifically. */
        min-height: 137px;
    }

    .ls-color-card-name {
        font-size: 13px;
        font-weight: 700;
        font-family: 'Playfair Display', serif;
        color: #1a1a1a;
        /* EDITED 8/27/2026 - explicit line-height so the min-height math
           above is predictable instead of relying on an inherited value. */
        line-height: 1.3;
    }

    /* The price copies WooCommerce's own price markup (".woocommerce-Price-amount"
       etc.) via innerHTML, same as every other price display in this file -
       font-size/weight reset here so it reads as plain card text instead of
       inheriting the bold/larger styling ".woocommerce-Price-amount" carries
       elsewhere on this page (see PURCHASE-INFO CARD STYLING above).
       Job 3 (Task 1): bumped from 12px/600 to 13px/700 - Price is the piece
       shoppers scan for first, so it needs to read as visibly more
       prominent than Material/Free-shipping below it, without going so
       large it throws off the card's overall balance (still well under the
       main Price display's own 22px, see PURCHASE-INFO CARD STYLING
       above). */
    .ls-color-card-price {
        font-size: 13px;
        /* EDITED - 700 -> 600, matched to the desktop copy of this rule.
           Lightens the price to the Knit/Size selector-button weight. */
        font-weight: 600;
        /* EDITED - added so the mobile card price renders in Playfair Display
           like the desktop cards already do (mobile was inheriting the system
           font here, the one desktop/mobile tile mismatch). The $ amount
           inside inherits this, so it doesn't need its own font-family. */
        font-family: 'Playfair Display', serif;
        color: #1a1a1a;
        /* EDITED 8/27/2026 - see the matching note on .ls-color-card-name
           above. */
        line-height: 1.3;
    }

    .ls-color-card-price .woocommerce-Price-amount {
        font-size: inherit;
        font-weight: inherit;
        color: inherit;
    }

    .ls-color-card-material,
    .ls-color-card-shipping {
        font-size: 11px;
        font-weight: 400;
        /* EDITED 8/27/2026 - see the matching note on .ls-color-card-name
           above. */
        line-height: 1.3;
        color: #767268;
    }

    .product_title.entry-title {
    font-size: 30px !important;
    font-weight: 500 !important;
    line-height: 15px !important;
    letter-spacing: -1px !important;
    font-family: 'Playfair Display', serif !important;
    margin-bottom: 0px !important;
}
    /* --- Title / short-description: mobile-only spacing ---
       Moved by the wp_footer script (functions.php) to sit before the main
       image, outside .summary.entry-summary-items - so they no longer get
       that card's own padding/background. .product-entry-wrapper already
       provides the same side inset the gallery/card get today (confirmed
       via computed layout - no extra horizontal padding needed here), this
       just adds a bit of breathing room below the short-description before
       the main image starts. */
    .ls-short-description {
        margin: 4px 0 16px;
    }
}

/* ============================================
   KNIT/Size selector, reference photos, and pills below - UNSCOPED
   (2026-08-25) to apply at every breakpoint, not just below 1000px.
   This chunk used to live inside the max-width:999px block above; it's
   split out into its own unscoped section here (and the block above is
   reopened again further down for the rest of the mobile-only content)
   because the approved wireframe now wants Size/Knit to use this exact
   same stacked layout at every breakpoint, not just on mobile/tablet -
   see functions.php, search "RETIRED (2026-08-25)" near "Job E" for the
   desktop-only column layout this replaces.
   ============================================ */

/* ============================================
   RETIRED (2026-08-25): KNIT / SIZE CURRENT-VALUE ROW (Job 3, Task 2a/2b)
       ============================================
       Used to turn Knit/Size's th.label into an inline "Size: Regular" /
       "Knit: Conventional" phrase. The approved wireframe now wants the
       pills to act as BOTH selector and value - no separate current-value
       word next to the label at all - so th.label no longer gets any
       special inline treatment here; it falls back to the same plain
       small-caps uppercase field-tag style every other (non-Color) label
       already uses (PURCHASE-INFO CARD STYLING, further up). The one thing
       still needed is hiding the plugin's own current-value element - see
       "HIDE THE CURRENT-VALUE TEXT" immediately below. */

    /* ============================================
       HIDE THE CURRENT-VALUE TEXT (Knit/Size) - 2026-08-25
       ============================================
       The Woo Variation Swatches plugin inserts
       ".woo-selected-variation-item-name" into th.label itself, dynamically
       via its own JS - it doesn't exist in the initial server-rendered
       HTML (confirmed directly), so there's no server-side markup to just
       not print; hiding the plugin's own element is the correct fix here.
       Scoped via ":has()" to just the Knit/Size <tr>s (same technique used
       throughout this file) so Color's own current-value text (still
       wanted - see "Desktop only (1000px+): collapses Color's label+value"
       further up, and ".ls-color-name-display" below) is never touched. */
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_knit"]) th.label .woo-selected-variation-item-name,
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_size"]) th.label .woo-selected-variation-item-name {
        display: none;
    }

    /* ============================================
       KNIT / SIZE ROW: GRID LAYOUT (Job 4, v2)
       ============================================
       Restructures each Knit/Size <tr> into a 3-block self-contained unit,
       per the approved wireframe (v2 - supersedes an earlier attempt that
       put the label+pills on one row with the description in the middle;
       that structure is fully replaced below, not layered on top of):
         Block 1 (top, left-aligned, compact): the "Size"/"Knit" label
                 (th.label - no current-value text next to it, see "HIDE
                 THE CURRENT-VALUE TEXT" above) directly above the pill row
                 (td.value's <ul>) - both left-aligned and sized to their
                 own content, not stretched edge to edge, so they read as
                 one small grouped block.
         Block 2 (middle): reference photo(s), centered as a group.
         Block 3 (bottom): the explanatory description, full width.

       CSS Grid - not a new wrapper element, and no DOM moves beyond the
       description's (see functions.php) - is what makes this possible
       without disturbing th.label or the real <select>/<ul>'s required
       positions: the plugin's own $(ul).parent().prev() lookup (long
       comment in functions.php) needs <ul> to stay a DIRECT child of
       <td class="value">, and td.value needs to stay th.label's immediate
       next sibling, at all times. "display: contents" on td.value below
       satisfies both - it doesn't move anything in the DOM, it only stops
       td.value from generating its own box, which promotes ITS children
       (the hidden real <select>, the pill <ul>, and the reference-photos
       wrapper .ls-photos-and-description) to be direct grid items of the
       <tr> itself, siblings of th.label in the grid - exactly what's
       needed to stack "th.label" and "the pill <ul>" as their own two grid
       rows while td.value's OTHER children (photos, description) land on
       their own rows too. The hidden real <select> (display:none, set by
       the Woo Variation Swatches plugin itself) needs no grid-area of its
       own - display:none already removes it from grid placement entirely,
       same as it does in normal flow.

       A single grid column (not two, unlike the superseded v1) since
       nothing needs to sit side by side anymore - every piece stacks
       full-width down the row, with th.label/the pill <ul> individually
       shrunk to their own content width (justify-self:start below) rather
       than the column stretching them.

       Spacing between the 4 grid rows (label/pills/photos/desc) is
       deliberately NOT uniform (a tight gap within Block 1, a bigger gap
       between blocks) - CSS Grid's own "gap"/"row-gap" property can only
       apply ONE value between every row, so per-item margin-bottom is used
       instead of grid-row-gap, giving each boundary its own size.

       Overrides the base KNIT/SIZE SELECTOR section's own
       "gap: 0 !important" (tr) and "display: flex" (td.value) rules
       further up this file - both needed !important there to beat
       WooCommerce's own flex-column row default, so this override needs
       !important too, and wins by appearing later in the file (same
       convention already used throughout this file for beating an earlier
       !important rule with a later, equally-specific one). */
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_knit"]),
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_size"]) {
        display: grid !important;
        /* 2026-08-25: "label"/"pills" now share ONE row (auto-sized first
           column for the label, second column takes the rest) instead of
           each being its own full-width row - the approved fix is the
           label sitting inline to the LEFT of the pills, not stacked above
           them. Every other row (static-label/photos/desc) still spans
           both of these columns as one full-width item (its area name is
           just repeated in both column positions below), so nothing about
           their own width/position changes. */
        grid-template-columns: auto 1fr;
        grid-template-areas:
            "label pills"
            "static-label static-label"
            "photos photos"
            "desc desc";
        /* Horizontal gap between the label and the pills, now that they're
           side by side - replaces th.label's old margin-bottom (was the
           vertical gap down to the pill row when stacked). align-items
           centers label text against the pills' own height - only visibly
           affects THIS row, since every other row's item spans the full
           width and has no second column item to align against. */
        column-gap: 8px;
        align-items: center;
    }

    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_knit"]) td.value,
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_size"]) td.value {
        display: contents;
    }

    /* Block 1: the plain "Size:"/"Knit:" label (typography comes from the
       shared base th.label rule, PURCHASE-INFO CARD STYLING further up -
       see "RETIRED... KNIT / SIZE CURRENT-VALUE ROW" above; the trailing
       ":" is static generated content, see "STATIC LABEL COLON" below -
       NOT the plugin's dynamic current-value span, which stays hidden).
       This just places th.label into the grid, inline with the pills via
       the tr's own grid-template-columns/column-gap above.
       justify-self/align-self:start keep it shrunk to its own content
       size and pinned to the row's top-left corner, instead of the grid
       cell's default "stretch". No margin-bottom needed any more - the
       label and the pills are on the same row now, so the pill row's own
       margin-bottom (below) is what provides the gap down to Block 2. */
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_knit"]) th.label,
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_size"]) th.label {
        grid-area: label;
        justify-self: start;
        align-self: center;
    }

    /* STATIC LABEL COLON (2026-08-25): "Size:"/"Knit:" - static generated
       text on the label itself, not adjacent to any dynamic value (the
       plugin's ".woo-selected-variation-item-name" stays display:none, see
       "HIDE THE CURRENT-VALUE TEXT" above - completely untouched by this).
       Same technique already used for Color's own label (search "Adds the
       ':'" above). */
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_knit"]) th.label label::after,
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_size"]) th.label label::after {
        content: ":";
    }

    /* Block 1: the pill row, inline beside the label (same reasoning as
       th.label above) - shrunk to its own content width, left-aligned
       within its own grid column. The pills' own internal layout (flex
       row, gap, shape, padding, colors) is untouched here - see PILL
       SELECTORS below, out of scope for this job. margin-bottom is the
       gap down to Block 2 (the "Size/Knit Comparison" heading) - now the
       only spacing needed between Block 1 and Block 2, since label no
       longer contributes its own margin-bottom above it.
       !important needed here specifically (th.label above didn't need
       it): Blocksy's own core reset (main.min.css, "ul, ol") sets
       "margin-block-end" - the LOGICAL-property equivalent of
       margin-bottom - which competes with this physical margin-bottom for
       the same edge; confirmed directly that without !important the
       gap collapsed to 0px, Blocksy's declaration winning despite this
       rule's higher selector specificity. */
    ul[data-attribute_name="attribute_pa_knit"],
    ul[data-attribute_name="attribute_pa_size"] {
        grid-area: pills;
        justify-self: start;
        align-self: center;
        margin-bottom: 14px !important;
    }

    /* New row (Job 7, Item 3): the static "Size Comparison"/"Knit
       Comparison" heading (text set in functions.php,
       ls_render_static_option_label(), renamed 2026-08-25 from "Long"/
       "Shaker") between the pills and the reference photo(s), naming the
       section the fixed comparison photo(s) belong to - not tied to the
       currently selected pill, see that function's docblock. Centered, and
       given its OWN margin-bottom for the gap down to Block 2 (photos) -
       the pill row's own margin-bottom above already provides the gap up
       to THIS row, so together the two margins give this row its own
       dedicated space rather than squeezing it into what used to be a
       single pills-to-photos gap. Switched on (display:block) here,
       overriding the "hidden by default" base rule (KNIT / SIZE SELECTOR +
       REFERENCE PHOTOS section) - now unscoped 2026-08-25 to apply at
       every breakpoint, since desktop uses this exact same stacked layout
       now instead of a separate two-column control. */
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_knit"]) .ls-static-option-label,
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_size"]) .ls-static-option-label {
        display: block;
        grid-area: static-label;
        justify-self: center;
        margin-bottom: 0px;
        padding: 4px 14px;
        font-size: 13px;
        font-weight: 700;
        font-family: 'Playfair Display', serif;
        letter-spacing: normal;
        text-transform: none;
        color: #686868;
    }

    /* Block 2: reference photo(s), centered as a group - full width (the
       grid area spans the row) with its content centered inside it rather
       than left-aligned or stretched, so Size's single photo and Knit's
       two photos both read as intentionally centered instead of Knit's
       wider pair looking "more full" than Size's one. margin-bottom is the
       gap down to Block 3 (description). */
    .entry-summary-items table.variations td.value .ls-photos-and-description {
        grid-area: photos;
        margin-bottom: 14px;
    }

    /* The actual centering happens here, not via anything on the wrapper
       above: .ls-reference-photos's children (.ls-lightbox-trigger) use a
       PERCENTAGE flex-basis (the box-gallery-matching fluid formula from
       "the reference-photo sizing fix", search "PHONE SUB-BREAKPOINT" -
       deliberately untouched, out of scope for this job). A percentage
       flex-basis can't be used to shrink .ls-reference-photos down to just
       the photo(s)' own size (confirmed directly - it kept computing to
       the row's full available width regardless of photo count, since the
       browser falls back to the images' large native intrinsic dimensions
       when sizing an unconstrained flex container), so centering the
       WRAPPER's content via align-items has nothing narrower to center.
       justify-content:center here centers the photo(s) WITHIN
       .ls-reference-photos's own (already full-width) row instead - same
       visual result, without touching the untouchable sizing formula. */
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_knit"]) .ls-reference-photos,
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_size"]) .ls-reference-photos {
        justify-content: center;
    }

    /* Block 3: the explanatory description, full width, as the LAST piece
       of the row (below the photo(s), not inline with the label anymore -
       that was the superseded v1 approach). This is a layout/position
       change only - the text content, its data source (the attribute
       term's Description field), and its dynamic update behavior are all
       unchanged (see functions.php, "Mobile layout reorder", search
       "Job 4"). Its own typography (font-size/line-height/color) still
       comes from the shared, unscoped ".ls-option-description" rule above
       in this section - nothing about the text itself changes here. No
       margin-bottom needed - it's the last block in the row, the existing
       inter-row divider (".variations tr:not(:last-child)", PURCHASE-INFO
       CARD STYLING above) already provides the gap down to the next <tr>. */
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_knit"]) .ls-option-description,
    .entry-summary-items table.variations tr:has(ul[data-attribute_name="attribute_pa_size"]) .ls-option-description {
        grid-area: desc;
    }

    /* ============================================
       PILL SELECTORS (Job 2 - Knit/Size)
       ============================================
       Converts the two-line "Optional [heading] / Select [switch]" toggle
       into a row of flat, tappable pill buttons - BOTH options shown and
       directly clickable (not one hidden "default" + one togglable
       "alternate"), similar in spirit to a size-selector button row.
       The underlying markup/classes (.ls-default-option / .ls-alt-toggle,
       and the Optional/Select/switch elements the toggle-build script
       above still always creates) are untouched - this section only
       changes how they RENDER, at every breakpoint (unscoped 2026-08-25 -
       desktop used to keep its own separate two-line switch UI here, see
       "RETIRED... PILL SELECTORS (desktop...) - Job B" further up; it now
       uses this exact same pill treatment instead). The two click-handler
       guards that make this behaviorally correct (tapping a pill selects
       it directly; re-tapping an already-selected pill is a no-op, not a
       forced switch to the other pill) live in functions.php - search
       "Job 2" near ".ls-toggle". */

    /* Un-hide the default option (e.g. "Conventional"/"Regular") as its own
       pill, same as the alternate option - both pills are now the only
       place a Knit/Size option's name appears at all (see "HIDE THE
       CURRENT-VALUE TEXT" above), at every breakpoint. !important beats
       the unconditional "display:none!important" hide rule further above
       (same selector, equal specificity) - this rule wins simply by
       appearing later in the file, the same convention used throughout
       this file. */
    ul[data-attribute_name="attribute_pa_knit"] .variable-item.ls-default-option,
    ul[data-attribute_name="attribute_pa_size"] .variable-item.ls-default-option {
        display: flex !important;
        /* Job 3 (Task 2c): forces the default option (e.g. "Regular") to
           render FIRST regardless of the underlying taxonomy terms' own
           admin-configured order (which currently lists the alternate
           option first, rendering "Long, Regular"/"Shaker, Regular"
           instead of the required "Regular, Long"/"Regular, Shaker"). Cheap
           fix since the row is already a flex container (see "The row
           itself" below): CSS "order" reorders flex items visually without
           touching the DOM - unlike the Title/Color-Name/Knit-Size row
           reorders in functions.php ("Mobile layout reorder"), which
           needed real DOM moves because THEIR containers aren't flex
           containers (see that script's own "Why DOM reordering instead of
           CSS order" comment) - this row already is one. */
        order: 1;
    }

    ul[data-attribute_name="attribute_pa_knit"] .variable-item.ls-alt-toggle,
    ul[data-attribute_name="attribute_pa_size"] .variable-item.ls-alt-toggle {
        /* Job 3 (Task 2c): pairs with "order: 1" on the default option
           above - keeps the alternate option (e.g. "Long"/"Shaker") second,
           after "Regular", regardless of DOM order. */
        order: 2;
    }

    /* EDITED 8/27/2026 */
    /* The row itself: both pills side by side, wrapping if the row ever
       needs more space than the card's width allows. Gap tightened from
       10px to 4px (2026-08-27) - real devices were measured rendering the
       pill text (Playfair Display) up to ~16% wider than desktop at the
       identical computed font-size/font-family, enough on its own to tip
       "Conventional"/"Shaker" into wrapping at 320px-wide phones even
       though desktop DevTools at the same 320px width stayed on one line.
       Freeing up the gap's spare width gives the row enough slack to
       absorb that per-device text-width variance without wrapping. */
    ul[data-attribute_name="attribute_pa_knit"],
    ul[data-attribute_name="attribute_pa_size"] {
        display: flex;
        flex-direction: row;
        flex-wrap: wrap;
        gap: 4px;
    }

    /* Shared pill shape for BOTH options - resets the fixed sizing meant
       for the old design back to a natural, content-based size, and lays
       out a plain bordered, rounded button. !important needed throughout:
       beating both the plugin's own higher-specificity sizing rule
       (".woo-variation-swatches .variable-items-wrapper .variable-item:not(.radio-variable-item)",
       already documented above, under KNIT / SIZE SELECTOR) and this
       file's own unconditional ".ls-alt-toggle" reset (margin/border/
       padding/background: ...!important, further above - written for the
       old design, where the alternate option had NO visible border/padding
       of its own). Selected-state styling is handled separately below
       ("PILL SELECTED-STATE FIX + ENHANCEMENT") - an earlier version of
       this comment assumed ".selected" would automatically pick up its
       highlight from the plugin's shared --wvs-selected-item-box-shadow
       mechanism with no extra rule needed (true for the Color cards, see
       SWATCH SELECTION & HOVER STATES at the top of this file) - confirmed
       directly that this silently failed for pills specifically, see the
       fix below for why. */
    ul[data-attribute_name="attribute_pa_knit"] .variable-item.ls-default-option,
    ul[data-attribute_name="attribute_pa_size"] .variable-item.ls-default-option,
    ul[data-attribute_name="attribute_pa_knit"] .variable-item.ls-alt-toggle,
    ul[data-attribute_name="attribute_pa_size"] .variable-item.ls-alt-toggle {
        width: auto !important;
        height: auto !important;
        /* EDITED 8/27/2026 - left margin tightened from 5px to 2px, paired
           with the row gap reduction above - see that comment for why. */
        margin: 12px 0 0 2px !important;
        /* Job 3 spacing pass: zeroed out (was "10px 18px" originally, then
           briefly widened) - pills now form-fit tightly around the text
           itself instead of carrying extra button-sized padding, confirmed
           live in DevTools to look tighter/cleaner against the inline
           label+description row above. Border still gives the pill its
           own visible edge even with no interior space.
           EDITED 8/27/2026 - top/bottom padding bumped 0 -> 2px (left/
           right stay 0, still form-fit horizontally) - pills were reading
           a bit too skinny/flat vertically. Unscoped rule, so this applies
           to both mobile/tablet and desktop. */
        padding: 2px 0 !important;
        border: 1px solid #d8d3c7 !important;
        /* Job 3 (Task 2d): rounded RECTANGLE (was fully-rounded 999px
           "pill" shape) - matches the corner radius already used elsewhere
           on this card (".summary.entry-summary-items" / ".ls-reference-photo"
           above, both 8px) instead of standing out as a different shape. */
        border-radius: 2px !important;
        background-color: #fff !important;
        white-space: nowrap;
        cursor: pointer;
    }

    /* ============================================
       PILL SELECTED-STATE FIX + ENHANCEMENT (Job 5)
       ============================================
       BUG FIX: confirmed via screenshot and direct testing that neither
       pill ever showed a correct "this is selected" state - the
       previously-selected pill kept its highlight after being deselected,
       and the newly-selected pill never got one. Root cause, found by
       auditing every CSS rule touching box-shadow on this page: this
       file's own ".ls-alt-toggle" reset, further up (search "The two
       swatch <li>s Woo Variation Swatches renders"), sets
       "box-shadow: none !important" UNCONDITIONALLY - at the time this was
       written, still correct and needed for desktop's own two-line switch
       UI (where the alt option's selection was shown by ".ls-toggle-switch"
       instead, not a box-shadow ring). This same pill treatment now covers
       desktop too (unscoped 2026-08-25 - this whole rule set no longer
       lives inside a max-width:999px block, see "KNIT / SIZE ROW: GRID
       LAYOUT" above), so the base reset is now just the
       shared neutral "unselected" baseline both mobile/tablet's and
       desktop's pills rely on - no switch-specific reason left, but the
       reset itself is still correct and still needed. At the time, the
       same !important also silently cancelled the alt-toggle pill's OWN
       selected-state ring below 1000px, with nothing anywhere overriding
       it back on for the selected case. Writing this fix as its own
       explicit, !important rule for BOTH the selected AND unselected pill
       states (rather than only patching the selected case) closes out
       every combination directly instead of relying on inheritance from
       the shared shape rule above or the plugin's own default - confirmed
       directly (repeated clicks, both attributes, both directions) that
       this reflects the correct pill on every click, not just on load.

       ENHANCEMENT: strengthens the selected look while this is being
       touched anyway. Amazon's own reference screenshot is the inspiration
       for the general APPROACH (bordered + filled + bold), but the border
       color stays this site's own orange, not Amazon's blue - reusing the
       exact same --wvs-selected-item-box-shadow variable/mechanism the
       Color info cards' own selected border already uses (SWATCH SELECTION
       & HOVER STATES, top of this file), not a new color. Background fill
       is a placeholder light blue (explicitly not a final brand color, per
       this job's own scope note) - just needs to read as clearly distinct
       from the unselected white fill. Scoped to Knit/Size pills ONLY -
       the Color info cards' own selection styling is untouched, out of
       scope for this job. */
    ul[data-attribute_name="attribute_pa_knit"] .variable-item.ls-default-option.selected,
    ul[data-attribute_name="attribute_pa_size"] .variable-item.ls-default-option.selected,
    ul[data-attribute_name="attribute_pa_knit"] .variable-item.ls-alt-toggle.selected,
    ul[data-attribute_name="attribute_pa_size"] .variable-item.ls-alt-toggle.selected {
        box-shadow: var(--wvs-selected-item-box-shadow) !important;
        background-color: #e0e0e0 !important;
    }

    /* Bold text for the selected pill's own label - set on the text span
       itself (not the <li>) because the shared, unscoped
       ".variable-item-span" rule (KNIT / SIZE SELECTOR section, top of
       this file) already targets the span directly with its own
       font-weight, which would otherwise win over anything merely
       inherited from the <li>. */
    ul[data-attribute_name="attribute_pa_knit"] .variable-item.ls-default-option.selected .variable-item-span,
    ul[data-attribute_name="attribute_pa_size"] .variable-item.ls-default-option.selected .variable-item-span,
    ul[data-attribute_name="attribute_pa_knit"] .variable-item.ls-alt-toggle.selected .variable-item-span,
    ul[data-attribute_name="attribute_pa_size"] .variable-item.ls-alt-toggle.selected .variable-item-span {
        font-weight: 500 !important;
    }

    /* Unselected pill(s): explicit reset so nothing carries over from a
       previous selection - no ring, plain white fill (already the shared
       shape rule's own default above, restated here so the selected/
       unselected split is complete and unambiguous in one place), regular
       (not bold) text. */
    ul[data-attribute_name="attribute_pa_knit"] .variable-item.ls-default-option:not(.selected),
    ul[data-attribute_name="attribute_pa_size"] .variable-item.ls-default-option:not(.selected),
    ul[data-attribute_name="attribute_pa_knit"] .variable-item.ls-alt-toggle:not(.selected),
    ul[data-attribute_name="attribute_pa_size"] .variable-item.ls-alt-toggle:not(.selected) {
        box-shadow: none !important;
        background-color: #fff !important;
    }

    ul[data-attribute_name="attribute_pa_knit"] .variable-item.ls-default-option:not(.selected) .variable-item-span,
    ul[data-attribute_name="attribute_pa_size"] .variable-item.ls-default-option:not(.selected) .variable-item-span,
    ul[data-attribute_name="attribute_pa_knit"] .variable-item.ls-alt-toggle:not(.selected) .variable-item-span,
    ul[data-attribute_name="attribute_pa_size"] .variable-item.ls-alt-toggle:not(.selected) .variable-item-span {
        font-weight: 400 !important;
    }

    /* Both options' inner contents: a plain, centered row (just the
       heading text remains visible now - see below), overriding the base
       KNIT / SIZE SELECTOR rule's column layout (built for the old
       two-line design) back to a single row for a pill's plain text. */
    ul[data-attribute_name="attribute_pa_knit"] .variable-item-contents,
    ul[data-attribute_name="attribute_pa_size"] .variable-item-contents {
        flex-direction: row;
        align-items: center !important;
    }

    /* "Optional"/"Select"/the switch box: all specific to the old two-line
       switch design, hidden here so only the option's own heading text
       (e.g. "Shaker") remains - the SAME heading text the default option
       already shows plainly (it never had this extra chrome to begin
       with), so both pills read consistently as a plain label each. */
    .ls-optional-label,
    .ls-choose-label,
    .ls-toggle-switch {
        display: none;
    }

    /* .ls-toggle-line1/.ls-toggle-line2 stop generating their own boxes -
       "display:contents" makes their remaining visible child (just the
       heading span, once Optional/Select/switch are hidden above) render
       as if it were a direct child of .variable-item-contents, with no
       leftover empty-wrapper spacing/line-break from the old two-line
       structure. */
.ls-toggle-line1,
.ls-toggle-line2 {
    display: contents;
}

/* ============================================
   MOBILE LAYOUT (below 1000px only), continued
   ============================================
   Reopens the max-width:999px block that was closed above (search
   "UNSCOPED (2026-08-25)") to carry the Knit/Size chunk out to its own
   unscoped section - everything from here down is still mobile/tablet-only,
   exactly as before. */
@media (max-width: 999px) {
    /* ============================================
       COLOR NAME FIELD (see functions.php, search "ls-color-name-display")
       ============================================
       The REAL row (".ls-color-row" - the same live th.label/td.value pair
       that renders inside table.variations at 1000px+) stays exactly where
       it is, fully functional, just hidden here - the real
       <select name="attribute_pa_color"> it contains has to stay a
       descendant of form.variations_form for variation matching/Add to
       Cart to keep working, so unlike Knit/Size below it's never actually
       moved (see the long comment in functions.php for why an earlier
       version that DID move it broke variation matching entirely).
       ".ls-color-name-display" is the separate, visual-only element shown
       here instead, directly between the Main Image and the Color
       carousel/cards - a MutationObserver (functions.php) keeps its value
       text mirroring the real row's live current-value text, so it still
       updates automatically on every variation change. */
    .ls-color-row {
        display: none !important;
    }

    /* QC fix: label + value inline on one line ("Color: Off-White"),
       not stacked - flex-direction:row + align-items:baseline instead of
       the column layout used everywhere else in this file for label/value
       pairs. (Job 3: Knit/Size now use this exact same inline pattern too -
       see KNIT / SIZE CURRENT-VALUE ROW above - so this is no longer the
       ONLY field styled this way, just the first/original one.) */
    .ls-color-name-display {
        display: flex;
        flex-direction: row;
        align-items: baseline;
        gap: 4px;
        margin: 0px 0 6px;
        /* EDITED 8/27/2026 - wrap as whole chunks (Color half, Material
           half) instead of overflowing past the card width, now that the
           label/value spans below are locked to white-space:nowrap. */
        flex-wrap: wrap;
    }

    /* QC fix: plain text, not the small-caps uppercase label style used
       elsewhere - reads as "Color:" immediately followed by the value, not
       a separate all-caps field label above it. */
    /* Material reuses the exact same rule as Color's own label/value
       (added to these selector lists rather than duplicated as new rules)
       so the two halves are guaranteed to render identically at this
       breakpoint, matching Color's own font-size/weight/color exactly. */
    .ls-color-name-display .ls-color-name-label,
    .ls-color-name-display .ls-material-name-label {
        /* EDITED 8/27/2026 - 18px to 16px, matched to the Knit/Size pill
           text size for consistent typography across the card. */
        font-size: 16px;
        font-weight: 500;
        font-family: 'Playfair Display', serif;
        color: #686868;
    }

    .ls-color-name-display .ls-color-name-value,
    .ls-color-name-display .ls-material-name-value {
        /* EDITED 8/27/2026 - 18px to 16px, matched to the Knit/Size pill
           text size for consistent typography across the card. */
        font-size: 16px;
        font-weight: 600;
        font-family: 'Playfair Display', serif;
        color: #686868;
        /* EDITED 8/27/2026 - hyphenated values (e.g. "Off-White",
           "Acrylic-Wool") were breaking mid-word right after the hyphen,
           a valid default CSS line-break point. white-space:nowrap keeps
           each value intact as one unit; the row above now wraps as whole
           Color/Material chunks instead when both don't fit on one line. */
        white-space: nowrap;
    }

    /* QC fix: "Choose Your Color" heading removed on mobile/tablet - no
       longer needed now that Color Name renders directly above this
       section. Scoped to ".ls-color-section-label" specifically (added
       alongside the shared ".ls-section-label" typography class in
       functions.php), NOT ".ls-section-label" itself, since that class is
       also used by "Our Packaging" above the box gallery - which must stay
       untouched at every width. */
    .ls-color-section-label {
        display: none;
    }

    /* ============================================
       "CLEAR OPTIONS": removed on mobile/tablet
       ============================================
       The Knit/Size selectors become pill-style buttons in a later job,
       which always require an explicit choice with no default state left
       to "clear" back to - this link already leads nowhere useful on
       mobile, so it's hidden now instead of sitting in a broken/pointless
       state until that job lands. !important beats the unconditional
       "display: block" this link gets further above (every breakpoint);
       display:none removes its own padding/margin too, so no empty gap is
       left behind regardless of where the wp_footer script has positioned
       it. Desktop/tablet (1000px+) keep the button exactly as it is today -
       this rule only exists inside this max-width:999px block. */
    .entry-summary-items .reset_variations {
        display: none !important;
    }

    /* QC fix: the duplicate Price display that used to live here (under
       Knit) was removed - see functions.php, search "QC fix" near "Mobile
       layout reorder", for why. The single, original price display already
       renders in its native, correct spot (".single_variation_wrap
       .woocommerce-variation-price", positioned via the "show only ONE
       price" note further above), directly before Quantity/Add to
       Cart/Buy Now - see the next section for how IT is styled now. */

    /* ============================================
       PRICE + QUANTITY + BUTTONS: ONE COMBINED SECTION (Job 3, Task 4)
       ============================================
       Below 1000px only - Price switches to the same inline "Label: Value"
       row every other field on mobile now uses (see Color Name above and
       KNIT / SIZE CURRENT-VALUE ROW), and its own divider is removed so
       Price, the Quantity stepper, Add to Cart, and Buy Now read as one
       continuous section instead of stacked blocks separated by rules.
       UPDATE (Job G): this comment used to say desktop/tablet kept the
       shared base ".price" rule's divider "exactly as before this job" -
       no longer true. Job G (search style.css for "Job G", scoped
       min-width:1000px) later removed that same divider at desktop too,
       for the same "one combined section" goal, per a separate later
       request. The two jobs don't conflict (this block only ever applies
       below 1000px, Job G's only at 1000px+), but this row's own stacked-
       vs-inline layout (flex-direction, gap, etc.) is still genuinely
       different per breakpoint - only "does Price have its own divider"
       changed to be the same (no) at every width now. */
    .entry-summary-items .price {
        flex-direction: row;
        flex-wrap: wrap;
        align-items: baseline;
        gap: 5px;
        /* EDITED 8/27/2026 - !important added to all three: confirmed live
           (via a console scan of every matching CSS rule) that this rule
           was losing a specificity tie against the unconditional base
           ".entry-summary-items .price" rule further up this file (search
           "Divider below price, separating it from quantity/add-to-cart"),
           which sets padding-bottom/margin-bottom/border-bottom for a
           divider this mobile rule is specifically meant to remove. Same
           selector, same specificity, tie broken by source order - the
           base rule apparently sits later in the file, so it was
           silently winning all three properties, not just margin-bottom.
           !important forces this override to win as originally intended. */
        padding-bottom: 0 !important;
        /* 16px -> 6px (2026-08-27): measured live, the rendered gap down
           to the "QUANTITY" label was 20px at 16px margin (a ~4px
           baseline/line-height offset beyond the margin itself), so 6px
           targets the requested ~10px rendered gap (half of the original
           20px). May need a small follow-up nudge once checked live. */
        margin-bottom: 6px !important;
        border-bottom: none !important;
    }

    /* "Price" -> "Price:" - same inline "Label: Value" punctuation used for
       Size/Knit above. Only the generated text changes here; font-size/
       weight/color/etc. are inherited unchanged from the shared
       ".price::before" rule above (PURCHASE-INFO CARD STYLING). */
    .entry-summary-items .price::before {
        content: "Price:";
    }

    /* ============================================
       "BUY NOW" (see functions.php, search "ls-buy-now-button")
       ============================================
       Job 6: Add to Cart and Buy Now each get their own full-width row,
       stacked vertically, instead of sharing one row side by side - see
       the approved wireframe. This used to set "flex-direction: row" here
       to split the row evenly between the two buttons, but that gave
       WooCommerce's native "View cart" link (inserted by Blocksy's AJAX
       add-to-cart between Add to Cart and Buy Now once an item's been
       added - see functions.php, "VIEW CART" reposition script) nowhere
       good to go, wedging itself between both buttons and crowding them.
       Job 6 moves "View cart" up next to the Quantity stepper instead
       (search "ls-quantity-row" below), which only works cleanly if this
       row is a plain vertical stack to begin with - no left/right split
       left needing a third slot. flex-direction:column here now matches
       the BASE (unconditional, desktop-safe) ".ls-cart-buttons-row" rule
       further up this file - this override only needs to restate "gap"
       now, everything else already matches. */
    .ls-cart-buttons-row {
        flex-direction: column;
        gap: 12px;
    }

    /* Add to Cart's mobile-only "share a row, auto width" override (was
       "width: auto; flex: 1 1 0;") is gone - the unconditional base rule
       further up this file (".entry-summary-items .single_add_to_cart_button
       { width: 100% }") already gives it its own full-width row now that
       ".ls-cart-buttons-row" stacks vertically again, so no mobile-specific
       override is needed here at all. */

    /* Same ".ls-cart-buttons-row" prefix + !important as the hidden-by-
       default rule above, and for the same reason (see that rule's
       comment) - Blocksy's own core "[type=\"submit\"]" button reset sets
       display too, at equal specificity to a lone class selector. */
    .ls-cart-buttons-row .ls-buy-now-button {
        display: flex !important;
        align-items: center;
        justify-content: center;
        /* Job 6: "flex: 1 1 0" (a main-axis sizing rule, only meaningful
           when sharing a ROW with Add to Cart) removed - now that
           ".ls-cart-buttons-row" stacks its children in a COLUMN again,
           this button gets its own full-width row for free via the
           column's default "align-items: stretch" (no explicit width
           needed here), exactly like Add to Cart's row above it. */
        padding: 5px 20px;
        border-radius: 3px;
        font-size: 15px;
        font-weight: 500;
        /* Matches Add to Cart's own current brand-blue button color
           (verified directly on the live button), just inverted into an
           outline instead of a solid fill - the usual "secondary action
           next to the primary one" pattern. */
        border: 2px solid #2872fa;
        background-color: transparent;
        color: #2872fa;
        cursor: pointer;
    }

    .ls-buy-now-button:hover {
        background-color: rgba(40, 114, 250, 0.08);
    }

    /* ============================================
       "VIEW CART" (Job 6 - see functions.php, search "ls-quantity-row")
       ============================================
       Once an item's been added to cart, WooCommerce's native "View cart"
       link gets moved (mobile-only - see the matching JS) out of
       ".ls-cart-buttons-row" and into this wrapper instead, as a sibling
       of the Quantity stepper - ".ls-quantity-row" wraps ONLY the stepper
       at first (so it renders identically to "just the stepper alone" per
       the wireframe's "Before" state, with nothing else needed here for
       that case), and gains "View cart" as a second child once it exists.
       A plain flex row, centered so the link's own text sits level with
       the stepper regardless of the stepper's fixed 55px height. */
    .ls-quantity-row {
        display: flex;
        align-items: center;
        gap: 12px;
    }

    /* ============================================
       PHONE SUB-BREAKPOINT (Job 3, Task 3 - max-width: 600px)
       ============================================
       Nested inside the existing max-width:999px block above (same
       "everything here is impossible to leak past its own boundary"
       convention that block's own top comment already documents) rather
       than a second flat @media rule, since every part of this is a
       further refinement of the mobile/tablet rules already in force here,
       not a separate concern of its own. Tablet width (601-999px,
       confirmed fine as-is) is completely unaffected by anything below -
       only phone widths (confirmed cramped at 430px) get different
       treatment. Deliberately does NOT touch the purchase-info card's own
       background/border color ("container background/edge treatment" stays
       exactly as it already is at every width below 1000px, per this
       task's own scope note) - only the content INSIDE it (info cards, box
       gallery thumbnails, the Knit/Size selector/photo columns) gets more
       of the available width. */
    @media (max-width: 600px) {
        /* Color cards: 3-per-row instead of 4 (see the split
           ".ls-color-display-copy .variable-item" / ".ls-box-item" rules
           above, just above COLOR INFO CARDS) - fewer items sharing the
           row's width means each card is meaningfully wider, fixing the
           skinny/cramped read confirmed at 430px. The row's own width
           formula (100%, set above) and the existing scroll/swipe/
           arrow-button JS (untouched) still reveal the rest exactly as
           before - only how many are visible at once without scrolling
           changes. Gap (14px, set above) is unchanged; only the divisor
           here drops from 4 to 3 to match. */
        .ls-color-display-copy .variable-item {
            flex: 0 0 calc((100% - (14px * 2)) / 3);
        }

        /* Box Gallery: same 3-per-row treatment, its own 12px gap
           (unchanged, see BOX GALLERY above) kept as the formula's
           divisor. */
        .ls-box-item {
            flex: 0 0 calc((100% - (12px * 2)) / 3);
        }

        /* Reference photos (Knit/Size), phone tier: same /3 divisor as
           .ls-box-item just above, mirroring its own change at this
           breakpoint so the two stay in sync here too, not just at the
           tablet tier. */
        .ls-lightbox-trigger {
            flex: 0 0 calc((100% - (12px * 2)) / 3);
        }

        /* Same Size-row specificity override as the tablet tier above,
           repeated here for the phone tier's /3 divisor. */
        ul[data-attribute_name="attribute_pa_size"]~.ls-photos-and-description .ls-lightbox-trigger {
            flex: 0 0 calc((100% - (12px * 2)) / 3);
        }

        /* Purchase-info card: tighter side padding frees up extra width for
           its inner content (pills, description text, the Price/Quantity
           row) without touching the card's own background/border - those
           stay exactly as they are at every width, per this task's scope
           note. */
        .summary.entry-summary-items {
            padding: 16px;
        }

        /* Knit/Size selector column and reference-photo(s) column: lower
           minimum widths than the 999px-block's own (130px / 160px above)
           so both can actually use the extra space the tighter card
           padding just freed up, instead of leaving it unused. */
        .entry-summary-items table.variations td.value ul[data-attribute_name] {
            min-width: 110px;
        }

        .entry-summary-items table.variations td.value .ls-photos-and-description {
            min-width: 140px;
        }
    }

    /* ============================================
       RETIRED (2026-08-25): TABLET SUB-BREAKPOINT (Job 8 - min-width: 601px)
       ============================================
       Used to split each Size/Knit row into two side-by-side columns at
       tablet widths only (label+pills on the left, heading+photo(s)+
       description on the right) - a tablet-specific layout that predates
       the desktop restructure and was never brought in line with it.
       Collapsing the label/pills relationship into one inline row (see the
       base grid rule above) made that inconsistency clear: this tablet-
       only split put the label on the same row as the heading and the
       pills in a cell spanning down next to the photos, a genuinely
       different arrangement from the single stacked column desktop and
       phone both use. Retired so all 3 breakpoints share the exact same
       structure (label+pills inline, then heading, then photo(s), then
       description, every row full-width) - confirmed by request. Tablet's
       reference photo(s) now fall back to the same fluid single-column
       width phone already used at this max-width:999px tier (the 3fr/5fr
       split above existed specifically to give them extra width) - flagged
       as a follow-up to revisit if that reads too narrow at tablet widths,
       out of scope for this fix. */
}