/* swatch.css
 * Shared swatch appearance (round + rectangular, selected/hover/out-of-
 * stock/unavailable states) — used by both product_page.css and
 * product_grid.css, so this doesn't need to be kept in sync by hand in
 * two places every time it's tweaked.
 *
 * Sized via CSS custom properties. Whichever page includes this file sets
 * --swatch-size (ROUND swatch diameter) on a suitable scope — e.g.
 *   body{ --swatch-size:44px; }               (product_page.php, inline)
 *   .product-card{ --swatch-size:30px; }      (product_grid.php, inline)
 * Falls back to 44px if neither page sets it.
 *
 * TEXT swatches ("Size", "Bag Size", etc.) use a SEPARATE height,
 * --swatch-text-height (2026-08-25 — a text swatch needs far less height
 * than a colour swatch needs diameter to read clearly, so forcing them to
 * match made text swatches look "too tall" no matter how far
 * SWATCH_TEXT_PADDING_VERTICAL, in constants.php, was reduced — that
 * padding only affects how much of a FIXED height the font may fill, not
 * the height itself). Falls back to --swatch-size if a page doesn't set
 * it, so this remains backward-compatible with anywhere that only ever
 * sets one variable. Width is untouched by either variable — text
 * swatches size their own width from font-size + padding, in the
 * generated SVG itself (calculateSwatchLabelWidth() in functions.php).
 *
 * --swatch-border-width and --swatch-corner-radius below are the one
 * place to change border thickness/rounding for both swatch shapes
 * (round swatches use 50% for their own shape regardless — the radius
 * variable only applies to the rectangular text swatch).
 */

:root{
    --swatch-border-width: 1px;
    --swatch-corner-radius: 2px;
}

.swatch-container{display:flex;flex-wrap:wrap;gap:8px}
/* ONE permanent black border, always — never changes colour on hover or
   selection — plus a red ring added OUTSIDE it via box-shadow only when
   selected (see .selected below), so the black border stays visible
   underneath the ring rather than being replaced by it. The icon's own
   SVG draws no border of its own (resolveSwatchIcon() passes border=0),
   so this is the only border a swatch ever shows.
   backface-visibility/translateZ(0) force this onto its own compositing
   layer — without it, scaling a circular border+box-shadow together (see
   .selected below) can rasterize the two independently by a sub-pixel
   amount, showing as a faint gap on one or two sides of the ring.
   IMPORTANT: transform is a single property, so every rule below that
   sets its own transform (hover, selected) must also include
   translateZ(0) alongside its scale() — otherwise that rule would
   silently replace this one and drop the layer promotion exactly when
   it's needed. */
.swatch{
    width:var(--swatch-size, 30px);
    height:var(--swatch-size, 30px);
    border-radius:50%;
    overflow:hidden;
    border:var(--swatch-border-width) solid #ccc;
    cursor:pointer;
    transition:box-shadow .2s, transform .2s;
    background:#fff;
    position:relative;
    backface-visibility:hidden;
    transform:translateZ(0);
}
.swatch:hover{transform:scale(1.05) translateZ(0)}
/* Hovering an ALREADY-selected swatch would otherwise still combine
   :hover's scale() with the selection ring below — the exact combination
   just removed from .selected itself for the sub-pixel gap reason
   explained there. transform:none here (same pattern already used for
   .unavailable/.out-of-stock below) keeps a selected swatch's ring gap-
   free on hover too, not just at rest. */
.swatch.selected:hover{transform:none}
/* Selection — a ring OUTSIDE the permanent border (box-shadow only;
   border itself is left untouched, both its width and colour, so it
   stays visible underneath the ring rather than being replaced by it —
   see this file's very first comment for why box-shadow specifically,
   not a thicker border, is used here).
   2026-08-26: no transform:scale() here any more — it used to grow the
   whole swatch slightly on selection for a bit of visual "pop", but
   scaling a border+box-shadow ring together can rasterize the two as
   separate compositing layers by a sub-pixel amount, showing as a faint
   gap between them (reported: "a 1px gap between the unselected border
   and the selected box-shadow"). The translateZ(0) trick below was a
   prior attempt to prevent exactly that by forcing both onto one
   compositing layer — evidently not reliable enough across browsers/
   zoom levels to fully avoid it. Dropping the scale entirely removes the
   root cause rather than continuing to patch around it: the ring alone
   already reads clearly as "selected" without also needing to grow. */
.swatch.selected{box-shadow:0 0 0 2px #000;}
/* font-size:0 + color:transparent suppress the browser's default
   fallback rendering (broken-image icon + the alt text shown as literal
   visible text) during the moment before each icon's own SVG request
   resolves — every swatch icon is a separate network request
   (swatch_icon.php), so on a slower connection this fallback can flash
   visibly. Purely visual: the alt attribute itself is untouched, so
   screen readers/assistive tech still get it normally. */
.swatch img{width:100%;height:100%;display:block;font-size:0;color:transparent}

/* Text swatches (e.g. "Size 1-2") render as rectangles, not circles — the
   icon SVG itself is a variable width (auto-sized to its own text, or a
   shared group width if USE_CONSISTENT_SWATCH_WIDTH is on), so the
   container needs to size to the image rather than force it into the
   fixed circle every other swatch type uses. border-radius/border here
   are the single source of truth for this shape's rounding/border —
   nothing else (PHP or the icon's own SVG) sets either. */
.swatch.swatch-text{width:auto;height:var(--swatch-text-height, var(--swatch-size, 30px));border-radius:var(--swatch-corner-radius);border:var(--swatch-border-width) solid #ccc}
.swatch.swatch-text img{width:auto;height:100%;display:block}
/* Same outer-ring-via-box-shadow treatment as round swatches — see
   .swatch.selected's own comment above for why there's no
   transform:scale() paired with it any more either. Needs its own rule
   (not just relying on .swatch.selected) because .swatch.swatch-text's
   plain "border" shorthand above has equal specificity (two classes
   each) and comes later in the file — without this, that shorthand
   would silently win. Three classes here beats both by specificity,
   regardless of source order. */
.swatch.swatch-text.selected{box-shadow: 0 0 0 2px #000;}

/* Out-of-stock: plain opacity only (a real variant, just currently at
   zero stock — kept lighter-touch than "unavailable" below, and with no
   cross, so the two remain visually distinct from each other). */
.swatch.out-of-stock{cursor:pointer; opacity:0.45;}
.swatch.out-of-stock:hover{transform:none}

/* Unavailable: this specific COMBINATION doesn't exist at all (no variant
   row for it) — opacity as above, PLUS a definite cross so it reads
   unambiguously as "not a real option" rather than "just low stock".
   Round icons get a plain 2px black X running the full width/height of
   the icon: the two bars below are deliberately drawn OVERSIZED (141.5%
   ≈ a square's diagonal ÷ its side) then rely on .swatch's own
   overflow:hidden to crop them back to a perfect corner-to-corner
   diagonal automatically, whatever the swatch's actual rendered size —
   no separate calculation needed. Text/rectangular swatches override
   this further down to a single plain strikethrough line instead.
   cursor:pointer + no pointer-events:none (2026-08-22 feedback) — this
   used to be genuinely unclickable, on the basis that "no data exists
   for this combination" meant there was nothing to select. But the
   actual desired behaviour is: clicking it SHOULD be allowed, and
   should resolve the conflict by adjusting whichever OTHER attribute-
   group is currently selected to whatever value makes this one valid —
   see product_swatches.js's selectSwatchAndResolve(), now used for
   every click instead of the plain click handler that used to just
   silently fail to update anything when the combo didn't exist. */
.swatch.unavailable{cursor:pointer; opacity:0.3;}
.swatch.unavailable:hover{transform:none}
.unavail-cross{
    position:absolute;
    inset:0;
    pointer-events:none;
    display:none;
}
.swatch.unavailable .unavail-cross{display:block;}
.unavail-cross::before, .unavail-cross::after{
    content:'';
    position:absolute;
    top:50%; left:50%;
    width:141.5%;
    height:2px;
    background:#000;
    transform-origin:center;
}
.unavail-cross::before{transform:translate(-50%, -50%) rotate(45deg);}
.unavail-cross::after{transform:translate(-50%, -50%) rotate(-45deg);}

/* Text swatches: ONE straight strikethrough line through the label,
   rather than the round swatches' full X — width is a PERCENTAGE of
   this swatch's own actual rendered width (--text-cross-line-width,
   default 90%), which CSS resolves against the swatch's own computed
   width automatically — no separate per-label JS/PHP calculation
   needed, it just naturally tracks however wide that particular label
   ended up being. */
.swatch.swatch-text .unavail-cross{
    left:50%;
    width:var(--text-cross-line-width, 90%);
    transform:translateX(-50%);
}
.swatch.swatch-text .unavail-cross::before{
    left:0; /* override the round version's left:50% (inherited by specificity order otherwise) */
    width:100%;
    transform:translateY(-50%); /* straight line, no rotation */
}
.swatch.swatch-text .unavail-cross::after{
    display:none; /* one line, not an X, for text swatches */
}

/* Shared hover popup — one instance per page, positioned/populated by
   product_swatches.js regardless of which page it's on. Was duplicated
   byte-for-byte between product_page.css and product_grid.css; both now
   just reference this. */
.popup-container{position:fixed;z-index:9999;pointer-events:none;background:#fff;padding:2px;border-radius:6px;box-shadow:0 4px 16px rgba(0,0,0,0.15);border:1px solid var(--card-border-grey, #e5e7eb);width:100px;display:none}
.popup-container object{width:100%;height:auto;display:block}
.popup-container::after{content:'';position:absolute;bottom:-8px;left:50%;transform:translateX(-50%);width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent;border-top:8px solid #fff;filter:drop-shadow(0 1px 2px rgba(0,0,0,0.1))}