/* ==========================================================================
   Product Pages — threadandfilm theme
   ============================================================================
   The single-product page's rules, and only those. inc/assets.php loads this
   sheet only when is_product(), right after woocommerce.css, so responsive.css
   still loads last, and no other page downloads it (theme 0.12.1; until then
   these rules sat in woocommerce.css and responsive.css on every page).

   The sections keep the numbers they had in woocommerce.css, so a "§3b" names
   one place in either file:
   Breadcrumb
   2. Variations Form (variable products)
   2a. The length picker
   3. Single Product Layout
   3a. Summary head
   3b. Purchase card
   3c. The gang-sheet purchase card
   4. Product Tabs
   4a. Description blocks
   4b. Callouts in the tab panels
   6. TAF FileDrop upload card (theme layer)
   7. Added-to-cart drawer
   Phones and narrow widths: the rules responsive.css held for these pages

   woocommerce.css keeps what other pages share: the listing pages and the
   product card (§1), the callout notes' base construction, which the homepage's
   How to apply uses too, star ratings (§5), and the cart, checkout and account
   (§8 to §17).
   ========================================================================== */


/* ── Breadcrumb ──
   WooCommerce's breadcrumb, which only the product pages print (woocommerce.css
   hides it anywhere else). It shows above both columns (site redesign Phase 3b,
   spec §5.1): 13px in muted ink, with WooCommerce's markup, its "/" separators and
   its one BreadcrumbList left as they are. The mobile twin hides it again (the
   phone rules at the end of this file, Mobile.dc.html M3). */
.single-product .woocommerce-breadcrumb {
    margin: 0 0 24px;
    font-size: 13px;
    line-height: 1.5;
    color: var(--color-text-light);
}

.single-product .woocommerce-breadcrumb a {
    color: inherit;
    text-decoration: none;
}

.single-product .woocommerce-breadcrumb a:hover {
    color: var(--color-text);
    text-decoration: underline;
}

.single-product .woocommerce-breadcrumb a:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}


/* ── 2. Variations Form ── */

/* WooCommerce renders variations as a <table>. We convert to stacked blocks
   so the label sits above the select, which works better on mobile and
   matches the rest of our form styling. */
.woocommerce div.product .variations {
    display: block;
    margin-bottom: 1rem;
}
.woocommerce div.product .variations tbody,
.woocommerce div.product .variations tr {
    display: block;
}
.woocommerce div.product .variations td {
    display: block;
    padding: 0;
}

/* Label row — sits above the select dropdown */
.woocommerce div.product .variations td.label {
    padding-bottom: 6px;
}
.woocommerce div.product .variations td.label label {
    font-size: 0.9rem;
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
}

/* Value row — one non-wrapping row: [ select ] [ ↺ Clear ].
   WooCommerce already emits the reset link as the select's next sibling, so
   the row needs no template override. The 10px gap is a one-off inline
   dimension, not a reusable spacing step, so it stays a literal. */
.woocommerce div.product .variations td.value {
    display: flex;
    align-items: center;
    flex-wrap: nowrap;
    gap: 10px;
    margin-bottom: 4px;
}

/* Custom styled select — replaces WooCommerce's unstyled native dropdown.
   The SVG chevron background-image gives a consistent arrow across browsers
   without needing a wrapper div or JS.
   min-width: 0 is load-bearing: a flex item will not shrink below its
   intrinsic content width without it, so the longest option ("22.5in x
   360in") would push the reset pill off-screen on narrow viewports instead
   of the select giving up width. */
.woocommerce div.product .variations select {
    flex: 1 1 auto;
    min-width: 0;
    width: 100%;
    max-width: 320px;
    min-height: 44px;            /* a 44px hit area (Phase 3b): the purchase control wherever the length picker does not run */
    padding: 10px 36px 10px 14px;
    border: 1px solid var(--color-border-light);
    border-radius: var(--radius);
    font-family: var(--font-body);
    font-size: 0.95rem;
    background: var(--color-bg);
    -webkit-appearance: none;
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23666' stroke-width='1.5' fill='none'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
    cursor: pointer;
    box-sizing: border-box;
}
.woocommerce div.product .variations select:focus {
    border-color: var(--color-primary);
}
/* Focus ring, shared verbatim with the reset pill so tabbing across the pair
   shows one consistent affordance.
   ONE ring, not two: the border returns to its resting colour instead of
   staying cyan underneath the outline. 0.9.78 shipped both at once and the
   result was a cyan inner ring inside a near-black outer ring — two competing
   treatments on one control.
   --color-text-light is 5.74:1 on white, well over the 3:1 minimum for a focus
   indicator, and softer than the --color-text ring 0.9.78 used.
   The cyan `:focus` rule above is NOT dead code: Chromium always matches
   :focus-visible on form controls, but where a browser does not, that border
   stays the only focus signal. This rule follows it deliberately — equal
   specificity, so the later one wins. Theme 0.9.85 removed the `outline: none`
   that used to sit in the rule above; it was already being overridden here,
   and no front-end stylesheet suppresses an outline any more. */
.woocommerce div.product .variations select:focus-visible {
    border-color: var(--color-border-light);
    outline: 2px solid var(--color-text-light);
    outline-offset: 2px;
}

/* "Clear" / reset link — a compact ghost pill beside the select. Fill,
   hairline and radius deliberately match the quantity input below it, so the
   two controls read as one family. The faint 1.19:1 border is fine here: the
   visible "Clear" label identifies the control, not its boundary.
   WooCommerce HIDES this link with `visibility` (`toggleResetLink`), so its
   box is kept and the row keeps its shape. The REVEAL path is different and
   does touch display: `.css('visibility','visible').hide().fadeIn()`. jQuery
   restores the stylesheet value inside the same task, so no frame ever renders
   the collapsed row — sampled across 56 animation frames of the reveal at 1440
   and 390px, `display: none` never appeared and the select never moved. That
   is what makes an inline row safe; do not restate it as "visibility, never
   display", which is only half true.
   Never colour the label with --color-primary: brand cyan on the muted fill
   is 2.32:1, well under the 4.5:1 small-text minimum. Hover darkens the text
   and border instead (14.6:1 and 5.74:1). */
.woocommerce div.product .variations .reset_variations {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    flex: 0 0 auto;
    padding: 7px 14px;
    border: 1px solid var(--color-border-light);
    border-radius: var(--radius-pill);
    background: var(--color-bg-muted);
    font-size: 0.8rem;
    font-weight: var(--font-weight-medium);
    line-height: 1;
    color: var(--color-text-light);
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    position: relative;          /* anchors the ::after hit area below (Phase 3b) */
    transition: color var(--ease-out-fast), border-color var(--ease-out-fast),
                background-color var(--ease-out-fast);
}
/* ↺ as its own flex child, spaced by the pill's gap. The previous
   `content: '\21BA '` leaned on a trailing space inside a CSS string, which
   does not space like markup whitespace — it rendered as "↺Clear". */
.woocommerce div.product .variations .reset_variations::before {
    content: '\21BA';
    font-size: 0.95rem;
    line-height: 1;
}
/* The pill is drawn about 31px tall; a transparent ::after reaches the 44 × 44px hit
   area every control needs (Phase 3b), as the length picker's Clear and chips do. It
   reaches up and down only: the pill is already wider than 44px, and the dropdown
   10px to its left keeps a hit area of its own. The pill shows only once a length is
   chosen, so PL-13 chooses one before it hit-tests. */
.woocommerce div.product .variations .reset_variations::after {
    content: '';
    position: absolute;
    inset: -8px 0;
}
.woocommerce div.product .variations .reset_variations:hover,
.woocommerce div.product .variations .reset_variations:focus-visible {
    color: var(--color-text);
    border-color: var(--color-text-light);
    background: var(--color-border-light);
}
/* Same ring as the select above, deliberately identical — a keyboard user
   tabbing from the dropdown to this pill should see the affordance stay put.
   The border is reset to its resting colour here, overriding the hover rule
   above: a darkened border sitting inside the outline reads as a double ring,
   the same defect the cyan border caused on the select in 0.9.78. Never the
   cyan token — #00AEEF is 2.53:1 on white, under the 3:1 minimum. */
.woocommerce div.product .variations .reset_variations:focus-visible {
    border-color: var(--color-border-light);
    outline: 2px solid var(--color-text-light);
    outline-offset: 2px;
}

/* Selected variation price — shown after user picks an option */
.woocommerce div.product .woocommerce-variation-price .price {
    font-size: 1.35rem;
    font-weight: var(--font-weight-bold);
    margin: 16px 0 12px;
}

/* Quantity input — pill-shaped with muted background.
   Aestheta pattern: 20px radius, 84px wide, #F5F5F5 bg. 44px tall since Phase 3b
   (it was 38px): an input has no pseudo-element to extend, so its own box is the 44px
   hit area every control needs wherever the gang sheets' stepper does not run. */
.woocommerce div.product form.cart .quantity {
    display: block;
    margin-bottom: 16px;
}
/* Pseudo-element "Quantity" label — avoids modifying WooCommerce templates */
.woocommerce div.product form.cart .quantity::before {
    content: 'Quantity';
    display: block;
    font-size: 17px;
    font-weight: var(--font-weight-medium);
    text-transform: uppercase;
    color: var(--color-text);
    margin-bottom: 8px;
}
.woocommerce div.product form.cart .quantity input {
    width: 84px;
    height: 44px;
    padding: 0 12px;
    border: 1px solid var(--color-border-light);
    border-radius: var(--radius-pill);
    background: var(--color-bg-muted);
    text-align: center;
    font-family: var(--font-body);
    font-size: 16px;
    font-weight: var(--font-weight-normal);
    box-sizing: border-box;
    transition: border-color var(--ease-out-fast);
}
.woocommerce div.product form.cart .quantity input:focus {
    border-color: var(--color-primary);          /* unchanged, all input modalities */
}
/* Ring per the focus convention. This field's only cue was a border moving
   #ebebeb -> #00AEEF: 2.32:1 against the white field, under 3:1. */
.woocommerce div.product form.cart .quantity input:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}

/* ── 2a. The length picker (site redesign Phase 3b) ──
   Fixed gang sheets (spec §5.4, design/Gang Sheet.dc.html). template-parts/product/
   picker.php prints div.taf-picker hidden, then assets/js/sheet-picker.js inline,
   which builds it as the page is parsed, before the first paint (theme 0.12.1), and
   shows it only when it can read every size (D4). While it runs, the form carries
   .taf-picker-on, which hides WooCommerce's variations table and its variation price:
   the readout and the summary's price line carry the price (A8). Without the picker,
   the table in section 2 is the purchase control, as it is without JavaScript.

   Every control keeps a 44 x 44px hit area: the round − and + and the range are drawn
   at 44px; Clear and the chips, drawn at 36px, take the rest from a transparent
   ::after. Nothing that holds a control clips (the bar's overflow holds none), and
   the chip row's 8px gap is exactly what keeps neighbouring hit areas from
   overlapping, so it must not shrink. */

.woocommerce div.product form.taf-picker-on table.variations,
.woocommerce div.product form.taf-picker-on .woocommerce-variation-price {
    display: none;
}

/* [hidden] must beat the display values below: the container stays hidden until the
   picker starts, and Clear while no length is chosen. */
.taf-picker[hidden],
.taf-picker-clear[hidden] {
    display: none;
}

.taf-picker {
    display: flex;
    flex-direction: column;
    gap: 16px;
    margin-bottom: 16px;
    padding: 18px;
    border-radius: var(--radius-tile);
    background: var(--color-bg-muted);
}

/* The label row is as tall as Clear, so it does not grow when Clear appears. */
.taf-picker-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    min-height: 36px;
}

.taf-picker-label {
    font-size: 12px;
    font-weight: var(--font-weight-bold);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--color-text);
}

/* The required mark, in the magenta that meets text (6.3:1 on the panel). Screen
   readers skip it and read the label alone. */
.taf-picker-req {
    margin-left: 0.25em;
    color: var(--color-accent-text);
}

.taf-picker-clear {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 36px;
    padding: 0 12px;
    border: 1px solid var(--color-border-mid);
    border-radius: var(--radius-full);
    background: var(--color-bg);
    font-family: var(--font-body);
    font-size: 12.5px;
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
    cursor: pointer;
    transition: border-color var(--ease-out-fast);
}

/* The × before "Clear": a mask in the text colour, so no icon markup. */
.taf-picker-clear::before {
    content: '';
    width: 11px;
    height: 11px;
    background-color: currentColor;
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'/%3E%3Cline x1='6' y1='6' x2='18' y2='18'/%3E%3C/svg%3E") center / contain no-repeat;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'/%3E%3Cline x1='6' y1='6' x2='18' y2='18'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* Drawn 36px tall with a 1px border. The ::after sits in the padding box, so 5px of
   transparent hit area above and below makes 44, and the 4px it reaches past each side
   edge meets the neighbouring chip's across the 8px gap without overlapping. */
.taf-picker-clear::after,
.taf-picker-chip::after {
    content: '';
    position: absolute;
    inset: -5px;
}

.taf-picker-clear:hover,
.taf-picker-chip:hover,
.taf-picker-dec:hover,
.taf-picker-inc:hover {
    border-color: var(--color-text);
}

.taf-picker-readout {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 6px;
}

/* The mock's 800 renders at 700: Inter is self-hosted from 400 to 700 (3a ruling M9). */
.taf-picker-big {
    font-size: 40px;
    line-height: 1;
    font-weight: var(--font-weight-bold);
    letter-spacing: -0.03em;
    font-variant-numeric: tabular-nums;
    color: var(--color-text);
}

/* The unset dash in the muted ink, 5.27:1 on the panel, not the mock's #B3B3B3, which
   measures 1.9:1 (D3). */
.taf-picker-big.is-unset {
    color: var(--color-text-light);
}

.taf-picker-unit {
    font-size: 15px;
    color: var(--color-text-light);
}

.taf-picker-meta {
    margin-left: auto;
    font-size: 14px;
    font-variant-numeric: tabular-nums;
    color: var(--color-text-light);
}

/* The ruler: a fill at the chosen length over the longest, a tick every 12px. The
   lighter cyan in the gradient is the mock's tick colour. */
.taf-picker-bar {
    position: relative;
    height: 28px;
    border: 1px solid var(--color-border-light);
    border-radius: var(--radius);
    background: var(--color-bg);
    overflow: hidden;
}

.taf-picker-fill {
    position: absolute;
    inset: 0 auto 0 0;
    width: 0;
    background: repeating-linear-gradient(90deg, var(--color-primary) 0 10px, #33BFF2 10px 12px);
    transition: width var(--ease-panel);
}

.taf-picker-controls {
    display: flex;
    align-items: center;
    gap: 12px;
}

.taf-picker-dec,
.taf-picker-inc {
    flex: none;
    width: 44px;
    height: 44px;
    border: 1px solid var(--color-border-mid);
    border-radius: var(--radius-full);
    background: var(--color-bg);
    font-family: var(--font-body);
    font-size: 20px;
    line-height: 1;
    color: var(--color-text);
    cursor: pointer;
    transition: border-color var(--ease-out-fast);
}

/* A native range: its box is the 44px hit area and the browser draws the track
   thinner inside it. */
.taf-picker-range {
    flex: 1;
    min-width: 0;
    height: 44px;
    accent-color: var(--color-text);
    cursor: pointer;
}

.taf-picker-popular {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
}

.taf-picker-popular-label {
    font-size: 12.5px;
    color: var(--color-text-light);
}

.taf-picker-chip {
    position: relative;
    height: 36px;
    padding: 0 12px;
    border: 1px solid var(--color-border-mid);
    border-radius: var(--radius-full);
    background: var(--color-bg);
    font-family: var(--font-body);
    font-size: 13px;
    font-weight: var(--font-weight-semibold);
    font-variant-numeric: tabular-nums;
    color: var(--color-text);
    cursor: pointer;
    transition: border-color var(--ease-out-fast), background-color var(--ease-out-fast), color var(--ease-out-fast);
}

.taf-picker-chip[aria-pressed="true"] {
    border-color: var(--color-text);
    background: var(--color-text);
    color: var(--color-bg);
}

.taf-picker button:focus-visible,
.taf-picker-range:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
    .taf-picker-fill {
        transition: none;
    }
}


/* ── 3. Single Product Layout ── */

/* The product pages (site redesign Phase 3b, spec §5.1): design/Product.dc.html and
   design/Gang Sheet.dc.html from 769px, design/Mobile.dc.html M3 on the mobile twin
   (below). Spec: 2026-10-07-shop-and-product-pages-design.md in the
   site-redesign folder under .plans/. WooCommerce's own templates print the page;
   the hooks in inc/woocommerce.php add the type pill, the price line, the purchase
   card and the Description tab, and inc/product-data.php resolves every figure they
   state. Horizontal edges come from .taf-container (--shell-gutter, capped at
   --content-max), not from the mocks' 56px frame padding.

   div.product is one column of two blocks: div.taf-product-top (inc/woocommerce.php),
   which holds the gallery and the summary, then the tabs. Below 1024px the gallery
   and the summary stack too. The uploader's rows need more than half of a narrow
   page, so the columns wait for 1024px rather than the mock's wrap near 850px
   (spec §6). */
.woocommerce div.product {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    row-gap: var(--space-md);
    margin-bottom: var(--space-xl);
}

.woocommerce div.product .taf-product-top {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    row-gap: var(--space-md);
    align-items: start;
}

/* The gallery is WooCommerce's own (FlexSlider, zoom and the lightbox), restyled. It
   is never wider than --product-gallery-max, stacked or not, so the band from 769 to
   1023px does not draw a photo as tall as the screen. This rule must come before the
   1024px block below: both weigh the same, so the later one's sticky wins there. */
.woocommerce div.product div.images {
    position: relative;     /* Holds the zoom trigger; from 1024px, sticky holds it */
    width: 100%;
    max-width: var(--product-gallery-max);
    /* The thumbnail strip's grid, read by the strip and by the reservation that holds
       its place before FlexSlider starts (below), so the two cannot drift apart. */
    --taf-thumbs-columns: repeat(4, minmax(0, 1fr));
    --taf-thumbs-gap: 10px;
    --taf-thumbs-offset: 12px;      /* The strip's distance under the photo */
}

/* Two columns from 1024px, 36px apart. The mock sizes the pair as flex items, the
   gallery flex: 1 1 300px capped at 560px and the summary flex: 1 1 400px, which grow
   alike, so the gallery runs 50px short of half the row until it meets its cap. The
   first track is that rule written as a grid column: about 412px at 1024 and 541px
   at 1440 in the theme's shell, never past --product-gallery-max. The pair has a grid
   of its own, div.taf-product-top, because a sticky element stays inside its
   containing block, and for a grid item that is the whole grid container, not its
   grid area (measured in Chrome 154). On div.product's own grid the gallery rode on
   over the tabs; here it stops where the summary ends. */
@media (min-width: 1024px) {
    .woocommerce div.product {
        row-gap: var(--space-xl);
    }

    .woocommerce div.product .taf-product-top {
        grid-template-columns: minmax(0, min(var(--product-gallery-max), calc((100% - var(--space-md)) / 2 - 50px))) minmax(0, 1fr);
        column-gap: var(--space-md);
        row-gap: var(--space-xl);
    }

    /* 21px under the sticky header, as the mock's top: 196px sits under its 175px
       header. --header-h is the header's measured height (base.css). */
    .woocommerce div.product div.images {
        position: sticky;
        top: calc(var(--header-h) + 21px);
    }
}

/* The main image: square, with 20px corners and a 1px frame. Each slide clips to the
   same corners, because zoom lays a larger copy (.zoomImg) over the slide on hover,
   and only the slide's clip keeps that copy inside them. */
.woocommerce div.product .woocommerce-product-gallery__image {
    border-radius: var(--radius-lg);
    overflow: hidden;
}

.woocommerce div.product .woocommerce-product-gallery__image > a > img {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 1;
    object-fit: cover;
    border: 1px solid var(--color-border-light);
    border-radius: var(--radius-lg);
}

/* The thumbnails: FlexSlider's control nav as one row of four square tiles, 12px
   under the photo. The current one is framed 2px in ink, the mock's construction;
   the tile keeps its size because a border sits inside the box. */
.woocommerce div.product div.images .flex-control-thumbs {
    display: grid;
    grid-template-columns: var(--taf-thumbs-columns);
    gap: var(--taf-thumbs-gap);
    margin: var(--taf-thumbs-offset) 0 0;
    padding: 0;
    list-style: none;
}

.woocommerce div.product div.images .flex-control-thumbs li {
    margin: 0;
}

.woocommerce div.product div.images .flex-control-thumbs img {
    display: block;
    width: 100%;
    height: auto;
    object-fit: cover;
    border: 1px solid var(--color-border-light);
    border-radius: var(--radius-md);
    background: var(--color-bg-alt);
    cursor: pointer;
    transition: border-color var(--ease-out-fast);
}

.woocommerce div.product div.images .flex-control-thumbs img:hover {
    border-color: var(--color-text-light);
}

.woocommerce div.product div.images .flex-control-thumbs img.flex-active {
    border: 2px solid var(--color-text);
}

/* The layout shift (spec §5.6, as amended by the owner on 2026-10-08; TODO.md).
   WooCommerce's stylesheets are off (inc/assets.php), and FlexSlider starts in two
   steps: one task moves the wrapper into .flex-viewport, widens it to 200% per slide
   and appends the thumbnail strip; 100ms later it writes each slide's width, float
   and display: block inline. Without these rules the images stack at full width in
   between, and the summary moves by 0.31 of the screen on phones. So the gallery
   keeps one height through both steps:
   - the second and later images stay hidden at every stage until FlexSlider's own
     inline display: block shows them, already floated and sized;
   - during the 100ms, the first image takes the viewport's width (100cqi), the value
     FlexSlider writes next, not 200% per slide;
   - the strip's thumbnails are square before their images load;
   - before FlexSlider starts, the wrapper reserves the strip's rows with the strip's
     own grid (below).
   Proven first as a control injected into a production copy
   (reference/preview/cls-control-v2.html, pp-ab.mjs --check=shift). The first rule
   relies on FlexSlider writing display: block inline, so after a WooCommerce update
   check that a thumbnail click still shows its image. */
.woocommerce div.product div.images .woocommerce-product-gallery__image:nth-child(n+2) {
    display: none;
}

.woocommerce div.product div.images .flex-viewport {
    container-type: inline-size;
}

.woocommerce div.product div.images .flex-viewport .woocommerce-product-gallery__image {
    width: 100cqi;
}

.woocommerce div.product div.images .flex-control-thumbs img {
    aspect-ratio: 1;
}

/* The reservation. Until FlexSlider starts, the wrapper is the gallery's direct child
   and takes the strip's grid: the first image across the row, then one empty square
   per image (the later images, their content hidden, and one ::after), so it holds as
   many rows as the strip FlexSlider appends. The child combinator stops matching in
   the same task that appends the strip, so no frame shows neither or both. The :has()
   keeps a one-image gallery, which FlexSlider never starts, from reserving a strip it
   never gets; a browser without :has() drops these rules and shows the strip shift
   alone. Phones show no strip, so the mobile twin turns this off (below). */
.woocommerce div.product div.images > .woocommerce-product-gallery__wrapper:has(> .woocommerce-product-gallery__image ~ .woocommerce-product-gallery__image) {
    display: grid;
    grid-template-columns: var(--taf-thumbs-columns);
    gap: var(--taf-thumbs-gap);
}

/* The first image spans the row, as far above the squares as the strip sits under
   the photo: the grid's gap, plus the difference in a margin. */
.woocommerce div.product div.images > .woocommerce-product-gallery__wrapper:has(> .woocommerce-product-gallery__image ~ .woocommerce-product-gallery__image) > .woocommerce-product-gallery__image:first-child {
    grid-column: 1 / -1;
    margin-bottom: calc(var(--taf-thumbs-offset) - var(--taf-thumbs-gap));
}

.woocommerce div.product div.images > .woocommerce-product-gallery__wrapper:has(> .woocommerce-product-gallery__image ~ .woocommerce-product-gallery__image) > .woocommerce-product-gallery__image:nth-child(n+2),
.woocommerce div.product div.images > .woocommerce-product-gallery__wrapper:has(> .woocommerce-product-gallery__image ~ .woocommerce-product-gallery__image)::after {
    display: block;
    aspect-ratio: 1;
}

.woocommerce div.product div.images > .woocommerce-product-gallery__wrapper:has(> .woocommerce-product-gallery__image ~ .woocommerce-product-gallery__image)::after {
    content: "";
}

/* The squares stay empty, so the later images' lazy images are not fetched early. */
.woocommerce div.product div.images > .woocommerce-product-gallery__wrapper > .woocommerce-product-gallery__image:nth-child(n+2) > * {
    display: none;
}

/* WooCommerce's zoom trigger, which its script injects and which opens the lightbox:
   a 44px white disc whose whole box is the hit area. The magnifier is drawn by the
   two pseudo-elements; font-size: 0 hides the emoji WooCommerce puts inside. */
.woocommerce div.product div.images .woocommerce-product-gallery__trigger {
    position: absolute;
    top: 12px;
    right: 12px;
    z-index: 5;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.9);
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
    font-size: 0;
    text-decoration: none;
    transition: background var(--ease-out-fast);
}

/* The lens */
.woocommerce div.product div.images .woocommerce-product-gallery__trigger::before {
    content: '';
    display: block;
    width: 16px;
    height: 16px;
    border: 2px solid var(--color-text);
    border-radius: 50%;
}

/* The handle */
.woocommerce div.product div.images .woocommerce-product-gallery__trigger::after {
    content: '';
    position: absolute;
    right: 12px;
    bottom: 12px;
    display: block;
    width: 2px;
    height: 6px;
    background: var(--color-text);
    transform: rotate(-45deg);
}

.woocommerce div.product div.images .woocommerce-product-gallery__trigger:hover {
    background: var(--color-bg);
}

.woocommerce div.product div.images .woocommerce-product-gallery__trigger:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}

/* ── 3a. Summary head ──
   The type pill, the h1, the price line and the short description, 10px apart, then
   the purchase card 22px under them: the mock's two column gaps. The mocks' weight
   800 is set at 700, the heaviest self-hosted Inter face. */
.woocommerce div.product div.summary {
    display: flex;
    flex-direction: column;
    gap: 10px;
    min-width: 0;
}

/* The type pill: the shop card's label and tone (taf_loop_card()) in a class of its
   own, because the card's .taf-loop-label is placed absolutely over the photo and
   hidden on phones. */
.taf-product-pill {
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    height: 26px;
    padding: 0 10px;
    border-radius: var(--radius-full);
    font-size: 12px;
    font-weight: var(--font-weight-bold);
    line-height: 1;
}

.taf-product-pill--upload {
    background: var(--label-upload-bg);
    color: var(--label-upload-text);
}

.taf-product-pill--builder {
    background: var(--label-builder-bg);
    color: var(--label-builder-text);
}

.woocommerce div.product .product_title {
    margin: 0;
    font-size: 40px;
    font-weight: var(--font-weight-bold);
    line-height: 1.08;
    letter-spacing: -0.03em;
    text-wrap: balance;
}

/* WooCommerce's own price, kept for a product of no known kind. */
.woocommerce div.product .price {
    margin: 0;
    font-size: 18px;
    font-weight: var(--font-weight-medium);
    color: var(--color-text);
}

.woocommerce div.product .price del {
    opacity: 0.5;
    font-size: 1.1rem;
}

.woocommerce div.product .price ins {
    text-decoration: none;
}

/* The price line (taf_product_price_head()): the figure, then the note in muted ink.
   A gang sheet's note is led by a "·" separator drawn here (below), so the note's own
   text stays the plain copy that the length picker restates and restores. Its empty
   alt text keeps a screen reader from reading it out; the first declaration is the
   fallback where alt text is not supported. */
.woocommerce div.product p.taf-price-line {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 8px;
    font-size: 15px;
    font-weight: var(--font-weight-normal);
    line-height: 1.5;
    color: var(--color-text-light);
}

.woocommerce div.product.taf-kind-upload p.taf-price-line {
    column-gap: 6px;
}

.taf-price-figure {
    flex-shrink: 0;
    font-size: 28px;
    font-weight: var(--font-weight-bold);
    line-height: 1.2;
    letter-spacing: -0.02em;
    color: var(--color-text);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

/* A gang sheet's separator sits in the gap before the note, outside the note's box:
   the gap is --taf-price-sep wide and the dot is centred in it. Where the note shares
   the figure's line, the dot shows between them. Where the note wraps to a line of its
   own (a fixed sheet's range on a phone), it starts flush with the line and its dot
   falls to the left of the line, where the horizontal clip hides it, so no line starts
   with a separator. This holds as the length picker rewrites the figure and the note,
   because the wrap is laid out afresh. Only the inline axis is clipped, so the glyphs
   keep their vertical overflow. A browser without clip (Safari before 16) keeps the
   hidden declared before it, so the dot is hidden there too; hidden also makes the
   vertical axis auto, and is allowed here only because the line holds no control. */
.woocommerce div.product.taf-kind-fixed p.taf-price-line,
.woocommerce div.product.taf-kind-rolling p.taf-price-line {
    --taf-price-sep: 20px;      /* The dot, with about 8px either side */
    column-gap: var(--taf-price-sep);
    overflow-x: hidden;         /* Where clip is unsupported (Safari before 16) */
    overflow-x: clip;
}

.taf-kind-fixed .taf-price-note,
.taf-kind-rolling .taf-price-note {
    position: relative;     /* Holds the separator */
}

.taf-kind-fixed .taf-price-note::before,
.taf-kind-rolling .taf-price-note::before {
    content: "·";
    content: "·" / "";
    position: absolute;
    top: 0;
    right: 100%;
    width: var(--taf-price-sep);
    text-align: center;
}

.woocommerce div.product .woocommerce-product-details__short-description {
    font-size: 16px;
    line-height: 1.6;
    color: var(--color-text-light);
    text-wrap: pretty;
}

.woocommerce div.product .woocommerce-product-details__short-description p + p {
    margin-top: 0.75em;
}

/* ── 3b. Purchase card ──
   The 20px-radius, 1px panel around the add-to-cart form (taf_purchase_open() and
   taf_purchase_close(), inc/woocommerce.php), 22px under the head. What sits inside
   is the product's own form: FileDrop's upload card, or a gang sheet's length,
   quantity and Build. */
.taf-purchase {
    margin-top: 12px;
    padding: 22px;
    border: 1px solid var(--color-border-light);
    border-radius: var(--radius-lg);
}

.woocommerce div.product .taf-purchase form.cart {
    margin: 0;
}

/* Add to cart, in the theme's action colours (0.9.85): dark ink on the brand cyan,
   an ink fill with white type on hover, and while disabled a grey fill with muted
   ink at full opacity (4.82:1, where 50% opacity failed contrast). 52px tall, the
   handoff's primary button. GSM hides it on the gang sheets, so FileDrop's is the
   only one the six products show. Wrapped in :where() so the rule weighs no more
   than the button's class, and anything that places the button, as FileDrop's upload
   card does, need not outrank it. */
:where(.woocommerce div.product form.cart) .single_add_to_cart_button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 52px;
    padding: 0 28px;
    border: 0;
    border-radius: var(--radius-full);
    background: var(--color-primary);
    color: var(--color-text);
    font-family: var(--font-body);
    font-size: 15px;
    font-weight: var(--font-weight-semibold);
    line-height: 1.2;
    text-transform: uppercase;
    letter-spacing: -0.01em;
    cursor: pointer;
    transition: background var(--ease-out-fast), color var(--ease-out-fast);
}

:where(.woocommerce div.product form.cart) .single_add_to_cart_button:not(:disabled):not(.disabled):hover {
    background: var(--color-text);
    color: var(--color-bg);
}

:where(.woocommerce div.product form.cart) .single_add_to_cart_button:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}

:where(.woocommerce div.product form.cart) .single_add_to_cart_button:disabled,
:where(.woocommerce div.product form.cart) .single_add_to_cart_button.disabled {
    background: var(--color-border-light);
    color: var(--color-text-light);
    opacity: 1;
    cursor: not-allowed;
}


/* ── 3c. The gang-sheet purchase card (site redesign Phase 3b) ──
   The four gang sheets (spec §5.4, design/Gang Sheet.dc.html): on fixed sheets the
   length picker (section 2a), then on every sheet the steps, the quantity and GSM's
   Build a Gang Sheet button. The parts are spaced by bottom margins, not a flex gap:
   WooCommerce keeps its .single_variation box between the picker and the steps,
   empty while the picker hides its price, and a gap would count it. */

/* The steps (taf_sheet_steps_list(), inc/woocommerce.php), printed only while GSM
   will draw Build (A9). The numbers are CSS counters on the <ol>. */
.taf-steps {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-bottom: 16px;
    list-style: none;
    counter-reset: taf-step;
}

.taf-steps li {
    display: flex;
    align-items: center;
    gap: 12px;
    font-size: 14.5px;
    line-height: 1.4;
    color: var(--color-text);
    counter-increment: taf-step;
}

.taf-steps li::before {
    content: counter(taf-step);
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    border-radius: var(--radius-full);
    background: var(--color-text);
    color: var(--color-bg);
    font-size: 12px;
    font-weight: var(--font-weight-bold);
}

/* The quantity stepper assets/js/qty-stepper.js adds on gang sheets only (A13),
   inline as the page is parsed, so the row is drawn once, already a stepper (theme
   0.12.1): − and + around WooCommerce's own input, which GSM reads when Build is
   clicked. The label is section 2's "Quantity" pseudo-element, set beside the stepper
   as the mock draws it.
   The three parts draw one 44px pill and each is its own 44px hit area, so nothing is
   extended or clipped. Without JavaScript the input keeps section 2's plain style. */
.woocommerce div.product form.cart .quantity.taf-qty-on {
    display: flex;
    align-items: center;
}

.woocommerce div.product form.cart .quantity.taf-qty-on::before {
    margin: 0 auto 0 0;
    font-size: 13px;
    font-weight: var(--font-weight-semibold);
    text-transform: none;
}

.taf-qty-btn {
    flex: none;
    width: 44px;
    height: 44px;
    border: 1px solid var(--color-border-mid);
    background: var(--color-bg);
    font-family: var(--font-body);
    font-size: 18px;
    line-height: 1;
    color: var(--color-text);
    cursor: pointer;
    transition: background-color var(--ease-out-fast);
}

.taf-qty-dec {
    border-right: 0;
    border-radius: var(--radius-full) 0 0 var(--radius-full);
}

.taf-qty-inc {
    border-left: 0;
    border-radius: 0 var(--radius-full) var(--radius-full) 0;
}

.taf-qty-btn:hover:not(:disabled) {
    background: var(--color-bg-muted);
}

/* Disabled at the minimum (qty-stepper.js). The glyph stays readable (5.74:1); the
   cursor says the button does nothing. */
.taf-qty-btn:disabled {
    color: var(--color-text-light);
    cursor: not-allowed;
}

/* Lifted above the input beside it, so the ring is never painted over. */
.taf-qty-btn:focus-visible {
    position: relative;
    z-index: 1;
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}

/* The input is lifted the same way when focused (its ring is section 2's): flex items
   paint in order, so the + after it would paint over the right side of its ring. */
.woocommerce div.product form.cart .quantity.taf-qty-on input.qty:focus-visible {
    position: relative;
    z-index: 1;
}

.woocommerce div.product form.cart .quantity.taf-qty-on input.qty {
    width: 44px;
    height: 44px;
    padding: 0;
    border: 1px solid var(--color-border-mid);
    border-right: 0;
    border-left: 0;
    border-radius: 0;
    background: var(--color-bg);
    font-size: 15px;
    font-weight: var(--font-weight-bold);
    font-variant-numeric: tabular-nums;
    -moz-appearance: textfield;
    appearance: textfield;
}

/* Typed, not spun: − and + replace the browser's own spin buttons. */
.woocommerce div.product form.cart .quantity.taf-qty-on input.qty::-webkit-inner-spin-button,
.woocommerce div.product form.cart .quantity.taf-qty-on input.qty::-webkit-outer-spin-button {
    -webkit-appearance: none;
    margin: 0;
}

/* GSM's Build a Gang Sheet button (spec §5.4), restyled only: its markup, its
   disabled state and its click are GSM's (gangsheet-maker 0.13.1,
   class-gsm-product.php), which enables it on WooCommerce's found_variation.
   GSM prints its own rules in <head>, after the theme's sheets, at (0,2,0), with
   !important on the background, ink, border, radius and padding and on the disabled
   opacity (.45). An !important tie goes to the later sheet, so these selectors
   outrank GSM's instead: (0,3,2), and (0,4,2) with :hover or :disabled, against
   GSM's (0,2,0) and (0,3,0). Only the properties GSM marks !important carry it here;
   the rest win on specificity alone. The grid icon is a mask in the ink colour, so
   the button's markup stays GSM's. Colours follow the theme's action convention
   (0.9.85): dark ink on the cyan fill, ink fill with white ink on hover; disabled is
   the light grey with muted ink (4.82:1) at full opacity. No helper line follows
   it (P3-7). */
body.gsm-gangsheet-product form.cart .gsm-build-button {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    width: 100%;
    height: 56px;
    padding: 0 24px !important;
    border: 0 !important;
    border-radius: var(--radius-full) !important;
    background: var(--color-primary) !important;
    color: var(--color-text) !important;
    font-family: var(--font-body);
    font-size: 15px;
    font-weight: var(--font-weight-semibold);
    line-height: 1.2;
    text-transform: uppercase;
    box-shadow: none;
    cursor: pointer;
    transition: background-color var(--ease-out-fast), color var(--ease-out-fast);
}

body.gsm-gangsheet-product form.cart .gsm-build-button::before {
    content: '';
    flex: none;
    width: 18px;
    height: 18px;
    background-color: currentColor;
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='3' width='7' height='7'/%3E%3Crect x='14' y='3' width='7' height='7'/%3E%3Crect x='3' y='14' width='7' height='7'/%3E%3Crect x='14' y='14' width='7' height='7'/%3E%3C/svg%3E") center / contain no-repeat;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='3' width='7' height='7'/%3E%3Crect x='14' y='3' width='7' height='7'/%3E%3Crect x='3' y='14' width='7' height='7'/%3E%3Crect x='14' y='14' width='7' height='7'/%3E%3C/svg%3E") center / contain no-repeat;
}

body.gsm-gangsheet-product form.cart .gsm-build-button:hover {
    background: var(--color-text) !important;
    color: var(--color-bg) !important;
}

body.gsm-gangsheet-product form.cart .gsm-build-button:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}

/* After :hover on purpose: a disabled button under the pointer stays grey. */
body.gsm-gangsheet-product form.cart .gsm-build-button:disabled {
    opacity: 1 !important;
    background: var(--color-border-light) !important;
    color: var(--color-text-light) !important;
    cursor: not-allowed;
}


/* ── 4. Product Tabs ── */

/* WooCommerce's tabs, as design/Product.dc.html's underline tabs, below the
   gallery and summary block. div.product is a one-column grid of that block
   (div.taf-product-top) and these tabs, so they need no column span. The markup,
   WooCommerce's tab script and the order (Description, Artwork, Heat Press or
   Applying, Shipping) are unchanged. min-width: 0 keeps the nowrap tab bar's
   min-content from holding that one track open, which overflowed at 320 and 375. */
.woocommerce div.product .woocommerce-tabs {
    min-width: 0;
}

/* 48px tabs on a 1px rule. The labels sit 36px apart, as drawn: the mock gives each
   tab 4px of padding and a 28px gap, and here each takes 8px and a 20px gap, which
   leaves room for the focus ring inside the tab. The row scrolls sideways rather
   than wrapping, so a fifth tab never makes a second row; the scrollbar is hidden,
   and the clipped last label is the cue. */
.woocommerce div.product .woocommerce-tabs ul.tabs {
    list-style: none;
    display: flex;
    flex-wrap: nowrap;
    gap: 20px;
    margin: 0;
    padding: 0;
    border-bottom: 1px solid var(--color-border-light);
    overflow-x: auto;
    overscroll-behavior-x: contain;  /* Don't chain a swipe into page navigation */
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    -ms-overflow-style: none;
}

.woocommerce div.product .woocommerce-tabs ul.tabs::-webkit-scrollbar {
    display: none;
}

.woocommerce div.product .woocommerce-tabs ul.tabs li {
    flex: 0 0 auto;         /* Never let a label compress onto two lines */
    margin: 0;
    padding: 0;
}

.woocommerce div.product .woocommerce-tabs ul.tabs li a {
    display: flex;
    align-items: center;
    height: 48px;
    padding: 0 8px;
    font-size: 15px;
    font-weight: var(--font-weight-semibold);
    color: var(--color-text-light);
    text-decoration: none;
    white-space: nowrap;
    box-shadow: inset 0 -2px 0 transparent;
    transition: color var(--ease-out-fast), box-shadow var(--ease-out-fast);
}

.woocommerce div.product .woocommerce-tabs ul.tabs li a:hover {
    color: var(--color-text);
}

/* The open tab: ink, with a 2px cyan underline drawn as an inset shadow, so the
   label never moves. The cyan is the line only, never text; the ink and the
   selected state say which tab is open. */
.woocommerce div.product .woocommerce-tabs ul.tabs li.active a {
    color: var(--color-text);
    box-shadow: inset 0 -2px 0 var(--color-primary);
}

/* The ring sits inside the tab (offset -2px) because ul.tabs scrolls, and a
   scroller clips anything drawn outside its box. The tab's 8px padding keeps the
   ring off the label. */
.woocommerce div.product .woocommerce-tabs ul.tabs li a:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: -2px;
}

/* The panel: 28px under the bar, 16px type in muted ink, never wider than
   --product-panel-max. */
.woocommerce div.product .woocommerce-tabs .panel {
    max-width: var(--product-panel-max);
    padding: 28px 0 0;
    font-size: 16px;
    line-height: 1.7;
    color: var(--color-text-light);
}

.woocommerce div.product .woocommerce-tabs .panel h2 {
    font-size: 1.25rem;
    margin-bottom: 1rem;
}

/* Prose in the panels. Three containers hold it:
   - the owner's own text under the Description blocks (.taf-pd-content);
   - the spec tabs (.taf-spec-panel, Settings > Product Specs);
   - for a product of no known kind, WooCommerce's own description, whose text sits
     straight in the panel.
   The blocks above the owner's text (.taf-pd, §4a) are not prose and keep their own
   rules.

   The mock's construction: 16px between blocks, 20px headings in ink, 6px square
   list markers and ink lead-ins. The content itself is unchanged (spec §5.1),
   because the homepage's How to apply parses the Heat Press and Applying tabs.

   One marker colour, cyan, on both lines: the mock's magenta on UV would have to
   key on a category slug, which no rule here does. */
.woocommerce div.product :is(.taf-pd-content, .taf-spec-panel, .woocommerce-Tabs-panel--description:not(:has(.taf-pd))) > * + * {
    margin-top: 16px;
}

.woocommerce div.product :is(.taf-pd-content, .taf-spec-panel, .woocommerce-Tabs-panel--description:not(:has(.taf-pd))) > * + h3 {
    margin-top: 28px;
}

.woocommerce div.product :is(.taf-pd-content, .taf-spec-panel, .woocommerce-Tabs-panel--description:not(:has(.taf-pd))) h3 {
    font-size: 20px;
    font-weight: var(--font-weight-bold);
    line-height: 1.3;
    color: var(--color-text);
}

.woocommerce div.product :is(.taf-pd-content, .taf-spec-panel, .woocommerce-Tabs-panel--description:not(:has(.taf-pd))) ul {
    list-style: none;
    padding: 0;
}

.woocommerce div.product :is(.taf-pd-content, .taf-spec-panel, .woocommerce-Tabs-panel--description:not(:has(.taf-pd))) ul > li {
    position: relative;
    padding-left: 18px;
}

/* The marker is centred on the first line: half the 1.7 line height, less half
   the 6px square. */
.woocommerce div.product :is(.taf-pd-content, .taf-spec-panel, .woocommerce-Tabs-panel--description:not(:has(.taf-pd))) ul > li::before {
    content: '';
    position: absolute;
    top: calc(0.85em - 3px);
    left: 0;
    width: 6px;
    height: 6px;
    background: var(--color-primary);
}

/* Numbered steps keep their numbers. The global reset strips list indentation, so
   it is restored here. */
.woocommerce div.product :is(.taf-pd-content, .taf-spec-panel, .woocommerce-Tabs-panel--description:not(:has(.taf-pd))) ol {
    list-style: decimal;
    padding-left: 1.35rem;
}

.woocommerce div.product :is(.taf-pd-content, .taf-spec-panel, .woocommerce-Tabs-panel--description:not(:has(.taf-pd))) li + li {
    margin-top: 10px;
}

.woocommerce div.product :is(.taf-pd-content, .taf-spec-panel, .woocommerce-Tabs-panel--description:not(:has(.taf-pd))) strong {
    color: var(--color-text);
    font-weight: var(--font-weight-semibold);
}


/* ── 4a. Description blocks ──
   The Description tab's computed blocks (template-parts/product/description.php,
   spec §5.2): Best for, the fact cards, the price table and Good to know, 28px apart,
   then the owner's own text. Each section opens with an eyebrow led by a square:
   cyan for Best for, magenta for the prices, yellow, ringed so it reads on white,
   for Good to know. */
.taf-pd {
    display: flex;
    flex-direction: column;
    gap: 28px;
}

.taf-pd + .taf-pd-content {
    margin-top: 28px;
}

.taf-pd-eyebrow {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0 0 10px;
    font-size: 12px;
    font-weight: var(--font-weight-bold);
    line-height: 1.3;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--color-text);
}

.taf-pd-eyebrow::before {
    content: '';
    flex: none;
    width: 10px;
    height: 10px;
    background: var(--color-primary);
}

.taf-pd-prices .taf-pd-eyebrow::before {
    background: var(--color-accent);
}

.taf-pd-know .taf-pd-eyebrow::before {
    background: var(--color-yellow);
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);
}

/* Best for: chips that are list items, not controls, so no hit area applies. */
.taf-pd-chips {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.taf-pd-chip {
    display: inline-flex;
    align-items: center;
    height: 36px;
    padding: 0 14px;
    border: 1px solid var(--color-border-mid);
    border-radius: var(--radius-full);
    font-size: 14px;
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
}

/* The fact cards: up to three in a row, one column on a narrow panel, each with a
   3px strip coloured by its position (cyan, magenta, yellow). */
.taf-pd-facts {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
    gap: 12px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.taf-pd-fact {
    position: relative;
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 18px;
    border-radius: var(--radius-tile);
    background: var(--color-bg-muted);
    overflow: hidden;       /* Clips the strip to the card's corners */
}

.taf-pd-fact::before {
    content: '';
    position: absolute;
    top: 0;
    right: 0;
    left: 0;
    height: 3px;
    background: var(--color-primary);
}

.taf-pd-fact:nth-child(2)::before {
    background: var(--color-accent);
}

.taf-pd-fact:nth-child(3)::before {
    background: var(--color-yellow);
}

.taf-pd-fact-title {
    font-size: 17px;
    font-weight: var(--font-weight-bold);
    line-height: 1.3;
    color: var(--color-text);
}

.taf-pd-fact-text {
    margin: 0;
    font-size: 14px;
    line-height: 1.55;
}

/* The price table: Example prices on an upload, Sizes & prices on a fixed sheet.
   A real table, drawn as the mock's rounded box: a muted header row of small caps,
   then rows ruled apart, the price column right-aligned in ink. The header's corner
   cells carry the radius themselves, so nothing has to clip the table. */
.taf-pd-table {
    width: 100%;
    margin: 0;
    border: 1px solid var(--color-border-light);
    border-collapse: separate;
    border-spacing: 0;
    border-radius: var(--radius-tile);
    font-size: 14.5px;
    font-variant-numeric: tabular-nums;
}

.taf-pd-table th,
.taf-pd-table td {
    padding: 11px 6px;
    border: 0;
    text-align: left;
    vertical-align: baseline;
}

.taf-pd-table th:first-child,
.taf-pd-table td:first-child {
    padding-left: 16px;
}

.taf-pd-table th:last-child,
.taf-pd-table td:last-child {
    width: 1%;
    padding-right: 16px;
    text-align: right;
    white-space: nowrap;
}

.taf-pd-table thead th {
    padding-top: 10px;
    padding-bottom: 10px;
    background: var(--color-bg-muted);
    font-size: 11.5px;
    font-weight: var(--font-weight-bold);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--color-text-light);
}

.taf-pd-table thead th:first-child {
    border-top-left-radius: calc(var(--radius-tile) - 1px);
}

.taf-pd-table thead th:last-child {
    border-top-right-radius: calc(var(--radius-tile) - 1px);
}

.taf-pd-table tbody td {
    border-top: 1px solid var(--color-border-light);
}

.taf-pd-table tbody td:first-child {
    font-weight: var(--font-weight-semibold);
    color: var(--color-text);
}

.taf-pd-table tbody td:last-child {
    font-weight: var(--font-weight-bold);
    color: var(--color-text);
}

/* The notes under the table: the formula and what the examples assume, or how many
   sizes the picker holds. */
.taf-pd-note {
    margin: 10px 0 0;
    font-size: 13px;
    line-height: 1.5;
    color: var(--color-text-light);
}

.taf-pd-note + .taf-pd-note {
    margin-top: 4px;
}

/* Good to know: questions in ink over muted answers, ruled apart. */
.taf-pd-faq {
    margin: 0;
    border-top: 1px solid var(--color-border-light);
}

.taf-pd-faq-item {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 14px 0;
    border-bottom: 1px solid var(--color-border-light);
}

.taf-pd-faq dt {
    font-size: 15.5px;
    font-weight: var(--font-weight-bold);
    line-height: 1.4;
    color: var(--color-text);
}

.taf-pd-faq dd {
    margin: 0;
    font-size: 14.5px;
    line-height: 1.6;
}

/* ── 4b. Callouts in the tab panels (scoped, ruling D6) ──
   The handoff's construction for the stored .taf-note--tip|warn|info paragraphs: a
   grey box with a coloured square, magenta for a warning, cyan for a tip and ink for
   an aside. It is scoped to the product tab panels: the unscoped rules above keep the
   tinted, railed construction everywhere else, including the homepage's How to
   apply, whose suite pins it. The square is decoration; every callout's copy states
   its own point, so colour is never the only signal. #666 on the grey is 5.27:1. */
.woocommerce-Tabs-panel .taf-note {
    position: relative;
    margin-bottom: 0;
    padding: 14px 16px 14px 36px;
    border: 0;
    border-radius: var(--radius-md);
    background: var(--color-bg-muted);
    font-size: 15px;
    line-height: 1.6;
    color: var(--color-text-light);
}

/* The square sits on the first line: 14px of padding plus half of the 24px line,
   less half the square. */
.woocommerce-Tabs-panel .taf-note::before {
    content: '';
    position: absolute;
    top: 22px;
    left: 16px;
    width: 8px;
    height: 8px;
    background: var(--color-text);
}

.woocommerce-Tabs-panel .taf-note--warn::before {
    background: var(--color-accent);
}

.woocommerce-Tabs-panel .taf-note--tip::before {
    background: var(--color-primary);
}

.woocommerce-Tabs-panel .taf-note--info::before {
    background: var(--color-text);
}

.woocommerce-Tabs-panel .taf-note strong {
    color: var(--color-text);
}


/* ── 6. TAF FileDrop upload card (theme layer) ── */

/* TAF FileDrop 1.10.0 styles its own upload card (taffd.css) on this theme's
   tokens, and section 3 styles Add to cart wherever it sits, colours and 52px
   pill included. This section holds only where the card meets the page. Phones
   (the fixed bar, the drop zone's touch wording, no icon tile) are in the mobile
   twin at the end of this file.

   The cascade: taffd.css loads after every theme sheet (render_uploader()
   enqueues it from the page body and WordPress moves the link into <head>), so
   a theme rule for a FileDrop element wins on specificity, never on order.
   Every selector here starts `.single-product .taffd-wrapper`; taffd.css never
   starts one with a body class. */

/* Inside the purchase card the card's own padding spaces the uploader, so
   FileDrop's standalone bottom margin goes. */
.single-product .taffd-wrapper {
    margin-bottom: 0;
}

/* Add to cart, once FileDrop's start-up has moved it into the footer (.taffd-js
   is the start-up's mark): flush in the footer's row, its label on one line.
   (0,5,0), above section 3's button rules. */
.single-product .taffd-wrapper.taffd-js .taffd-footer-action .single_add_to_cart_button {
    margin: 0;
    white-space: nowrap;
}


/* ── 7. Added-to-cart drawer ── */

/* The drawer (site redesign Phase 3b, spec §5.5): design/Product.dc.html from 769px,
   a bottom sheet on the mobile twin (the end of this file, Mobile.dc.html M10). Markup:
   template-parts/product/drawer.php; behaviour: assets/js/product.js.

   A native <dialog>, so `display` is set on [open] only: an author display on the
   bare element would outrank the browser's dialog:not([open]) { display: none } and
   print the closed drawer into the page, which readers without JavaScript would then
   see. showModal() puts it in the top layer, above the sticky header and the
   uploader's phone bar, so it needs no z-index.

   A browser without <dialog> support has no such default, and product.js then returns
   early and never closes the element, so the :not([open]) rule below hides a closed
   drawer there; its notice is the confirmation. Where <dialog> exists the rule changes
   nothing: product.js calls close() only after the slide-out ends, so the dialog keeps
   [open] while it animates. */
.taf-added:not([open]) {
    display: none;
}

.taf-added {
    position: fixed;
    inset: 0 0 0 auto;
    width: min(420px, 100%);
    max-width: 100%;
    height: 100%;
    max-height: 100%;
    margin: 0;
    padding: 0;
    border: 0;
    background: var(--color-bg);
    color: var(--color-text);
    box-shadow: -20px 0 50px rgba(0, 0, 0, 0.2);
}

.taf-added[open] {
    display: flex;
    flex-direction: column;
    animation: taf-added-in var(--ease-drawer);
}

/* product.js adds .is-closing, waits for this animation to end, then calls close().
   --ease-drawer is the theme's drawer motion (0.22s); the handoff draws this panel at
   320ms on the same curve. */
.taf-added.is-closing {
    animation: taf-added-out var(--ease-drawer) forwards;
}

.taf-added::backdrop {
    /* The fallback repeats --color-scrim's value in base.css: Safari before 17.4 does
       not pass custom properties to ::backdrop, which would leave the scrim transparent. */
    background: var(--color-scrim, rgba(26, 26, 26, 0.45));
}

@keyframes taf-added-in {
    from { transform: translateX(100%); }
}

@keyframes taf-added-out {
    to { transform: translateX(100%); }
}

/* The phone sheet's slide: the mobile twin below swaps the animation names. */
@keyframes taf-added-up {
    from { transform: translateY(100%); }
}

@keyframes taf-added-down {
    to { transform: translateY(100%); }
}

.taf-added-bar {
    flex: none;
    height: 4px;
    background: linear-gradient(90deg, var(--color-cyan), var(--color-magenta), var(--color-yellow));
}

/* The grab handle is the phone sheet's (the mobile twin below). */
.taf-added-handle {
    display: none;
}

.taf-added-head {
    flex: none;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 20px 22px;
    border-bottom: 1px solid var(--color-border-light);
}

.taf-added-check {
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: var(--radius-full);
    background: var(--state-success-bg);
    color: var(--state-success-text);
}

.taf-added-title {
    flex: 1;
    min-width: 0;
    font-size: 17px;
    font-weight: var(--font-weight-bold);
    line-height: 1.3;
    color: var(--color-text);
}

/* A focus target, not a control: product.js focuses it when the drawer opens, so a
   screen reader starts at the heading. It draws no ring. */
.taf-added-title:focus {
    outline: none;
}

.taf-added-close {
    position: relative;
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border: 0;
    border-radius: var(--radius-full);
    background: var(--color-bg-muted);
    font-family: var(--font-body);
    font-size: 22px;
    line-height: 1;
    color: var(--color-text);
    cursor: pointer;
    transition: background-color var(--ease-out-fast);
}

/* Drawn 40px; a transparent square takes the hit area to 48, past the 44 minimum. */
.taf-added-close::after {
    content: '';
    position: absolute;
    inset: -4px;
}

.taf-added-close:hover {
    background: var(--color-border-light);
}

/* The body scrolls on its own when the panel is shorter than its contents; it holds no
   control, so its overflow clips nothing that is hit-tested. */
.taf-added-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding: 20px 22px;
}

.taf-added-line {
    display: flex;
    align-items: center;
    gap: 14px;
}

.taf-added-thumb {
    flex: none;
    width: 64px;
    height: 64px;
    border: 1px solid var(--color-border-light);
    border-radius: var(--radius-md);
    background: var(--color-bg-alt);
    overflow: hidden;
}

.taf-added-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.taf-added-text {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.taf-added-name {
    font-size: 15px;
    line-height: 1.3;
    color: var(--color-text);
}

.taf-added-summary {
    font-size: 13px;
    color: var(--color-text-light);
}

.taf-added-price {
    flex: none;
    font-variant-numeric: tabular-nums;
    color: var(--color-text);
}

.taf-added-min {
    font-size: 13.5px;
    line-height: 1.5;
    color: var(--color-text-light);
}

.taf-added-count {
    padding-top: 14px;
    border-top: 1px solid var(--color-border-light);
    font-size: 14.5px;
    color: var(--color-text-light);
}

.taf-added-actions {
    flex: none;
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 20px 22px;
    border-top: 1px solid var(--color-border-light);
}

.taf-added-checkout,
.taf-added-cart {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 52px;
    border-radius: var(--radius-full);
    font-size: 15px;
    font-weight: var(--font-weight-semibold);
    text-decoration: none;
    transition: background-color var(--ease-out-fast), color var(--ease-out-fast);
}

/* The action convention (0.9.85): dark ink on the cyan fill (6.88:1), the ink fill
   with white ink on hover, for Checkout and View cart alike. */
.taf-added-checkout {
    background: var(--color-primary);
    color: var(--color-text);
    text-transform: uppercase;
}

.taf-added-cart {
    border: 1.5px solid var(--color-text);
    background: var(--color-bg);
    color: var(--color-text);
}

.taf-added-checkout:hover,
.taf-added-cart:hover {
    background: var(--color-text);
    color: var(--color-bg);
}

/* A text button; min-height gives it the 44px hit area its 14px label alone lacks. */
.taf-added-continue {
    align-self: center;
    min-height: 44px;
    padding: 0 12px;
    border: 0;
    background: none;
    font-family: var(--font-body);
    font-size: 14px;
    font-weight: var(--font-weight-semibold);
    color: var(--color-link);
    cursor: pointer;
    transition: color var(--ease-out-fast);
}

.taf-added-continue:hover {
    color: var(--color-accent-text);
}

.taf-added a:focus-visible,
.taf-added button:focus-visible {
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}

/* product.js hides the "...has been added to your cart" notice with [hidden] once the
   drawer is open (D11). The notice's own display: flex (woocommerce.css §16) would otherwise
   outrank the browser's [hidden] rule and keep it on screen. */
.woocommerce-notices-wrapper .woocommerce-message[hidden] {
    display: none;
}

@media (prefers-reduced-motion: reduce) {
    .taf-added[open],
    .taf-added.is-closing {
        animation: none;
    }
}


/* ══ Phones and narrow widths ══
   The rules responsive.css held for these pages until 0.12.1, in the order they
   had there: the 880px tabs, the mobile twin, the 680px tabs and the 480px price
   table. They follow every rule above, as they did then; responsive.css still
   loads after this file, so an override written there still wins on order. */

/* ── Product Tabs Breakpoint (880px) ──────────────────────────────── */
/* The underline tabs keep their 48px height, the touch target, at every width. Below
   880px the labels and the space between them tighten, so more tabs stay in view
   before the row needs a swipe. */
@media (max-width: 880px) {
    .woocommerce div.product .woocommerce-tabs ul.tabs {
        gap: 12px;
    }

    .woocommerce div.product .woocommerce-tabs ul.tabs li a {
        font-size: 14px;
    }
}

/* ── Phones (the mobile twin) ──
   TWIN of responsive.css's mobile-overlay block, responsive.css's utility-row block,
   responsive.css's blog-collapse block, pages.css's card-overlap block, and
   MOBILE_QUERY in assets/js/main.js. All six are byte-identical and must stay that
   way. */
@media (max-width: 768px), (max-height: 500px) and (orientation: landscape) {

    /* ── WooCommerce: Single Product (Mobile.dc.html M3) ──
       One column, 14px under the header: the photo, 250px tall, cropped to fill, with
       16px corners, then the pill, the title, the price line and the purchase form.
       No breadcrumb, thumbnail strip or short description, so the uploader is reached
       quickly; the tabs below still carry the details. The gallery stays
       FlexSlider's, so the photo still swipes. The gang-sheet pages, which have no
       phone design, simply stack (spec §5.1). This block also undoes the 1024px
       column rules wherever the twin applies above that width, in a short landscape
       window. */

    .single-product .taf-main {
        padding-top: 14px;
    }

    .single-product .woocommerce-breadcrumb {
        display: none;
    }

    .woocommerce div.product,
    .woocommerce div.product .taf-product-top {
        grid-template-columns: minmax(0, 1fr);
        row-gap: 14px;
    }

    .woocommerce div.product div.images {
        position: relative;
        top: auto;
        max-width: none;
    }

    .woocommerce div.product .woocommerce-product-gallery__image,
    .woocommerce div.product .woocommerce-product-gallery__image > a > img {
        border-radius: var(--radius-tile);
    }

    .woocommerce div.product .woocommerce-product-gallery__image > a > img {
        height: 250px;
        aspect-ratio: auto;
    }

    .woocommerce div.product div.images .flex-control-thumbs,
    .woocommerce div.product .woocommerce-product-details__short-description {
        display: none;
    }

    /* No strip, so nothing reserves one (§3 above): until FlexSlider
       starts, the wrapper shows the first image alone. Each selector repeats the
       reservation's, so it weighs the same and wins by coming later. */
    .woocommerce div.product div.images > .woocommerce-product-gallery__wrapper:has(> .woocommerce-product-gallery__image ~ .woocommerce-product-gallery__image) {
        display: block;
    }

    .woocommerce div.product div.images > .woocommerce-product-gallery__wrapper:has(> .woocommerce-product-gallery__image ~ .woocommerce-product-gallery__image) > .woocommerce-product-gallery__image:first-child {
        margin-bottom: 0;
    }

    .woocommerce div.product div.images > .woocommerce-product-gallery__wrapper:has(> .woocommerce-product-gallery__image ~ .woocommerce-product-gallery__image) > .woocommerce-product-gallery__image:nth-child(n+2) {
        display: none;
    }

    .woocommerce div.product div.images > .woocommerce-product-gallery__wrapper:has(> .woocommerce-product-gallery__image ~ .woocommerce-product-gallery__image)::after {
        content: none;
    }

    .woocommerce div.product .product_title {
        font-size: 26px;
        line-height: 1.1;
        letter-spacing: -0.025em;
    }

    /* M3 draws the purchase form straight in the column, with no card around it. */
    .taf-purchase {
        margin-top: 4px;
        padding: 0;
        border: 0;
        border-radius: 0;
    }

    .woocommerce div.product .woocommerce-tabs {
        margin-top: 22px;
    }

    /* ── TAF FileDrop: phone bar (Phase 3b, Mobile.dc.html M3) ──
       On phones the uploader's footer (the count, the files' price, Add to cart)
       becomes a bar fixed to the bottom of the screen, one row high, so Add to
       cart is always in reach. The minimum-charge line stays in the uploader
       above it (it is outside #taffd-footer), which is what keeps the bar one
       row. The bar is fixed to the viewport, so it owes nothing to the purchase
       card, which this twin draws without padding or border (M3).
         - Keyed on .taffd-js, which FileDrop's inline start-up adds as soon as Add
           to cart is parsed, so the bar forms before anything below the card is
           laid out. Without JavaScript the footer stays in place, unpadded.
         - --taffd-bar-h is the bar's height without the safe area: 12px above and
           below a 52px button, plus the 1px top rule. The bar and the page's bottom
           padding both read it, so the page always ends above the bar and the bar
           never covers the site footer.
         - z-index 100 sits under the header shell (1000), which holds the phone
           menu, so an open menu covers the bar (and isolatePage() makes the bar
           inert with the rest of the page). The Added-to-cart <dialog> is in the
           top layer, above both.
         - env(safe-area-inset-bottom) is 0 until the viewport meta opts into
           viewport-fit=cover (header.php does not), so today it adds nothing.
         - No ancestor of #taffd-footer may set transform, filter, contain or
           will-change: any of them would make that ancestor, not the viewport,
           the bar's containing block, and the bar would scroll away. */
    body:has(.taffd-wrapper.taffd-js) {
        --taffd-bar-h: 77px;
        padding-bottom: calc(var(--taffd-bar-h) + env(safe-area-inset-bottom, 0px));
    }

    .single-product .taffd-wrapper.taffd-js .taffd-footer {
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 100;
        flex-wrap: nowrap;
        gap: 12px;
        height: calc(var(--taffd-bar-h) + env(safe-area-inset-bottom, 0px));
        margin: 0;
        padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px));
        border-top: 1px solid var(--color-border-light);
        background: var(--color-bg);
    }

    /* One row whatever the figures: the count and the price never wrap, and the
       button takes the room left. */
    .single-product .taffd-wrapper.taffd-js .taffd-footer-sum {
        flex: none;
    }

    .single-product .taffd-wrapper.taffd-js .taffd-count-line {
        font-size: 12px;
        white-space: nowrap;
    }

    .single-product .taffd-wrapper.taffd-js .taffd-files-total {
        font-size: 20px;
        white-space: nowrap;
    }

    .single-product .taffd-wrapper.taffd-js .taffd-footer-action {
        flex: 1 1 auto;
        min-width: 0;
    }

    /* Add to cart fills the rest of the bar (section 3 keeps its colours and
       52px height; section 6, its one-line label). */
    .single-product .taffd-wrapper.taffd-js .taffd-footer-action .single_add_to_cart_button {
        width: 100%;
        padding: 0 12px;
        font-size: 14px;
    }

    /* The drop zone on phones (M3): "Tap to upload files" and no icon tile (ruling
       D1, as M3 draws it), a tighter box. taffd.css's own 768px query does the
       wording for a site without this theme; this twin also covers a landscape
       phone. */
    .single-product .taffd-wrapper .taffd-dropzone {
        gap: 6px;
        padding: 22px 14px;
    }

    .single-product .taffd-wrapper .taffd-drop-tile,
    .single-product .taffd-wrapper .taffd-drop-desktop {
        display: none;
    }

    .single-product .taffd-wrapper .taffd-drop-touch {
        display: inline;
    }

    .single-product .taffd-wrapper .taffd-drop-title {
        font-size: 15px;
    }

    .single-product .taffd-wrapper .taffd-drop-limits {
        font-size: 12px;
    }

    /* ── Gang sheets: phones (site redesign Phase 3b) ──
       The gang-sheet pages have no phone mock (spec §5.1: they simply stack). The
       purchase card loses its padding and border here (WooCommerce: Single Product,
       above, after M3), so the length picker is the one inset panel left in the column,
       and it tightens to match: less padding and a narrower gap around the range, and a
       smaller figure, so the length, "long" and the sheet line wrap less at 320px.
       Every control keeps its 44px hit area, and the chip row its 8px gap. */

    .taf-picker {
        padding: 14px;
    }

    .taf-picker-controls {
        gap: 8px;
    }

    .taf-picker-big {
        font-size: 32px;
    }

    /* ── Added-to-cart drawer: bottom sheet (site redesign Phase 3b, Mobile.dc.html M10) ──
       The same <dialog> as a sheet along the bottom edge, with a grab handle, sliding up.
       "Cart · n items" and Continue shopping are desktop only (spec §5.5). The × stays,
       because a touch user has no other visible way to close the sheet (Escape and the
       backdrop also close it). The thumbnail stays 64px (the spec over M10's 56px).
       The 24px radius is the sheet's alone, outside the theme's 8/12/16/20/999 scale,
       so it is a literal here. The brand bar runs edge to edge under the rounded
       corners, so the sheet clips to its own shape with clip-path, not overflow:
       hidden, which this theme keeps off containers that hold a hit-area extension;
       the ×'s extension sits well inside the edge either way. The safe-area inset
       keeps the buttons clear of a home indicator where the browser reports one. */

    .taf-added {
        inset: auto 0 0 0;
        width: 100%;
        height: auto;
        border-radius: 24px 24px 0 0;
        clip-path: inset(0 round 24px 24px 0 0);
        box-shadow: none;
    }

    .taf-added[open] {
        animation-name: taf-added-up;
    }

    .taf-added.is-closing {
        animation-name: taf-added-down;
    }

    .taf-added-handle {
        display: block;
        padding-top: 10px;
    }

    .taf-added-handle::before {
        content: '';
        display: block;
        width: 40px;
        height: 4px;
        margin: 0 auto;
        border-radius: var(--radius-full);
        background: var(--color-border-mid);
    }

    .taf-added-head {
        padding: 14px 18px;
    }

    .taf-added-body {
        gap: 12px;
        padding: 16px 18px;
    }

    .taf-added-name {
        font-size: 14.5px;
    }

    .taf-added-summary {
        font-size: 12.5px;
    }

    .taf-added-count,
    .taf-added-continue {
        display: none;
    }

    .taf-added-actions {
        gap: 8px;
        padding: 0 18px calc(22px + env(safe-area-inset-bottom, 0px));
        border-top: 0;
    }

    /* Reduced motion: the sheet appears and goes without sliding. Nested @media
       rather than a new copy of the twin, which must stay byte-identical. */
    @media (prefers-reduced-motion: reduce) {
        .taf-added[open],
        .taf-added.is-closing {
            animation: none;
        }
    }
}

/* ── Font Size Reduction (680px) ──────────────────────────────────── */
/* Only the tab labels shrink here, to their smallest size. The product title and
   price take their phone sizes in the mobile twin's single-product block, which
   covers every width below 769px. */
@media (max-width: 680px) {
    .woocommerce div.product .woocommerce-tabs ul.tabs li a {
        font-size: 13.5px;
    }
}

/* ── Small Phone Breakpoint (480px) ── */
@media (max-width: 480px) {

    /* ── WooCommerce: Description tab price table ── */
    /* The Description tab's three-column table (§4a above): tighter cells
       and type, so "11 × 14 in", "× 50" and "$146.09" each keep to one line at
       320px. */

    .taf-pd-table {
        font-size: 13.5px;
    }

    .taf-pd-table th,
    .taf-pd-table td {
        padding: 10px 4px;
    }

    .taf-pd-table th:first-child,
    .taf-pd-table td:first-child {
        padding-left: 12px;
    }

    .taf-pd-table th:last-child,
    .taf-pd-table td:last-child {
        padding-right: 12px;
    }
}
