/*
Theme Name: Technical Shop
Theme URI: https://shop.sergejkeller.xyz
Author: Sergej Keller
Description: A light technical-storefront block theme for WooCommerce. Derived from Technical Notes: the same serif headlines and blue/cyan/turquoise accent system, re-grounded on paper white and widened for product grids. Path-style breadcrumbs and print-inspired rules carry over; the journal apparatus does not.
Version: 1.7.0
Requires at least: 6.5
Tested up to: 6.9
Requires PHP: 7.4
License: GNU General Public License v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: technical-shop
Tags: e-commerce, light, full-width-template, block-patterns, full-site-editing
*/

/* Instrument Serif — self-hosted (SIL OFL), no external font requests */
@font-face {
  font-family: "Instrument Serif";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("assets/fonts/instrument-serif-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Instrument Serif";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("assets/fonts/instrument-serif-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Instrument Serif";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("assets/fonts/instrument-serif-italic-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Instrument Serif";
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url("assets/fonts/instrument-serif-italic-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* The light ramp, mirrored from the Technical Notes dark one: the sheet is
   the brightest surface and every panel steps *down* from it, where the dark
   theme stepped up. Same three accents, re-darkened — a hue that reads as an
   accent against #17191B is a pastel against white, and #5FD4E0 measures
   1.63:1 on the sheet, which is not a colour you can put text in.

   Every value below was measured against all four backgrounds it can land
   on (sheet #FFFFFF, ground #F7F8F9, surface #F0F2F4, deep #ECEFF1) rather
   than against white alone, because the panels are where the accents get
   closest to failing. Three of the agreed values needed a nudge to survive
   that: muted #667075 → #616B70, cyan #0E7C8A → #0C707C and turquoise
   #12806A → #0F7460, each of which was between 4.2 and 4.4:1 on the deep
   panel — fine on the sheet, under AA exactly where product cards and the
   cart totals put small copy. Blue was already clear at 4.80 and is
   unchanged. Worst case across the whole table is now 4.73:1. */
:root {
  --ts-bg: #F7F8F9;
  --ts-deep: #ECEFF1;
  --ts-page: #FFFFFF;
  --ts-surface: #F0F2F4;
  --ts-line: #DFE3E6;
  --ts-line-soft: #E9ECEE;
  --ts-text: #2B3134;
  --ts-heading: #14181A;
  --ts-muted: #616B70;
  /* --ts-faint is decoration only: rules, field edges, card borders. It is
     2.15:1 on the page sheet, so it must never carry text. --ts-dim is the
     dimmest tone that still clears WCAG AA 4.5:1 for small copy (5.69:1 on
     the page sheet, 4.93:1 on the deep panel). */
  --ts-faint: #A9B2B8;
  --ts-dim: #5F686D;
  --ts-cyan: #0C707C;
  --ts-blue: #1F6BB0;
  --ts-turquoise: #0F7460;
  /* Non-text only: scrollbar thumb, glass field edge. 3.03:1 on the sheet,
     which is the 3:1 minimum for a UI affordance and nothing more. */
  --ts-cyan-dim: #5E9EA9;
  /* Resting underline under prose links. The bright cyan bar sweeps in over
     this on hover, so it carries no text itself — but it is the affordance
     that says "link" before the pointer arrives, and an affordance needs the
     3:1 non-text minimum. 3.20:1 on the sheet. */
  --ts-link-rest: #4E9BA6;
  /* Channel triplets for the cursor-following "glass" that buttons, fields,
     pagination and table rows share. They are raw `r, g, b` rather than
     colours because every use multiplies a per-component alpha by --glass,
     and rgba() is the only form that takes a variable alpha without
     color-mix().

     The dark theme built that highlight out of light: a near-white specular
     core (#E2FAFD) fading through cyan. On a white sheet there is no lighter
     to go, so the light theme inverts the axis and builds it out of the
     accent instead — the lit edge is now the *darkest* point at the cursor
     and fades outward to --ts-cyan-dim. The glow alphas carry over unchanged
     (cyan at .16–.20 reads the same on either ground), while the specular
     alpha rises .85 → .95 and the outer edge .18 → .45, because a tint that
     sat on top of a dark surface has to sink into a light one to be seen. */
  --ts-glow-rgb: 12, 112, 124;
  --ts-edge-lo-rgb: 94, 158, 169;
  /* Shadows and flat washes. Near-black rather than pure black: a neutral
     #000 shadow on a slightly cool white reads brown at low alpha. */
  --ts-shade-rgb: 20, 24, 26;
  --ts-serif: "Instrument Serif", "Iowan Old Style", Georgia, "Times New Roman", serif;
  --ts-sans: system-ui, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
  --ts-mono: ui-monospace, "Cascadia Code", "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;
  color-scheme: light;
}

/* Neither element otherwise gets a background of its own — only the inner
   containers (.ts-page, .ts-header, .ts-footer…) paint, on the assumption
   the html/body canvas is never actually visible. On iOS Safari it is: the
   rubber-band bounce past the top (or bottom) of the page reveals the
   <html> element's own background, and the sticky header's backdrop-filter
   blur, sitting right at that boundary, samples it during a bounce. The
   mismatch is smaller here than it was on the dark theme — ground is nearly
   white — but a bounce would still show a hard edge where #F7F8F9 meets the
   UA default. Desktop browsers (and Chromium's simulated mobile viewports)
   don't rubber-band, which is why it never shows up there. */
html {
  background: var(--ts-bg);
}

body {
  -webkit-font-smoothing: antialiased;
}

/* ── themed scrollbar ───────────────────────────────── */

::-webkit-scrollbar {
  width: 12px;
  height: 12px;
}

::-webkit-scrollbar-track {
  background: var(--ts-deep);
}

::-webkit-scrollbar-thumb {
  background: var(--ts-line);
  border: 3px solid var(--ts-deep);
  border-radius: 6px;
}

::-webkit-scrollbar-thumb:hover {
  background: var(--ts-cyan-dim);
}

::-webkit-scrollbar-corner {
  background: var(--ts-deep);
}

/* Firefox only — in Chromium, scrollbar-color would disable the
   ::-webkit-scrollbar styling above */
@supports (-moz-appearance: none) {
  html {
    scrollbar-color: var(--ts-line) var(--ts-deep);
  }
}

/* pin the footer to the viewport bottom on short pages */
.wp-site-blocks {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-height: 100svh;
}

/* main sets both cross-axis margins to auto (via .ts-shell), which under
   flexbox exempts it from stretching — it would otherwise shrink to fit
   its content, so template types with only short rows (search, archive)
   rendered narrower than ones containing long paragraphs. */
.wp-site-blocks > main {
  flex-grow: 1;
  width: 100%;
  /* present unconditionally (not just alongside the :has() blur rule
     further down) so the transition still runs when the blur is
     removed, not just when it's applied */
  transition: filter .3s ease;
}

a:focus-visible {
  outline: 2px solid var(--ts-cyan);
  outline-offset: 3px;
  border-radius: 2px;
}

/* ── shells and page sheets ─────────────────────────── */

/* The blog held this at 960px because everything inside it was a single
   column of prose. A storefront is the opposite problem: the grid wants to
   show four products across before it wraps, and a 960px shell caps it at
   three with slack on both sides. 1440px matches theme.json's wideSize, so
   an alignwide block and the shell agree on where the page ends — and the
   masthead and footer use the same shell, which is what keeps the header
   rule, the product grid and the footer columns on one set of margins
   instead of three.

   Prose does not follow it out there: .ts-article below re-centres the sheet
   at a readable width, so the legal pages stay a column and only the
   storefront actually spends the 1440. */
.ts-shell {
  max-width: 1440px;
  margin-left: auto;
  margin-right: auto;
  padding-left: 32px;
  padding-right: 32px;
}

@media (max-width: 700px) {
  .ts-shell {
    padding-left: 20px;
    padding-right: 20px;
  }
}

/* Templates that render authored prose (pages, the prose template) keep the
   old measure by centring the sheet itself rather than the shell — the
   masthead above it still spans the full width. */
.ts-article .ts-page {
  max-width: 940px;
  margin-left: auto;
  margin-right: auto;
}

.ts-page {
  background: var(--ts-page);
  border: 1px solid var(--ts-line);
  border-radius: 3px;
  padding: 44px 52px 40px;
  margin: 48px 0;
  /* The dark theme kept this shadow tight (16px) and deep (.38) to dodge
     8-bit banding: on a #17191B ground the ramp spanned about four luma
     levels and quantised into visible steps. On a near-white ground the
     opposite constraint applies — there are plenty of levels to spend, and
     the danger is weight, not banding. .38 over white is a smudge. The
     geometry is kept and the alphas drop by roughly 5x to .05/.08, which
     lands the sheet just off the ground rather than floating over it. */
  box-shadow: 0 1px 0 rgba(var(--ts-shade-rgb), .05), 0 7px 16px rgba(var(--ts-shade-rgb), .08);
}

@media (max-width: 700px) {
  .ts-page { padding: 28px 22px 26px; }
}

/* thick-thin print rule under section labels */
.ts-rule-head {
  position: relative;
  padding-bottom: 9px;
  margin-bottom: 22px;
  border-bottom: 2px solid var(--ts-line);
}

.ts-rule-head::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -5px;
  height: 1px;
  background: var(--ts-line);
}

.ts-smallcaps {
  font-family: var(--ts-sans);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .22em;
  text-transform: uppercase;
}

/* ── masthead ───────────────────────────────────────── */

.ts-header {
  display: block;
  position: sticky;
  top: -1px;
  z-index: 100;
  background: var(--ts-deep);
  border-bottom: 1px solid var(--ts-line);
  transition: background-color .35s ease;
}

/* The frosted glass lives on an overlay that overshoots the top edge,
   so the backdrop-filter's sampling boundary sits off-screen — its
   on-screen edge is what flickers in Chromium while scrolling. Painted
   above the header's own background (negative z-index child of a
   stacking context) but under the content; blurring the uniform tint
   is a no-op, so the effect only shows once the tint goes translucent.
   saturate(180%) + blur(20px) is the Apple frosted-glass recipe —
   the saturation keeps colors behind the glass vivid. */
.ts-header::before {
  content: "";
  position: absolute;
  top: -12px;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: -1;
  transform: translateZ(0);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  transition: -webkit-backdrop-filter .35s ease, backdrop-filter .35s ease;
}

/* compact mode — html.ts-stuck is set by theme.js past the page top */

html.ts-stuck .ts-header {
  background: rgba(255, 255, 255, .72);
}

/* While the mobile menu is up the header goes fully opaque and drops
   the glass entirely, so it and the dropdown hanging off it read as one
   flat, continuous surface with nothing showing through either of them.
   The dropdown (further down) sets the same var(--ts-deep) fill for the
   same reason; matching them on a *translucent* tint instead can't work
   — they are separate elements, so each backdrop-filter samples
   whatever happens to be behind itself and the two drift out of sync as
   the page scrolls.

   Keyed off .is-menu-open directly rather than a lingering helper
   class: core clears it the instant a close starts, which is exactly
   when the header should begin fading back to its resting (or stuck)
   tint, in step with the panel rolling up. Outranks the html.ts-stuck
   rule above on specificity, so the compact header gives up its
   translucency for the duration too. */
html:has(.ts-nav .wp-block-navigation__responsive-container.is-menu-open) .ts-header {
  background: var(--ts-deep);
}

html:has(.ts-nav .wp-block-navigation__responsive-container.is-menu-open) .ts-header::before {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

.ts-mast {
  /* anchors the mobile nav dropdown (below) to "right after the brand/nav
     row" — its padding box is exactly where .ts-crumb-bar begins, so
     top:100% there needs no JS measurement and holds regardless of
     resting/stuck sizing. */
  position: relative;
  padding-top: 27px;
  padding-bottom: 6px;
  transition: padding .35s ease;
}

html.ts-stuck .ts-mast {
  padding-top: 10px;
  padding-bottom: 0;
}

/* centred, not baseline: the search field has no text baseline to share
   with the site title, and baseline alignment dropped it ~19px low */
.ts-mast-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
}

.ts-brand {
  display: flex;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
}

.ts-brand .wp-block-site-title {
  font-family: var(--ts-serif);
  font-weight: 400;
  letter-spacing: .025em;
  word-spacing: .06em;
  color: var(--ts-heading);
  font-size: clamp(1.8rem, 3.4vw, 2.4rem);
  line-height: 1.1;
  margin: 0;
  transition: font-size .35s ease;
}

html.ts-stuck .ts-brand .wp-block-site-title {
  font-size: 1.4rem;
}

.ts-brand .wp-block-site-title a {
  color: inherit;
  text-decoration: none;
}

.ts-brand-em {
  font-family: var(--ts-serif);
  font-style: italic;
  color: var(--ts-cyan);
  font-size: clamp(1.5rem, 2.8vw, 1.95rem);
  margin: 0;
  transition: font-size .35s ease;
}

html.ts-stuck .ts-brand-em {
  font-size: 1.15rem;
}

/* ── header search ──────────────────────────────────── */

.ts-mast-aside {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 10px;
  /* core's flow layout gives group children margin-block-start: 24px,
     which pushed the field below the title's centre line */
  margin: 0;
  align-self: center;
  transition: gap .35s ease;
}

.ts-search {
  margin: 0;
  max-height: 40px;
  overflow: hidden;
  visibility: visible;
  transition: opacity .3s ease, max-height .35s ease, transform .35s ease,
              visibility 0s linear 0s;
}

/* The compact masthead used to collapse the search field away at every
   width. Above 700px it does not have to: the field shares the brand row
   there rather than taking one of its own, so keeping it costs 4px of
   header height (measured: 88 -> 92px at 1440, 1024 and 760px) and the row
   still does not wrap.

   Between 600 and 700px .ts-mast-aside is full-width and would wrap the
   field onto a row of its own, so while stuck it is shrunk back to its
   content and joins the brand row instead — there is 385px of free space
   next to the brand at 700px and the field needs 245px.

   Below 600px the hamburger exists, and the field lives inside it as the
   menu's first item (moved there by theme.js) rather than in the header
   at all. */
@media (min-width: 600px) and (max-width: 700px) {
  html.ts-stuck .ts-mast-aside {
    gap: 0;
    width: auto;
  }
}

/* Once theme.js has moved the field into the menu this group holds
   nothing, and an empty full-width flex item still takes a wrapped row of
   its own — measured 24px of .ts-mast-row gap spent on a zero-height line.
   The class is set by theme.js rather than matching :empty, which never
   fired: the block template leaves two whitespace text nodes inside the
   group, and :empty counts those as content. */
.ts-mast-aside.is-vacated {
  display: none;
}

/* The cart and account controls used to live in this group, which meant the
   vacated class took them off screen along with the search field on mobile.
   They now sit in .ts-mast-sub instead — below the rule, opposite the
   navigation, where the blog puts its social links — so this group is once
   again only the search field, and can collapse exactly as it did there. */

/* Below the rule, opposite the navigation. .ts-mast-sub is already
   space-between, which puts the nav left and this right; the auto margin
   keeps it right even though the tagline is absent (so the row has only two
   items), and when the nav wraps and this lands on a line of its own. */
.ts-mast-sub .ts-shop-actions {
  margin-left: auto;
}

/* ── search inside the mobile menu ──────────────────────
   The field is the menu's first item below 600px. It is a real child of
   core's panel content, so it inherits the panel's 24px inset and its
   open/close clip-path for free — nothing here has to animate. */
.ts-search.ts-search-in-menu {
  width: 100%;
  align-self: stretch;
  max-height: none;
  /* No rule under the field, and no margin either. It read as a stray mark
     against the first menu link rather than as a separator, and the margin
     was doubling the panel's own 24px row-gap to 48px — the field now sits
     on the same rhythm as the links it precedes. */
  margin: 0;
}

.ts-search.ts-search-in-menu .wp-block-search__input,
.ts-search.ts-search-in-menu .wp-block-search__input:focus {
  width: 100%;
}

/* Same four-layer glass as the buttons. The masthead background is
   --ts-deep, so the resting fill stays --ts-surface to keep the field
   from disappearing into the header, and the resting edge is --ts-faint
   rather than the buttons' dimmer --ts-line. */
/* the glass itself is shared with the text inputs, further down */
.ts-search .wp-block-search__inside-wrapper {
  display: flex;
  align-items: center;
  padding: 0;
  position: relative;                 /* anchors the console prompt ghost */
}

.ts-search .wp-block-search__input {
  width: 190px;
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: 6px 10px;
  font-family: var(--ts-mono);
  font-size: 12.5px;
  color: var(--ts-text);
  transition: width .3s ease;
}

.ts-search .wp-block-search__input:focus {
  outline: none;
  width: 230px;
}

.ts-search .wp-block-search__input::placeholder {
  color: var(--ts-muted);
}

/* the icon button drops the block button's border and sweep */
.ts-search .wp-block-search__button {
  background: transparent;
  background-image: none;
  border: 0;
  border-left: 1px solid var(--ts-faint);
  border-radius: 0;
  padding: 5px 9px;
  min-width: 0;
  color: var(--ts-text);
  box-shadow: none;
  transition: color .2s ease;
}

/* stays transparent on hover: an opaque fill here would mask the field's
   own glass and read as a dark patch under the cursor */
.ts-search .wp-block-search__button:hover {
  background: transparent;
  background-image: none;
  color: var(--ts-cyan);
  box-shadow: none;
}

.ts-search .wp-block-search__button:active {
  transform: none;
}

.ts-search .wp-block-search__button svg {
  width: 19px;
  height: 19px;
  fill: currentColor;
  display: block;
}

/* ── mobile nav: header-attached dropdown, not a full-screen modal ──
   overlayMenu:"mobile" on the wp:navigation block (parts/header.html)
   turns the flat 4-item list into a single toggle button below core's
   own fixed 600px breakpoint. Core's own default for that toggle is a
   full-viewport modal (position:fixed; inset:0) — overridden below so
   the panel instead drops down from the bottom of the brand/nav row,
   as an extension of the header itself: the header stays in view, the
   breadcrumb bar is overdrawn by the panel rather than pushed down, and
   the page content beneath is blurred instead of hidden behind a solid
   scrim. The extra .ts-nav class in each selector is what wins
   specificity over core's own `:not(.has-background)` /
   `:not(.has-text-color)` fallback rules, which are otherwise exactly
   as specific.

   Core reuses the exact same list markup for two very different jobs:
   below 600px it's this dropdown (.is-menu-open), at 600px and up the
   very same .wp-block-navigation__responsive-container-content is simply
   redisplayed in place as the ordinary flat nav row (see core's own
   `@media (min-width:600px) …:not(.is-menu-open) { display:block; … }`).
   Styling ".wp-block-navigation-item a" without the .is-menu-open guard
   therefore doesn't just style the dropdown — it also overrides the
   existing .ts-nav a rule (size, tracking, underline sweep) on the
   ordinary desktop nav, which is not what these rules are for. Every
   selector below is scoped to .is-menu-open so the flat/resting nav is
   left to .ts-nav a untouched. */

.ts-nav .wp-block-navigation__responsive-container-open,
.ts-nav .wp-block-navigation__responsive-container-close {
  align-items: center;
  justify-content: center;
  color: var(--ts-text);
  padding: 10px;
  transition: color .2s ease;
}

/* Unconditional display:flex here would beat core's own
   `@media (min-width:600px) …:not(.always-shown){ display:none; }` rule
   (same specificity, later in the cascade) and leave the hamburger toggle
   visible on desktop even though all nav items are already shown flat.
   Scoping it to the same sub-600px range core uses keeps that hide rule
   intact while still giving the mobile toggle its layout below it. */
@media (max-width: 599.98px) {
  .ts-nav .wp-block-navigation__responsive-container-open,
  .ts-nav .wp-block-navigation__responsive-container-close {
    display: flex;
  }
}

.ts-nav .wp-block-navigation__responsive-container-open:hover,
.ts-nav .wp-block-navigation__responsive-container-close:hover {
  color: var(--ts-cyan);
}

/* Hamburger <-> close: a real bar morph, not a swap between two icons.
   Core's two SVGs (a two-bar glyph vs. an X path) can't be interpolated
   into each other — there's no shared path to animate between. theme.js
   replaces -open's SVG with three real <span class="ts-burger-bar">
   elements instead, so the outer bars can rotate 45°/-45° and slide to
   the middle bar's line while the middle bar itself fades away, the same
   three-line-crossing-into-a-cross animation as a typical native
   hamburger-menu icon. -close keeps no visible icon of its own — it
   stays exactly where -open is (see the negative top offset below) as
   an invisible hit target so a real click/tap lands on core's own
   closeMenuOnClick, while the animated bars living on -open are what the
   user actually sees turn into an X. */
.ts-nav .wp-block-navigation__responsive-container-open svg,
.ts-nav .wp-block-navigation__responsive-container-close svg {
  display: none;
}

.ts-burger {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  width: 22px;
  height: 16px;
}

.ts-burger-bar {
  display: block;
  width: 100%;
  height: 2px;
  border-radius: 1px;
  background: currentColor;
  transform-origin: center;
  transition: transform .35s ease, opacity .35s ease;
}

.ts-nav:has(.wp-block-navigation__responsive-container.is-menu-open) .ts-burger-bar:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}

.ts-nav:has(.wp-block-navigation__responsive-container.is-menu-open) .ts-burger-bar:nth-child(2) {
  opacity: 0;
  transform: scaleX(0);
}

.ts-nav:has(.wp-block-navigation__responsive-container.is-menu-open) .ts-burger-bar:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}

.ts-nav .wp-block-navigation__responsive-container-close {
  opacity: 0;
  pointer-events: none;
}

.ts-nav:has(.wp-block-navigation__responsive-container.is-menu-open) .wp-block-navigation__responsive-container-close {
  pointer-events: auto;
}

.ts-nav:has(.wp-block-navigation__responsive-container.is-menu-open) .wp-block-navigation__responsive-container-open {
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .ts-burger-bar {
    transition: none;
  }
}

/* Core positions this dialog as a full-viewport fixed overlay
   (position:fixed; inset:0) unconditionally, sized to cover the whole
   screen. Repositioned here to hang instead from .ts-mast — the nearest
   positioned ancestor once .ts-mast carries position:relative above —
   so top:100% always lands exactly at .ts-mast's own bottom edge
   (where .ts-crumb-bar begins) regardless of the resting/stuck header
   height, with no JS measurement needed. bottom:auto lets it size to
   its own content instead of stretching to the viewport's bottom edge;
   max-height + overflow guard against a very long menu on a very short
   screen. Because it's now anchored inside .ts-mast's own padding box,
   left/right:0 already lands it at the shell's 24px inset — the same
   content width the breadcrumb text uses, so the panel's edges line up
   with what it's covering. */
/* Core sets `.wp-block-navigation { position: relative; }` unconditionally
   on the <nav> itself (block-library/style.css) — normally there to anchor
   dropdown submenus. This nav has none, but it's still a *closer*
   positioned ancestor than .ts-mast, so without this override the panel
   below anchored to <nav>'s own small, button-sized box instead of
   .ts-mast — landing narrow and only one row down rather than spanning
   the header and clearing the whole brand/nav row. Same specificity as
   core's rule (one class each); this wins on source order alone since
   the theme stylesheet loads after core's. */
.ts-nav {
  position: static;
}

/* ── mobile nav dropdown ────────────────────────────────
   Core drives this panel with a hard display:none <-> flex swap on
   .is-menu-open, with no transition step to animate through — and it
   drops every bit of its own dropdown styling in that same instant, so
   anything keyed off that class snapped back to core's flat-nav
   defaults while the panel was still visibly on screen: the items
   reflowed into a horizontal row partway through a close.

   Everything below is therefore keyed off the mobile breakpoint alone,
   never .is-menu-open — the panel is always laid out and always styled
   as a dropdown, and only clip-path/visibility toggle. Nothing has to
   revert mid-animation, so nothing can flash. It costs no space while
   closed (the panel is absolutely positioned) and shows nothing (it is
   clipped to zero height and visibility:hidden).

   The reveal is clip-path rather than transform or height:
   • transform is unusable — core runs its own ~100ms open animation on
     this element through the Web Animations API, and a running WAAPI
     effect overrides the cascade (!important included) for whatever
     properties it targets, which are transform and opacity.
   • height/max-height is unusable — overflow has to stay visible for
     -close, which sits 60px above this box's own top edge (see the
     negative offsets below), and an overflow-clipped ancestor would cut
     that hit target off.
   Sweeping the bottom inset 100% -> 0% gives the same top-to-bottom
   unroll. The top inset stays pinned at -60px rather than 0 so -close
   is never clipped away, in the resting header height or the stuck one.

   The wordy selectors are specificity, not decoration: core styles
   these same elements with four class-level selectors of its own under
   .is-menu-open, and its `:not(.hidden-by-default):not(.is-menu-open)`
   rule (three classes) otherwise drags the panel back to
   position:relative/display:block the moment is-menu-open goes away.
   Each rule here carries one class more than core's equivalent so it
   wins outright rather than on stylesheet order. */
@media (max-width: 599.98px) {
  .ts-nav.wp-block-navigation .wp-block-navigation__responsive-container:not(.disable-default-overlay) {
    display: flex !important;
    flex-direction: column;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    bottom: auto;
    width: auto;
    height: auto;
    overflow: visible;
    z-index: 60;
    /* Opaque, and the same fill the header is forced to for as long as
       the menu is up (see the :has(.is-menu-open) .ts-header rule near
       the top), so the two read as one continuous surface — no glass,
       no translucency, nothing showing through from behind either.
       These two carry !important because core's own closed-state rule
       does: it forces `background-color: inherit !important` (and the
       same for color) on this element, which no amount of specificity
       can outrank — with a normal declaration here the panel fell back
       to transparent the moment a close began, and to core's flat white
       while open. */
    background-color: var(--ts-deep) !important;
    color: var(--ts-text) !important;
    border-bottom: 1px solid var(--ts-line);
    box-shadow: 0 16px 32px rgba(var(--ts-shade-rgb), .14);
    clip-path: inset(-60px 0 100% 0);
    visibility: hidden;
    pointer-events: none;
    transition: clip-path .3s ease, visibility 0s linear .3s;
  }

  .ts-nav.wp-block-navigation .wp-block-navigation__responsive-container.is-menu-open:not(.disable-default-overlay) {
    clip-path: inset(-60px 0 0% 0);
    visibility: visible;
    pointer-events: auto;
    transition: clip-path .3s ease, visibility 0s linear;
  }

  /* box-sizing is core's, and only under .is-menu-open — without it the
     content box below starts counting its 24px side padding outside its
     own width partway through a close. */
  .ts-nav.wp-block-navigation .wp-block-navigation__responsive-container:not(.disable-default-overlay) .wp-block-navigation__responsive-close,
  .ts-nav.wp-block-navigation .wp-block-navigation__responsive-container:not(.disable-default-overlay) .wp-block-navigation__responsive-dialog,
  .ts-nav.wp-block-navigation .wp-block-navigation__responsive-container:not(.disable-default-overlay) .wp-block-navigation__responsive-container-content {
    box-sizing: border-box;
  }

  /* The column layout is core's as well, and likewise only under
     .is-menu-open — the list's own base rule is a horizontal
     `display:flex; flex-wrap:wrap` row, which is exactly what flashed
     through as the panel collapsed.

     The padding replaces core's, whose horizontal inset is a clamp()
     reading --wp--style--root--padding-left/right — custom properties
     this theme's theme.json never defines. An undefined var() inside
     clamp() invalidates the whole declaration, so the browser drops it
     rather than substituting 0, and the list rendered flush against the
     panel edge; 24px is what the rest of the shell uses. Core's
     generous top clearance is unnecessary too, now that -close sits up
     in the header row rather than inside this content box. */
  .ts-nav.wp-block-navigation .wp-block-navigation__responsive-container:not(.disable-default-overlay) .wp-block-navigation__responsive-container-content {
    display: flex;
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: flex-start;
    padding: 20px 24px 0;
    /* the scroll safety net for a very long menu on a very short
       screen, scoped to the list rather than the panel, whose overflow
       has to stay visible (see above) */
    max-height: 70vh;
    overflow-y: auto;
  }

  .ts-nav.wp-block-navigation .wp-block-navigation__responsive-container:not(.disable-default-overlay) .wp-block-navigation__container {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
  }

  /* A hairline between items and a bit more room per row is what the
     flat colour list was missing — the same quiet-rule language as the
     contents list and CV timeline elsewhere. The rows deliberately keep
     core's shrink-to-fit width, so each rule runs only as wide as the
     label it sits under. */
  .ts-nav.wp-block-navigation .wp-block-navigation__responsive-container:not(.disable-default-overlay) .wp-block-navigation-item {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    border-bottom: 1px solid var(--ts-line-soft);
  }

  .ts-nav.wp-block-navigation .wp-block-navigation__responsive-container:not(.disable-default-overlay) .wp-block-navigation-item:last-child {
    border-bottom: 0;
  }

  .ts-nav.wp-block-navigation .wp-block-navigation__responsive-container:not(.disable-default-overlay) .wp-block-navigation-item a {
    display: block;
    font-family: var(--ts-sans);
    font-size: 15px;
    font-weight: 600;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--ts-text);
    text-decoration: none;
    padding: 14px 0;
    transition: color .2s ease, transform .2s ease;
  }

  .ts-nav.wp-block-navigation .wp-block-navigation__responsive-container:not(.disable-default-overlay) .wp-block-navigation-item a:hover {
    color: var(--ts-cyan);
    transform: translateX(6px);
  }
}

/* Matches the specificity of the two panel rules above (and the page
   scrim's further down) and sits after them, so it wins the tie. */
@media (prefers-reduced-motion: reduce) {
  .ts-nav.wp-block-navigation .wp-block-navigation__responsive-container:not(.disable-default-overlay),
  .ts-nav.wp-block-navigation .wp-block-navigation__responsive-container.is-menu-open:not(.disable-default-overlay),
  .ts-header ~ main,
  .ts-header ~ footer {
    transition: none;
  }
}

/* -close is nested inside the panel above, so it inherits *that* as its
   positioned ancestor — its containing block's top-left corner is
   therefore .ts-mast's own bottom-left corner (0, panel top). -open's
   own on-screen box was measured at (24, 153) resting and (24, 54)
   stuck, i.e. 60px / 46px above that same corner — so a negative `top`
   of that distance, with the same 24px `left` the shell uses everywhere
   else, lands -close exactly over -open instead of at core's default
   top:0;right:0 (the panel's own top-right corner). Left is used rather
   than right because .ts-mast-sub renders the tagline first and it's
   normally empty, which puts the nav — and so -open — flush at the
   shell's left edge, not the right. */
.ts-nav .wp-block-navigation__responsive-container-close {
  top: -60px;
  left: 24px;
  right: auto;
}

html.ts-stuck .ts-nav .wp-block-navigation__responsive-container-close {
  top: -46px;
}

/* ── blur the page behind the open dropdown ─────────────
   Same idea as the lightbox scrim: rather than hiding the content below
   the header, blur it in place so the page still reads as "there, just
   not what has focus right now".

   The transition lives on the resting rule rather than the open one so
   it runs in both directions — declared only on the open rule, the
   un-blur has no transition to inherit once that rule stops matching
   and snaps sharp instead. And the trigger is .is-menu-open, which core
   clears the instant a close begins, so the page sharpens over the same
   300ms the panel takes to roll back up rather than waiting for the
   roll to finish first. pointer-events:none keeps a blurred, unfocused
   page from still being clickable/scrollable underneath the dropdown. */
.ts-header ~ main,
.ts-header ~ footer {
  transition: filter .3s ease;
}

html:has(.ts-nav .wp-block-navigation__responsive-container.is-menu-open) .ts-header ~ main,
html:has(.ts-nav .wp-block-navigation__responsive-container.is-menu-open) .ts-header ~ footer {
  filter: blur(8px) brightness(.7);
  pointer-events: none;
  user-select: none;
}

/* Retuned for the shop's composition. On the blog this row carried a
   tagline, the navigation and a social strip, and space-between spread the
   three across the width; the asymmetric padding underneath it was balancing
   against that. The shop row holds the navigation alone, and the site has no
   tagline set, so the old rhythm read as arbitrary gaps — 30px above the nav
   against 12px below, under a masthead with 27px of top padding and 6px of
   bottom. The spacing is symmetric around the rule now: equal air above and
   below the nav, and the same proportion kept when the header collapses. */
.ts-mast-sub {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  margin-top: 18px;
  padding: 12px 0;
  /* --ts-line-soft (#E9ECEE) is the value the blog used, where it sat on a
     near-black masthead and read clearly. On this one it is a two-level step
     off the ground and effectively invisible — the rule that separates the
     brand row from the navigation was not doing its job. Stepped down to a
     tone that actually registers as a line without becoming a border. */
  border-top: 1px solid #C7D0D5;
  transition: margin-top .35s ease, padding .35s ease;
}

html.ts-stuck .ts-mast-sub {
  margin-top: 8px;
  padding: 7px 0;
}

.ts-tagline {
  font-family: var(--ts-serif);
  font-style: italic;
  font-size: 1.1rem;
  color: var(--ts-muted);
  margin: 0;
}

/* a sibling of the nav, so space-between parks it at the right edge;
   margin-left keeps it there even if a site tagline is ever set, and
   margin: 0 defeats core's flow-layout margin-block-start */
.ts-social {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0 0 0 auto;
}

/* 24x24 hit area (WCAG 2.2 SC 2.5.8) around an 18x18 mark. The gap drops
   from 15px to 9px so the icons keep their original 33px centre-to-centre
   pitch — the targets grow, the rhythm does not change. */
.ts-social-link {
  display: block;
  box-sizing: border-box;
  width: 24px;
  height: 24px;
  padding: 3px;
  color: var(--ts-muted);
  transition: color .2s ease, transform .2s ease;
}

.ts-social-link:hover {
  color: var(--ts-cyan);
  transform: translateY(-1px);
}

.ts-social-link svg {
  display: block;
  width: 100%;
  height: 100%;
}

.ts-nav a {
  font-family: var(--ts-sans);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--ts-muted);
  text-decoration: none;
  /* 4px block padding lifts the 19px line box to a 27px target
     (WCAG 2.2 SC 2.5.8); the underline rides the padding-box bottom
     edge, so it stays put relative to the text */
  padding: 4px 0;
  background-image: linear-gradient(var(--ts-cyan), var(--ts-cyan));
  background-repeat: no-repeat;
  background-position: right bottom;
  background-size: 0% 2px;
  transition: color .2s ease, background-size .28s ease;
}

/* underline grows in from the left, exits to the right (the
   background-position jump is deliberately not transitioned) */
.ts-nav a:hover {
  color: var(--ts-cyan);
  background-position: left bottom;
  background-size: 100% 2px;
}

.ts-nav .current-menu-item > a,
.ts-nav [aria-current="page"] {
  color: var(--ts-text);
  background-position: left bottom;
  background-size: 100% 2px;
}

/* ── breadcrumb bar (path style) ────────────────────── */

.ts-crumb-bar {
  background: var(--ts-deep);
  border-bottom: 1px solid var(--ts-line-soft);
  transition: background-color .35s ease;
}

/* stays visible in compact mode — just goes translucent so the
   header's backdrop blur shows through */
html.ts-stuck .ts-crumb-bar {
  background: transparent;
}

.ts-crumb-bar .wp-block-shortcode { padding: 9px 0; }

.ts-crumbs {
  display: flex;
  align-items: baseline;
  font-family: var(--ts-mono);
  font-size: 12.5px;
  color: var(--ts-dim);
  white-space: nowrap;
  overflow: hidden;
  /* breathing room under the trail, on the nav rather than the shortcode
     wrapper around it so the caret's descender below the baseline is
     inside the padding rather than against the bar's edge */
  padding-bottom: 4px;
}

.ts-crumbs a {
  flex: none;
  color: var(--ts-muted);
  text-decoration: none;
}

.ts-crumbs a:hover { color: var(--ts-cyan); }
.ts-crumbs .sep { flex: none; margin: 0 .55em; }

/* ~/notes/glossary are short and fixed; the current-page title (the last
   segment) is the one part that's ever long enough to wrap the trail
   onto a second line — a long post title or archive label. Truncating
   just that segment, rather than the whole trail, keeps the "you are
   here" path visible up to its own name instead of losing the leading
   path context off one end. */
.ts-crumbs .here {
  overflow: hidden;
  min-width: 0;
  color: var(--ts-cyan);
  text-overflow: ellipsis;
}

/* A terminal caret closing the path, since the trail already reads as one
   (`~ / glossary / canopen` in mono). Drawn as a box rather than an "_"
   glyph so its weight and length are set here instead of inherited from
   whatever mono face the visitor actually has — underscore thickness and
   how far it sits below the baseline both vary between families, and a
   glyph would also be selectable as text.

   align-self: baseline puts the box's bottom edge on the text baseline,
   which is where an underscore belongs: it reads as attached to the last
   segment rather than trailing after it.

   It rides on the nav's ::after, not on .here::after, because .here is the
   element that ellipsises: a caret inside it would be clipped by that same
   overflow the moment a title got long. Sitting outside, it stays put at
   the end of the trail.

   Only above 600px. The longest trail on the site (the soldering-iron
   post) has 139px to spare there, but just 19px at 480 and it ellipsises
   below ~460 — and a caret trailing an "…" reads as a glitch rather than a
   prompt. Phones also hold the breadcrumb closer to the eye, where a
   moving element earns its keep least. */
@media (min-width: 600px) {
  .ts-crumbs::after {
    content: "";
    flex: none;
    align-self: baseline;
    width: .62em;
    height: .13em;
    margin-left: .45em;
    background: var(--ts-cyan);
    /* 1.8s and eased, so it swells and fades rather than snapping: a hard
       toggle at the usual ~500ms terminal rate is exactly the flicker that
       pulls the eye away from the text below it. */
    animation: ts-caret 1.8s ease-in-out infinite;
  }
}

@keyframes ts-caret {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .ts-crumbs::after {
    animation: none;
    opacity: .55;
  }
}

/* ── pagination ─────────────────────────────────────── */

.wp-block-query-pagination {
  display: flex;
  gap: 18px;
  margin-top: 26px;
  font-family: var(--ts-mono);
  font-size: 13px;
  align-items: center;
}

.wp-block-query-pagination-previous,
.wp-block-query-pagination-next,
.wp-block-query-pagination .page-numbers {
  --glass: 0;
  --mx: 50%;
  --my: 50%;
  position: relative;
  font-family: var(--ts-mono);
  font-size: 13px;
  letter-spacing: .05em;
  color: var(--ts-text);
  background-image:
    radial-gradient(120px circle at var(--mx) var(--my),
      rgba(var(--ts-glow-rgb), calc(var(--glass) * .20)),
      rgba(var(--ts-glow-rgb), 0) 65%),
    linear-gradient(var(--ts-surface), var(--ts-surface)),
    radial-gradient(90px circle at var(--mx) var(--my),
      rgba(var(--ts-glow-rgb), calc(var(--glass) * .95)),
      rgba(var(--ts-glow-rgb), calc(var(--glass) * .55)) 45%,
      rgba(var(--ts-edge-lo-rgb), calc(var(--glass) * .45)) 100%),
    linear-gradient(var(--ts-line), var(--ts-line));
  background-clip: padding-box, padding-box, border-box, border-box;
  background-origin: border-box;
  background-repeat: no-repeat;
  border: 1px solid transparent;
  padding: 8px 16px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
  transition:
    --glass .28s ease,
    color .2s ease,
    transform .12s ease;
}

.wp-block-query-pagination-previous:hover,
.wp-block-query-pagination-next:hover,
.wp-block-query-pagination .page-numbers:hover {
  --glass: 1;
  color: var(--ts-heading);
}

.wp-block-query-pagination-previous:active,
.wp-block-query-pagination-next:active,
.wp-block-query-pagination .page-numbers:active {
  transform: translateY(1px) scale(.99);
}

/* styling the page numbers as a combined button */
.wp-block-query-pagination-numbers {
  display: inline-flex;
  gap: 0;
}

/* overlap borders on combined page numbers to look like a single button block */
.wp-block-query-pagination .page-numbers {
  border-radius: 0;
  margin-left: -1px;
}

/* restore outer border-radius for the combined button group */
.wp-block-query-pagination .page-numbers:first-child,
.wp-block-query-pagination-numbers .page-numbers:first-child {
  border-top-left-radius: 3px;
  border-bottom-left-radius: 3px;
  margin-left: 0;
}

.wp-block-query-pagination .page-numbers:last-child,
.wp-block-query-pagination-numbers .page-numbers:last-child {
  border-top-right-radius: 3px;
  border-bottom-right-radius: 3px;
}

/* separate buttons for previous and next links */
.wp-block-query-pagination-previous,
.wp-block-query-pagination-next {
  border-radius: 3px;
}

/* current page highlights */
.wp-block-query-pagination .page-numbers.current {
  color: var(--ts-cyan);
  background-image:
    radial-gradient(120px circle at var(--mx) var(--my),
      rgba(var(--ts-glow-rgb), calc(var(--glass) * .20)),
      rgba(var(--ts-glow-rgb), 0) 65%),
    linear-gradient(var(--ts-deep), var(--ts-deep)),
    radial-gradient(90px circle at var(--mx) var(--my),
      rgba(var(--ts-glow-rgb), calc(var(--glass) * .95)),
      rgba(var(--ts-glow-rgb), calc(var(--glass) * .55)) 45%,
      rgba(var(--ts-edge-lo-rgb), calc(var(--glass) * .45)) 100%),
    linear-gradient(var(--ts-cyan-dim), var(--ts-cyan-dim));
}

.wp-block-query-pagination .page-numbers:hover,
.wp-block-query-pagination .page-numbers.current {
  z-index: 1;
}

/* ── separators in the article body ─────────────────────
   Core draws its separator as a 2px mid-grey rule, which is both heavier and
   warmer than anything else on the page — every other line the theme draws is
   1px of --ts-line, so the section break above the comments was reading as a
   foreign element rather than as part of the article.

   The rail has its own separator rule directly below. It is equal in
   specificity to this one, so it has to stay *after* it in source order to
   keep its own tighter 14px spacing. */
.ts-article .wp-block-separator {
  border: 0;
  border-top: 1px solid var(--ts-line);
  background: none;
  height: auto;
  width: auto;
  opacity: 1;
  /* Zero below: the comments heading brings its own leading, and the section
     break belongs to the article it closes rather than to what follows. */
  margin: 44px 0 0;
}

.ts-rail hr,
.ts-rail .wp-block-separator {
  border: 0;
  border-top: 1px solid var(--ts-line);
  margin: 14px 0;
  background: none;
  height: auto;
  width: auto;
}

@media (min-width: 782px) {
  .ts-rail {
    border-right: 1px solid var(--ts-line-soft);
    padding-right: 24px;
  }
}

/* ── tables ──────────────────────────────────────────── */

/* border+radius live on the wrapper, not the table: border-collapse fuses
   cell borders into one hairline (no doubled edges between rows), but it
   also flattens border-radius on the table element itself in most
   engines, so the rounded corners have to come from a wrapper that clips
   the table's square corners with overflow: hidden instead. */
:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) .wp-block-table {
  margin: 26px 0;
  overflow-x: auto;
  overflow-y: hidden;
  border: 1px solid var(--ts-line);
  border-radius: 8px;
  /* the glass surface under the table is what the header/scrollbar-track
     recipe elsewhere calls --ts-surface; a plain flat fill here (no blur)
     is enough to lift the table off the page sheet the way a card does */
  background: var(--ts-surface);
}

:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) table {
  width: 100%;
  border-collapse: collapse;
  font-size: .92rem;
}

/* Core's "fixed width table cells" toggle divides the width equally
   regardless of what is in a column, which starves a prose column next to
   numeric ones: the five-column metrics table gave "Share of dangerous
   faults covered by diagnostics" the same 115px as "≥ 90 %", and core's
   overflow-wrap: break-word then split the last word across two lines as
   "diagnostic / s".

   Auto layout measured better at every width and never worse: zero
   horizontal scroll at 1440, 900 and 390px, and the metrics table fell from
   527px tall to 301px because the columns finally match their contents. */
:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) table.has-fixed-layout {
  table-layout: auto;
}

/* Core puts its own 3px currentColor border-bottom directly on <thead>
   (and a matching border-top on <tfoot>) — separate from, and on top of,
   the 1px themed border already set on the th cells below. At
   currentColor that rendered as a thick near-white line under the header
   regardless of the cells' own border. */
:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) thead,
:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) tfoot {
  border: 0;
}

:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) thead th {
  font-family: var(--ts-sans);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  text-align: left;
  color: var(--ts-dim);
  background: var(--ts-deep);
  /* core's own table styles set a full 1px border in currentColor on every
     cell; at --ts-dim/--ts-text that reads as a near-white line on three
     sides once only border-bottom below is overridden, so the border is
     zeroed out first and rebuilt with just the one themed edge. */
  border: 0;
  border-bottom: 1px solid var(--ts-line);
  padding: 12px 16px;
  /* NOT nowrap. This rule never fired until the header rows were promoted,
     because no table on the site had ever been authored with a <thead> — and
     the first time it did fire, "Covered by a safety mechanism" and "Effect
     on the safety goal" ran straight over each other: core's tables carry
     has-fixed-layout, so a nowrap header wider than its equal-width column
     overflows the cell instead of widening it. */
  white-space: normal;
  /* a two-line header sits on the rule with its last line, next to the
     one-line headers beside it, instead of floating above them */
  vertical-align: bottom;
}

/* No-JS fallback for a table authored without core's "Header section"
   toggle: the header row is then ordinary <td> in <tbody> and nothing above
   reaches it. theme.js promotes that row to a real <thead>/<th scope="col">
   so it is announced as a header too — and the :has() guard here switches
   this paint off the instant that happens, so the two can never both apply
   and the row never gets styled twice. */
:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) table:not(:has(thead)) tbody tr:first-child td {
  font-family: var(--ts-sans);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  text-align: left;
  color: var(--ts-dim);
  background: var(--ts-deep);
  border: 0;
  border-bottom: 1px solid var(--ts-line);
  padding: 12px 16px;
  white-space: normal;
  vertical-align: bottom;
}

/* the row is furniture, not data, so it must not answer the pointer the way
   the body rows below it do */
:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) table:not(:has(thead)) tbody tr:first-child {
  background-image: none;
}

/* Core allows an emergency break anywhere inside a cell, which under auto
   layout splits a quantity that is joined by no-break spaces: "≥ 90 %" came
   apart as "≥ 90 / %" and "< 10⁻⁷ h⁻¹" broke between the 10⁻ and its own
   exponent. Turning that off makes each quantity a single unbreakable run,
   and auto layout then sizes the column to hold it — the two rules only
   work together. */
:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) th,
:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) td {
  overflow-wrap: normal;
  word-break: normal;
}

:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) td,
:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) tbody th {
  padding: 10px 16px;
  border: 0;
  border-bottom: 1px solid var(--ts-line-soft);
  color: var(--ts-text);
  vertical-align: top;
}

:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) tbody tr:last-child td,
:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) tbody tr:last-child th {
  border-bottom: 0;
}

/* alternating rows read better than a hairline grid alone once a table
   runs past four or five rows; the tint is deliberately faint (2% white)
   so it stays a reading aid rather than competing with the hover glass
   below */
:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) tbody tr:nth-child(even) {
  /* background-color, not the `background` shorthand: the shorthand resets
     every layer it doesn't mention, including background-image — which is
     exactly where the hover glass below lives, so writing it as a shorthand
     here silently erased the hover on every even row. */
  background-color: rgba(var(--ts-shade-rgb), .028);
}

/* Same cursor-following glass recipe as the contents rows and photo
   hover (--glass is the shared @property declared with the buttons,
   above) — the "hover animation already used on the wp-block-post
   cards". --mx/--my are fed by theme.js, which now also tracks pointer
   position over table rows. Painted as the row's own background rather
   than a pseudo-element: a <tr> cannot reliably host ::before/::after
   layered above its cells the way a block-level row can. */
:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) tbody tr {
  --glass: 0;
  --mx: 50%;
  --my: 50%;
  /* Alpha kept at the value the dark theme measured to (a mean lift of
     8.1/255 per channel at the peak, three times quieter than the .12/.05
     first tried), but the sign is flipped: the table sits on --ts-surface,
     which is now lighter than the row highlight rather than darker, so the
     wash darkens with --ts-shade-rgb where it used to lighten with white.
     The cyan layer underneath is unchanged in intent and lifted .016 → .022,
     since the same tint carries less against a light surface. */
  background-image:
    radial-gradient(280px circle at var(--mx) var(--my),
      rgba(var(--ts-shade-rgb), calc(var(--glass) * .035)),
      rgba(var(--ts-shade-rgb), 0) 70%),
    linear-gradient(
      rgba(var(--ts-glow-rgb), calc(var(--glass) * .022)),
      rgba(var(--ts-glow-rgb), calc(var(--glass) * .022)));
  transition: --glass .3s ease;
}

:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) tbody tr:hover {
  --glass: 1;
}

@media (prefers-reduced-motion: reduce) {
  :is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) tbody tr {
    transition: none;
  }
}

@media (max-width: 480px) {
  :is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) th,
  :is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) td {
    padding: 9px 12px;
  }
}

/* Exponents and indices — "10⁻⁷ h⁻¹" in the PMHF row of the metrics table.
   The UA default is font-size: smaller with the element still contributing
   its full leading, which pushes a table row taller than its neighbours;
   line-height: 0 with an explicit size takes that back so a row carrying an
   exponent lines up with one that does not. */
:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) sup,
:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) sub {
  font-size: .72em;
  line-height: 0;
  font-variant-numeric: tabular-nums;
}

/* ── archive / search headers ───────────────────────── */

.ts-archive-title,
.wp-block-query-title {
  font-family: var(--ts-serif);
  font-weight: 400;
  letter-spacing: .025em;
  word-spacing: .06em;
  color: var(--ts-heading);
  font-size: clamp(1.6rem, 3.2vw, 2.2rem);
  margin: 0 0 24px;
}

.wp-block-search__input {
  color: var(--ts-text);
  padding: 8px 12px;
  font-family: var(--ts-mono);
  font-size: 14px;
}

/* ── buttons (block buttons, comment submit, search) ── */

/* --glass fades the specular layers in and out; declaring it with
   @property is what makes it animatable (a plain custom property would
   snap from 0 to 1). Unsupported browsers just get an instant switch. */
@property --glass {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}

.wp-element-button,
.wp-block-button__link,
.woocommerce a.button,
.woocommerce button.button,
.woocommerce input.button,
.woocommerce-page a.button,
.woocommerce-page button.button,
.woocommerce-page input.button,
.wc-block-components-button:not(.wc-block-mini-cart__button):not(.wc-block-components-drawer__close),
.comment-form input[type="submit"] {
  --glass: 0;
  --mx: 50%;
  --my: 50%;
  /* The fill is painted by the background-image layers below, and on the blog
     that was the whole story. WooCommerce, though, sets background-color
     #32373c on .wp-element-button — a dark chip sitting under light layers
     with dark label text on top. It never shows while the layers paint, so
     it looks correct and measures 1.1:1: found on the storefront cards, the
     cart cross-sells and the 404 search button. Declaring the colour the
     layers already paint makes the two agree, so anything that drops the
     image (forced-colors, print, a Woo stylesheet reshuffle) degrades to a
     legible button instead of an invisible one. */
  background-color: var(--ts-surface);
  position: relative;
  font-family: var(--ts-mono);
  font-size: 13px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ts-text);
  /* Four stacked layers, painted top to bottom:
       1. specular bloom that follows the cursor   (padding box)
       2. the opaque body fill                     (padding box)
       3. lit edge, brightest nearest the cursor   (border box)
       4. the resting border colour                (border box)
     Layers 1-2 clip to the padding box, so only 3-4 show through the
     transparent 1px border — a gradient border that still respects
     border-radius. Backgrounds rather than pseudo-elements because the
     comment submit is an <input>, which cannot have ::before/::after. */
  background-image:
    radial-gradient(120px circle at var(--mx) var(--my),
      rgba(var(--ts-glow-rgb), calc(var(--glass) * .20)),
      rgba(var(--ts-glow-rgb), 0) 65%),
    linear-gradient(var(--ts-surface), var(--ts-surface)),
    radial-gradient(90px circle at var(--mx) var(--my),
      rgba(var(--ts-glow-rgb), calc(var(--glass) * .95)),
      rgba(var(--ts-glow-rgb), calc(var(--glass) * .55)) 45%,
      rgba(var(--ts-edge-lo-rgb), calc(var(--glass) * .45)) 100%),
    linear-gradient(var(--ts-line), var(--ts-line));
  background-clip: padding-box, padding-box, border-box, border-box;
  background-origin: border-box;
  background-repeat: no-repeat;
  border: 1px solid transparent;
  border-radius: 3px;
  padding: 9px 18px;
  cursor: pointer;
  transition:
    --glass .28s ease,
    color .2s ease,
    transform .12s ease;
}

.wp-element-button:hover,
.wp-block-button__link:hover,
.woocommerce a.button:hover,
.woocommerce button.button:hover,
.woocommerce input.button:hover,
.woocommerce-page a.button:hover,
.woocommerce-page button.button:hover,
.woocommerce-page input.button:hover,
.wc-block-components-button:not(.wc-block-mini-cart__button):not(.wc-block-components-drawer__close):hover,
.comment-form input[type="submit"]:hover {
  --glass: 1;
  color: var(--ts-heading);
}

.wp-element-button:active,
.wp-block-button__link:active,
.woocommerce a.button:active,
.woocommerce button.button:active,
.woocommerce input.button:active,
.woocommerce-page a.button:active,
.woocommerce-page button.button:active,
.woocommerce-page input.button:active,
.wc-block-components-button:not(.wc-block-mini-cart__button):not(.wc-block-components-drawer__close):active,
.comment-form input[type="submit"]:active {
  transform: translateY(1px) scale(.99);
}

.wp-element-button:focus-visible,
.wp-block-button__link:focus-visible,
.woocommerce a.button:focus-visible,
.woocommerce button.button:focus-visible,
.woocommerce input.button:focus-visible,
.woocommerce-page a.button:focus-visible,
.woocommerce-page button.button:focus-visible,
.woocommerce-page input.button:focus-visible,
.wc-block-components-button:not(.wc-block-mini-cart__button):not(.wc-block-components-drawer__close):focus-visible,
.comment-form input[type="submit"]:focus-visible {
  outline: 2px solid var(--ts-cyan);
  outline-offset: 3px;
}

/* keep the search button matched to its input's height */
.wp-block-search__button {
  padding: 8px 14px;
}

/* ── glass fields (search + text inputs) ────────────── */

/* Same four-layer construction as the buttons, but the states differ:
   the specular highlight appears on hover only. Once a field has focus
   the user is reading and typing, so it settles into a darker, static
   fill — a moving highlight under the caret is just distracting. */
.ts-search .wp-block-search__inside-wrapper,
.wp-block-search__input,
.comment-form textarea,
.comment-form input[type="text"],
.comment-form input[type="email"],
.woocommerce-page input[type="text"],
.woocommerce-page input[type="email"],
.woocommerce-page input[type="password"],
.woocommerce-page input[type="tel"],
.woocommerce-page input[type="number"],
.woocommerce-page textarea,
.woocommerce input[type="text"],
.woocommerce input[type="email"],
.woocommerce input[type="password"],
.woocommerce input[type="tel"],
.woocommerce input[type="number"],
.woocommerce textarea,
.wc-block-components-text-input input,
.wc-block-components-textarea,
input.qty,
.comment-form input[type="url"] {
  --glass: 0;
  --mx: 50%;
  --my: 50%;
  --field-fill: var(--ts-surface);
  --field-edge: var(--ts-faint);
  /* core leaves the wrapper white underneath the layers */
  background-color: var(--field-fill);
  background-image:
    radial-gradient(150px circle at var(--mx) var(--my),
      rgba(var(--ts-glow-rgb), calc(var(--glass) * .16)),
      rgba(var(--ts-glow-rgb), 0) 65%),
    linear-gradient(var(--field-fill), var(--field-fill)),
    radial-gradient(100px circle at var(--mx) var(--my),
      rgba(var(--ts-glow-rgb), calc(var(--glass) * .95)),
      rgba(var(--ts-glow-rgb), calc(var(--glass) * .55)) 45%,
      rgba(var(--ts-edge-lo-rgb), calc(var(--glass) * .45)) 100%),
    linear-gradient(var(--field-edge), var(--field-edge));
  background-clip: padding-box, padding-box, border-box, border-box;
  background-origin: border-box;
  background-repeat: no-repeat;
  border: 1px solid transparent;
  border-radius: 3px;
  transition: --glass .28s ease, box-shadow .2s ease;
}

/* glass on hover, but never while the field is being edited */
.ts-search .wp-block-search__inside-wrapper:hover:not(:focus-within),
.wp-block-search__input:hover:not(:focus-within),
.comment-form textarea:hover:not(:focus-within),
.comment-form input[type="text"]:hover:not(:focus-within),
.comment-form input[type="email"]:hover:not(:focus-within),
.woocommerce-page input[type="text"]:hover:not(:focus-within),
.woocommerce-page input[type="email"]:hover:not(:focus-within),
.woocommerce-page input[type="password"]:hover:not(:focus-within),
.woocommerce-page input[type="tel"]:hover:not(:focus-within),
.woocommerce-page input[type="number"]:hover:not(:focus-within),
.woocommerce-page textarea:hover:not(:focus-within),
.woocommerce input[type="text"]:hover:not(:focus-within),
.woocommerce input[type="email"]:hover:not(:focus-within),
.woocommerce input[type="password"]:hover:not(:focus-within),
.woocommerce input[type="tel"]:hover:not(:focus-within),
.woocommerce input[type="number"]:hover:not(:focus-within),
.woocommerce textarea:hover:not(:focus-within),
.wc-block-components-text-input input:hover:not(:focus-within),
.wc-block-components-textarea:hover:not(:focus-within),
input.qty:hover:not(:focus-within),
.comment-form input[type="url"]:hover:not(:focus-within) {
  --glass: 1;
}

/* the settled editing state: darker, calm, cyan edge */
.ts-search .wp-block-search__inside-wrapper:focus-within,
.wp-block-search__input:focus-within,
.comment-form textarea:focus-within,
.comment-form input[type="text"]:focus-within,
.comment-form input[type="email"]:focus-within,
.woocommerce-page input[type="text"]:focus-within,
.woocommerce-page input[type="email"]:focus-within,
.woocommerce-page input[type="password"]:focus-within,
.woocommerce-page input[type="tel"]:focus-within,
.woocommerce-page input[type="number"]:focus-within,
.woocommerce-page textarea:focus-within,
.woocommerce input[type="text"]:focus-within,
.woocommerce input[type="email"]:focus-within,
.woocommerce input[type="password"]:focus-within,
.woocommerce input[type="tel"]:focus-within,
.woocommerce input[type="number"]:focus-within,
.woocommerce textarea:focus-within,
.wc-block-components-text-input input:focus-within,
.wc-block-components-textarea:focus-within,
input.qty:focus-within,
.comment-form input[type="url"]:focus-within {
  --glass: 0;
  --field-fill: var(--ts-deep);
  --field-edge: var(--ts-cyan-dim);
  outline: none;
  box-shadow: 0 0 0 3px rgba(var(--ts-glow-rgb), .20);
}

/* In the header the glass and the focus ring belong to the wrapper; the
   input nested inside it must stay bare. Otherwise the input draws its
   own ring, whose right edge lands in the gap before the icon divider
   and reads as a stray vertical line. */
.ts-search .wp-block-search__input,
.ts-search .wp-block-search__input:hover:not(:focus-within),
.ts-search .wp-block-search__input:focus-within {
  --glass: 0;
  background: transparent;
  background-image: none;
  border: 0;
  box-shadow: none;
  outline: none;
}

/* ── comment form fields ────────────────────────────── */

.comment-form textarea,
.comment-form input[type="text"],
.comment-form input[type="email"],
.comment-form input[type="url"] {
  width: 100%;
  color: var(--ts-text);
  font-family: var(--ts-mono);
  font-size: 14px;
  line-height: 1.6;
  padding: 10px 12px;
}

.comment-form ::placeholder {
  color: var(--ts-muted);
}

.comment-form label {
  display: inline-block;
  font-family: var(--ts-sans);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ts-muted);
  margin-bottom: 6px;
}

/* ── footer ─────────────────────────────────────────── */

.ts-footer {
  /* flow-root (not block) gives the footer its own block-formatting
     context, so a last child's bottom margin can't collapse through the
     footer's own bottom edge — it stayed flush without this when the
     trace bar was reordered to the end, since .ts-footer carries no
     border/padding below to stop the collapse on its own. */
  display: flow-root;
  background: var(--ts-deep);
  border-top: 1px solid var(--ts-line);
  margin-top: 56px;
  transition: filter .3s ease;
}

/* full-bleed differential trace across the top of the footer */
.ts-foot-trace {
  margin: 18px 0 0;
}

/* The trace can be reordered to the end of the footer (the "hold 5s"
   easter egg) via the template part. When it trails .ts-foot-inner instead
   of leading it, its own 0 bottom margin left it flush against the
   footer's edge, and .ts-foot-inner's 46px padding-bottom (sized for
   closing the footer, not for leading into the trace) stacked on top of
   the trace's own 18px top margin for a 64px gap above it. Mirroring the
   opening spacing — 26px into the trace, 18px out of it — keeps both
   footer layouts reading the same regardless of which element is last. */
.ts-foot-inner:has(+ .ts-foot-trace) {
  padding-bottom: 26px;
}

.ts-foot-inner + .ts-foot-trace {
  margin: 0 0 18px;
}

/* nowrap, not wrap: with flex-wrap:wrap, the browser decides whether a
   second item needs a new line using each item's *unshrunk* content size —
   shrinking is only applied within a line already decided, never consulted
   for the wrap decision itself. That silently broke the two-column layout
   at ~965px, nearly 350px above the width the columns actually need once
   they're allowed to reflow their own text. nowrap forces both columns
   to stay on one line and shrink/reflow instead, so the row only gives up
   on two columns at the deliberate breakpoint below, not a content-driven
   accident. */
.ts-foot-inner {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 32px;
  flex-wrap: nowrap;
  padding-top: 26px;
  padding-bottom: 46px;
}

/* two stacks: identity on the left, routes out on the right. margin: 0
   defeats core's flow-layout margin-block-start on the group children.
   min-width: 0 overrides the flex item default (min-width: auto), which
   would otherwise re-introduce the same early-wrap problem now that
   .ts-foot-inner is nowrap and actually relies on shrinking to fit. */
.ts-foot-col {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0;
  min-width: 0;
}

.ts-foot-right {
  align-items: flex-end;
  text-align: right;
}

.ts-foot-note {
  font-family: var(--ts-serif);
  font-style: italic;
  font-size: 1.05rem;
  color: var(--ts-dim);
  margin: 0;
  max-width: 46ch;
}

/* the colophon is a shortcode block ([ts_year]), so the paragraph is
   generated by wpautop rather than authored — style it through the group */
.ts-foot-meta,
.ts-foot-meta p {
  font-family: var(--ts-mono);
  font-size: 11.5px;
  letter-spacing: .02em;
  color: var(--ts-dim);
  margin: 0;
}

.ts-foot-links {
  font-family: var(--ts-sans);
  font-size: .85rem;
  margin: 0;
  color: var(--ts-dim);
}

.ts-foot-links a {
  color: var(--ts-muted);
  text-decoration: none;
  /* inline text links, so SC 2.5.8 does not apply — but the padding still
     buys a little room on touch without disturbing the baseline */
  padding: 2px 0;
}

.ts-foot-links a:hover { color: var(--ts-cyan); }

.ts-foot-social {
  margin: 0;
}

/* Obfuscated contact links (functions.php + theme.js module 14). Until one
   is reached for it carries a prompt rather than the address, and the dotted
   underline is what says the prompt is the control and not a truncation. It
   goes away with .is-revealed, at which point the link is an ordinary link
   and inherits whatever its surroundings style. */
.ts-obf {
  cursor: pointer;
  text-decoration: underline dotted currentColor;
  text-underline-offset: .18em;
}

.ts-obf.is-revealed {
  text-decoration: inherit;
}

/* Mid-decode. No font switch here: the run is set in whatever face the link
   already had, and the line is held still by the cells instead — theme.js
   sizes each one to the exact advance of the character it will become. An
   earlier version swapped in the monospace face to get that stability for
   free, and it cost a visible jolt on the last frame when the text changed
   font underneath the reader.

   Line breaking is handled by .is-atomic rather than here, because it has to
   outlast the decode — see theme.js. */
.ts-obf.is-decoding {
  text-decoration: none;
}

/* Set by theme.js on a link it is about to animate, and left in place
   afterwards: the cells and the finished text have to break the same way, and
   the only way two different structures break the same way is not at all. It
   is only applied once the string has been measured against the column, so it
   cannot push anything sideways. */
.ts-obf.is-atomic {
  white-space: nowrap;
}

/* Cells not yet resolved. inline-block so the width theme.js sets applies,
   and so the scaleX that fits the glyph into it has a box to work on;
   transform-origin at the left edge keeps the fitted glyph on the cell's own
   start rather than creeping toward its middle.

   --ts-cyan at 45% for the glow; the theme has no color-mix() anywhere else,
   so this stays literal to match. */
.ts-obf .ts-obf-x {
  display: inline-block;
  transform-origin: 0 50%;
  white-space: pre;
  color: var(--ts-cyan);
  text-shadow: 0 0 6px rgba(var(--ts-glow-rgb), .30);
}

/* Resolved, but still a cell: it keeps its box so the characters after it do
   not move, and only stops looking encrypted. */
.ts-obf .ts-obf-x.is-set {
  color: inherit;
  text-shadow: none;
}

/* Reduced motion needs no override: theme.js returns before any of the above
   is applied, so the address simply appears as ordinary text. */

/* Below this, shrinking the two columns further (rather than stacking
   them) starts wrapping the note and links onto too many lines to read
   comfortably — measured at 4+ wrapped lines each once the row narrows
   past this point. This is also where .ts-foot-right's own alignment
   flip below was already written for a stacked layout; it just never
   used to be reachable because the row broke to one column ~350px
   earlier than this on its own. */
@media (max-width: 640px) {
  .ts-foot-inner {
    flex-direction: column;
  }

  .ts-foot-right {
    align-items: flex-start;
    text-align: left;
  }

  .ts-foot-social {
    margin: 0;
  }
}

/* ── standalone pages: full-sheet reading column ────── */

/* pages carry no meta rail, so every block fills the sheet — the same
   full width the article grid gives posts */
.ts-page-body {
  max-width: none;
  margin: 0;
}

.ts-page-body .wp-block-post-content > *,
.ts-page-body .wp-block-post-content > p,
.ts-page-body .wp-block-post-content > ul,
.ts-page-body .wp-block-post-content > ol,
.ts-page-body .wp-block-post-content blockquote {
  max-width: none;
}

/* ...but running prose still needs a measure. The sheet is 806px wide on a
   desktop viewport, and uncapped that ran ~90 characters per line — well
   past the 45-75 comfortable range, and the reason pages read heavier than
   posts (which the 160px meta rail happens to constrain to 577px / ~63).
   Capping only paragraphs and lists leaves the sheet, the CV's grids, chips
   and contact strip at full width; p.ts-cv-contact re-opens itself below.

   55ch looks low for a ~70-character target because ch is the advance of
   "0" — 11.45px at 18px system-ui — while real mixed-case prose averages
   ~9px per character. 55ch lands at 630px, measured at 70 characters. The
   65ch on posts above is inert for the same reason (it computes to 744px,
   wider than any column the grid produces); it is left alone because the
   rail already holds that text at a good measure. */
.ts-page-body .wp-block-post-content > p,
.ts-page-body .wp-block-post-content > ul,
.ts-page-body .wp-block-post-content > ol,
.ts-page-body .wp-block-post-content .ts-cv-desc {
  max-width: 55ch;
}

/* .ts-entry is a body of reference prose that owns the whole sheet: the
   standalone prose pages, and — below 782px, where the article columns
   stack — glossary entries too. On a desktop glossary entry the wrapper now
   sits inside the article grid's content column instead, which is narrower
   than the measure below, so every cap here resolves to the column and the
   rules go quiet without needing a second set scoped to that case.

   With the whole sheet to fill there is no meta rail on the other side, so
   the entry takes one measure centred in it. Capping only the paragraphs
   left them 177px short of the headings, the eyebrow rule, the formula and
   the back link — one narrow element among full-width ones, which is what
   read as unbalanced.

   So the measure goes on every child rather than on the wrapper: that
   keeps the common centre axis, while letting the blocks that are *not*
   prose opt back out below. Capping the wrapper (which is what this used
   to do) also squeezed the diagram and the table into the 630px column
   inside an 806px sheet, wasting 88px a side and cramping both — line
   length is a property of running text, not of the container.

   Deliberately not applied to .ts-page-body at large: the CV mixes capped
   prose with full-width items, chips and the skills grid, and those need a
   common left edge rather than a centred column.

   The cap is an absolute length, not the 55ch used elsewhere, because ch
   is the advance of "0" in each element's *own* font: one 55ch
   declaration shared across these children resolved to 440px on the mono
   eyebrow and 1133px on the serif h1 — past the sheet, so no cap at all —
   against 630px on body-font prose. --ts-measure is that prose value,
   measured, and it is a variable because three rules below need the same
   column: the children, and the captions that align back to them. */
.ts-page-body.ts-entry {
  --ts-measure: 630px;
  max-width: none;
  margin-inline: 0;
}

/* box-sizing is set here because the theme has no global border-box reset:
   these children were previously sized by the 630px wrapper, so padding sat
   inside that width. Capping them individually under content-box measured
   the cap against the *content* box instead, which pushed the formula panel
   (24px padding + 1px border a side) to 680px — 25px past the column on
   each side. Paragraphs and headings have no inline padding, so this is
   inert for them and only the padded blocks are affected. */
.ts-page-body.ts-entry > *,
.ts-page-body.ts-entry .wp-block-post-content > * {
  box-sizing: border-box;
  max-width: var(--ts-measure);
  margin-inline: auto;
}

/* the content wrapper itself must stay open, or it would re-impose the
   measure on the figures below before they get a chance to break out */
.ts-page-body.ts-entry .wp-block-post-content {
  max-width: none;
}

/* Diagrams, tables and code read better wide: a byte-lane diagram or a
   two-column reference table gains information density from the extra
   176px, where prose would only lose it. These take the full sheet and
   stay centred on the same axis as the text. Below ~600px the sheet is
   already narrower than the measure, so this resolves to no change;
   .wp-block-table carries its own overflow-x, so a wide table scrolls
   rather than pushing the page sideways. */
/* .ts-figure is the caption wrapper technical_shop_figure_caption() adds;
   the bare block selectors alongside it still matter, because that filter
   does not run in the block editor and add_editor_style() loads this sheet
   there too. */
.ts-page-body.ts-entry .wp-block-post-content > .ts-figure,
.ts-page-body.ts-entry .wp-block-post-content > .wp-block-image,
.ts-page-body.ts-entry .wp-block-post-content > .wp-block-table,
.ts-page-body.ts-entry .wp-block-post-content > .wp-block-math,
.ts-page-body.ts-entry .wp-block-post-content > .wp-block-code,
.ts-page-body.ts-entry .wp-block-post-content > svg,
.ts-page-body.ts-entry .wp-block-post-content > pre,
/* theme.js wraps every code panel to hang the copy button off it, which
   makes the wrapper the direct child and would otherwise drop the panel
   back to the prose measure */
.ts-page-body.ts-entry .wp-block-post-content > .ts-copy-wrap {
  max-width: none;
}

/* The wrapper takes over the block's vertical rhythm — figure carries a UA
   `margin: 1em 40px`, so leaving it alone would both indent the diagram and
   double the gap above it against the block's own 26px. */
.ts-page-body.ts-entry .wp-block-post-content > .ts-figure {
  margin: 26px 0;
}

.ts-page-body.ts-entry .ts-figure > :first-child {
  margin: 0;
}

/* The image figure shrink-wraps its picture instead of filling the sheet.
   Two things ride on the figure's box rather than the picture's, and both
   were landing in the empty space beside a diagram narrower than its
   column: the hover glass, which is an ::after pinned to the figure's
   inset and so spanned 804px against the drawing's 599, and core's enlarge
   trigger, which its own setButtonStyles() positions from the figure edge
   — it computed right: 223px, overshooting the picture by 120px, because
   it counts the centring offset twice.

   Sizing the figure to the picture makes both correct by construction
   rather than by re-deriving either offset here. Restates the auto margins
   because the :first-child rule above, which zeroes them, matches this
   element too and has equal specificity. */
.ts-page-body.ts-entry .ts-figure > .wp-block-image {
  width: max-content;
  max-width: 100%;
  margin: 0;
}

/* Unlike a table, an image figure stays in the text column rather than
   breaking out to the sheet: a diagram reads as part of the argument it
   illustrates, so it shares the paragraphs' left edge instead of floating
   on its own axis, and the measure caps it. The auto inline margin centres
   the *column* in the sheet — which is what puts its left edge on the
   prose — while the figure's own contents stay left-aligned. Overrides the
   breakout rule above at equal specificity, so it has to follow it. */
.ts-page-body.ts-entry .wp-block-post-content > .ts-figure--image {
  max-width: var(--ts-measure);
  margin: 26px auto;
}

/* Glossary figures render at their own size, centred, rather than being
   stretched to the sheet: these are technical drawings whose stroke
   weights, corner radii and label sizes are chosen at one scale, and
   enlarging a 595px export to 806 thickens every hairline and swells the
   text out of proportion with the body copy beside it. max-width still
   shrinks them when the column is narrower, which costs nothing.

   Scoped to .ts-entry, so the CV portrait and in-post screenshots keep
   whatever size the editor gave them. */
.ts-page-body.ts-entry .wp-block-post-content .wp-block-image img {
  display: block;
  width: auto;
  max-width: 100%;
  height: auto;
  margin-inline: auto;
}

/* Captions take the prose column, not the figure's full width: a caption is
   running text, so it belongs on the same left edge and the same measure as
   the paragraphs that discuss it — centring the measure inside the wider
   figure lands it exactly there. Below ~600px the figure is already narrower
   than the measure, so the cap is inert and the caption keeps the column.
   Mono at the breadcrumb's size and dimness: present for reference, quiet
   enough that it never competes with the prose. */
.ts-page-body.ts-entry .ts-figcaption {
  max-width: var(--ts-measure);
  margin: 10px auto 0;
  font-family: var(--ts-mono);
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--ts-dim);
}

/* Cyan is this theme's label colour — it is what .ts-eyebrow uses for the
   "GLOSSARY" kicker, so a figure number in it reads as the same class of
   thing. --ts-muted, tried first, sits one step from --ts-dim and made the
   label indistinguishable from the caption text beside it. */
.ts-page-body.ts-entry .ts-figcaption-label {
  color: var(--ts-cyan);
  text-transform: uppercase;
  letter-spacing: .06em;
}

/* ── SK chip logo ───────────────────────────────────── */

.ts-logo {
  width: 46px;
  height: 46px;
  flex: none;
  align-self: center;
  transition: width .35s ease, height .35s ease;
}

html.ts-stuck .ts-logo {
  width: 32px;
  height: 32px;
}

/* The blog's SK monogram is not ported — this is a stand-in, and it is drawn
   to look like one on purpose: a dashed frame and a slash, the conventional
   "image goes here" mark, so nobody mistakes it for a finished logo or ships
   it by accident.

   It occupies exactly the geometry the real mark will: 48x48 viewBox, 46px
   at rest, 32px stuck. Replacing it is a one-element edit in parts/header.html
   — swap the <svg> for the real one, keep class="ts-logo", drop
   is-placeholder — and nothing in the masthead layout has to move. */
.ts-logo.is-placeholder .frame {
  fill: var(--ts-surface);
  stroke: var(--ts-faint);
  stroke-width: 1.5;
  stroke-dasharray: 4 3;
}

.ts-logo.is-placeholder .slash {
  fill: none;
  stroke: var(--ts-faint);
  stroke-width: 1.5;
  stroke-linecap: round;
}

.ts-logo.is-placeholder .label {
  fill: var(--ts-dim);
  font-family: var(--ts-mono);
  font-size: 9px;
  font-weight: 600;
  letter-spacing: .06em;
}

/* Kept for the real mark when it lands: the monogram recipe the blog uses,
   minus the SK-specific paths. */
.ts-logo .body {
  fill: var(--ts-deep);
  stroke: var(--ts-muted);
  stroke-width: 1.5;
}

.ts-logo .trace {
  fill: none;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ts-logo .c { stroke: var(--ts-cyan); }
.ts-logo .b { stroke: var(--ts-blue); }
.ts-logo .t { stroke: var(--ts-turquoise); }

/* ── masthead: shop actions ──────────────────────────── */

/* The blog put a social-link row here. A storefront needs the two controls a
   shopper actually reaches for, so account and cart replace it — and both
   arrive as separate WooCommerce blocks, each wrapped in its own div. Left as
   core renders them (is-layout-flow) they stack, which measured a 288px
   masthead with the search, account and cart in a vertical stripe down the
   right edge. */
/* The blog stacked this column-wise because it held one item. With three
   controls the column is what produced the 288px masthead, so the direction
   is flipped back to a row here rather than at the original rule — the mobile
   rules below 600px still relocate the search into the menu and rely on the
   original block being intact. */
.ts-mast-aside {
  flex-direction: row;
  align-items: center;
  gap: 14px;
}

.ts-shop-actions {
  display: flex;
  align-items: center;
  gap: 4px;
}

/* Both groups are core block groups, so they carry is-layout-flow, and core
   ships `:where(.is-layout-flow) > * + * { margin-block-start: 24px }`.
   Turning the group into a flex container does not stop that rule matching:
   the margin is still applied, now to a flex item, where block-start means
   *top* rather than the vertical rhythm it was written for. Measured, that
   put the actions group 24px below the search (centres 72 vs 84) and the
   cart another 24px below the account (84 vs 96) — the three controls each
   on their own baseline, stepping down to the right.

   align-items: center cannot rescue it, because centring happens after
   margins are honoured. Zeroing the margin on the flex children is the fix;
   the gap properties above own the spacing instead. */
.ts-mast-aside > *,
.ts-mast-sub > *,
.ts-shop-actions > * {
  margin-block-start: 0;
  margin-block-end: 0;
}

/* The two Woo blocks also arrive at different intrinsic heights (40px and
   42px), which centring alone leaves visibly uneven against a 36px search
   field. Giving both controls the same box makes the row read as one set. */
.ts-shop-actions > * {
  display: flex;
  align-items: center;
}

/* Modelled on the blog's social links rather than on a button: these are
   glyphs, and the boxed hover they had (a filled panel and a border appearing
   under the pointer) made two small icons behave like the only framed
   controls in the masthead. The blog's treatment is a colour shift and a 1px
   lift, which is quieter and already the site's vocabulary for an icon link.

   Sized to .tn-social-link's 24px box as well, because in this row the
   controls are what set the line height: at 38px they made the navigation row
   62px tall against the blog's 51px, which is the extra air above and below
   the menu. 24px is also exactly the WCAG 2.5.8 minimum target, so matching
   the blog costs nothing in reachability. */
.ts-shop-actions .wc-block-customer-account__link,
.ts-shop-actions .wc-block-mini-cart__button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 0;
  border-radius: 3px;
  background: none;
  color: var(--ts-muted);
  cursor: pointer;
  transition: color .2s ease, transform .2s ease;
}

.ts-shop-actions .wc-block-customer-account__link:hover,
.ts-shop-actions .wc-block-mini-cart__button:hover {
  color: var(--ts-cyan);
  transform: translateY(-1px);
}

/* Focus still needs a visible ring — the lift alone is not an indicator. */
.ts-shop-actions .wc-block-customer-account__link:focus-visible,
.ts-shop-actions .wc-block-mini-cart__button:focus-visible {
  color: var(--ts-cyan);
  outline: none;
  box-shadow: 0 0 0 3px rgba(var(--ts-glow-rgb), .28);
}

@media (prefers-reduced-motion: reduce) {
  .ts-shop-actions .wc-block-customer-account__link,
  .ts-shop-actions .wc-block-mini-cart__button { transition: color .2s ease; }
  .ts-shop-actions .wc-block-customer-account__link:hover,
  .ts-shop-actions .wc-block-mini-cart__button:hover { transform: none; }
}

.ts-shop-actions svg {
  width: 20px;
  height: 20px;
  fill: currentColor;
  transition: fill .2s ease;
}

/* The cart's count sits in a badge over the icon. Core positions it against
   its own button padding, which is zeroed above, so it is re-anchored here. */
.ts-shop-actions .wc-block-mini-cart__quantity-badge {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.ts-shop-actions .wc-block-mini-cart__badge {
  position: absolute;
  top: -6px;
  right: -8px;
  min-width: 17px;
  height: 17px;
  padding: 0 4px;
  border-radius: 9px;
  background: var(--ts-cyan);
  color: #fff;
  font-family: var(--ts-mono);
  font-size: 10px;
  line-height: 17px;
  text-align: center;
}

html.ts-stuck .ts-shop-actions .wc-block-customer-account__link,
html.ts-stuck .ts-shop-actions .wc-block-mini-cart__button {
  width: 22px;
  height: 22px;
}

/* ── storefront: results bar ─────────────────────────── */

/* Count on the left, sort on the right, on the same print rule the blog used
   under section labels. */
.ts-shop-bar {
  align-items: center;
  gap: 16px;
  margin: 10px 0 26px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--ts-line);
}

.ts-shop-bar .woocommerce-result-count {
  margin: 0;
  font-family: var(--ts-mono);
  font-size: 12px;
  letter-spacing: .04em;
  color: var(--ts-dim);
}

.ts-shop-bar .woocommerce-ordering {
  margin: 0;
}

/* Core leaves the sort control as a raw <select>, which renders as the UA
   widget — #EFEFEF on a #767676 border, the one element on the page that
   looked like a browser rather than the theme. Styled as the same field the
   search uses, with the disclosure arrow drawn as a background image because
   appearance:none removes the native one. */
.ts-shop-bar select.orderby {
  appearance: none;
  -webkit-appearance: none;
  font-family: var(--ts-mono);
  font-size: 12px;
  letter-spacing: .03em;
  color: var(--ts-text);
  background-color: var(--ts-surface);
  background-image:
    linear-gradient(45deg, transparent 50%, var(--ts-muted) 50%),
    linear-gradient(135deg, var(--ts-muted) 50%, transparent 50%);
  background-position:
    calc(100% - 15px) calc(50% + 1px),
    calc(100% - 10px) calc(50% + 1px);
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  border: 1px solid var(--ts-faint);
  border-radius: 3px;
  padding: 7px 32px 7px 12px;
  cursor: pointer;
}

.ts-shop-bar select.orderby:focus-visible {
  outline: none;
  border-color: var(--ts-cyan-dim);
  box-shadow: 0 0 0 3px rgba(var(--ts-glow-rgb), .20);
}

/* ── storefront: product grid ────────────────────────── */

.ts-shop .ts-archive-title {
  margin-bottom: 4px;
}

/* Each product is a page sheet in miniature — same white fill, same hairline
   border and 3px radius as .ts-page — so the grid reads as a set of cards cut
   from the same stock as the rest of the site rather than as bare stacks.

   Laid out as a column with the button pushed to the bottom by margin-top:
   auto: product titles run one to three lines, and without it the add-to-cart
   row sat at a different height in every card across a row. */
.ts-grid .wc-block-product {
  display: flex;
  flex-direction: column;
  background: var(--ts-page);
  border: 1px solid var(--ts-line);
  border-radius: 3px;
  padding: 14px 14px 18px;
  transition: border-color .25s ease, box-shadow .25s ease, transform .25s ease;
}

.ts-grid .wc-block-product:hover {
  border-color: var(--ts-cyan-dim);
  box-shadow: 0 1px 0 rgba(var(--ts-shade-rgb), .04), 0 6px 18px rgba(var(--ts-shade-rgb), .07);
  transform: translateY(-2px);
}

@media (prefers-reduced-motion: reduce) {
  .ts-grid .wc-block-product { transition: none; }
  .ts-grid .wc-block-product:hover { transform: none; }
}

.ts-grid .wc-block-components-product-image {
  margin: 0 0 14px;
  border-radius: 2px;
  overflow: hidden;
  background: var(--ts-surface);
}

/* Products photographed at different aspect ratios would otherwise set
   different card heights and break the row rhythm; a fixed square with
   object-fit crops rather than letterboxes. */
.ts-grid .wc-block-components-product-image img {
  display: block;
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
}

.ts-grid .wp-block-post-title {
  margin: 0 0 6px;
  font-family: var(--ts-serif);
  line-height: 1.3;
}

.ts-grid .wp-block-post-title a {
  color: var(--ts-heading);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color .2s ease, border-color .2s ease;
}

.ts-grid .wp-block-post-title a:hover,
.ts-grid .wp-block-post-title a:focus-visible {
  color: var(--ts-cyan);
  border-bottom-color: var(--ts-cyan-dim);
}

.ts-grid .wc-block-components-product-price {
  font-family: var(--ts-mono);
  font-size: 14px;
  color: var(--ts-heading);
}

/* A struck original beside the sale price: the old number is reference, not
   the offer, so it drops to the decorative tone and a smaller size. */
.ts-grid .wc-block-components-product-price del,
.ts-grid .wc-block-components-product-price del .amount {
  color: var(--ts-dim);
  font-size: 12px;
  text-decoration-thickness: 1px;
}

.ts-grid .wc-block-components-product-price ins {
  text-decoration: none;
  color: var(--ts-turquoise);
}

.ts-grid .wc-block-components-product-button {
  margin: 14px 0 0;
}

/* WooCommerce ships wp-element-button with background-color #32373c. The
   theme's own button rules paint over it with background-image layers, so
   the visible control is correct — but the colour underneath is dark while
   the label on top is dark text, and anything that suppresses the image
   layers (forced-colors, print, a future specificity change in Woo) would
   leave an unreadable button. Setting the colour to match what the layers
   paint keeps the fallback legible instead of relying on the paint.

   Three classes deep, not two, and deliberately so: Woo's own rule for this
   button is also (0,2,0) and its stylesheet is printed after the theme's, so
   a two-class selector here loses the tie and the width silently stayed at
   the label's intrinsic 136px inside a 297px slot. Out-specifying it is the
   fix that keeps working when Woo's sheet moves; !important would too, but
   it would also fight every later override including the editor's.

   Scoped to .wc-block-product rather than .ts-grid, because the same card
   button also appears in the related-products and cross-sells collections
   that Woo renders inside its own templates — those are outside the theme's
   grid, and measuring a product page found four of them still carrying the
   dark fallback. Full width stays a .ts-grid rule: it is a property of the
   storefront card, not of every card Woo emits. */
.wc-block-product .wc-block-components-product-button__button,
.wc-block-product .wp-block-button__link,
.wp-block-button__link.add_to_cart_button {
  background-color: var(--ts-surface);
}

.ts-grid .wc-block-product .wc-block-components-product-button__button,
.ts-grid .wc-block-product .wp-block-button__link {
  width: 100%;
}

.ts-grid .wc-block-components-product-sale-badge {
  font-family: var(--ts-mono);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #fff;
  background: var(--ts-turquoise);
  border: none;
  border-radius: 2px;
  padding: 4px 9px;
}

.ts-empty {
  font-family: var(--ts-mono);
  font-size: 13px;
  color: var(--ts-dim);
  padding: 40px 0;
  text-align: center;
}

/* The grid drops a column at a time rather than reflowing to a single stack:
   at 1100px four 327px cards would each fall under 240px, which is narrower
   than the add-to-cart label. */
@media (max-width: 1180px) {
  .ts-grid .wp-block-woocommerce-product-template { grid-template-columns: repeat(3, 1fr) !important; }
}

@media (max-width: 860px) {
  .ts-grid .wp-block-woocommerce-product-template { grid-template-columns: repeat(2, 1fr) !important; }
}

@media (max-width: 520px) {
  .ts-grid .wp-block-woocommerce-product-template { grid-template-columns: 1fr !important; }
  .ts-shop-bar { flex-direction: column; align-items: flex-start; gap: 10px; }
}

/* ── WooCommerce templates the theme does not override ── */

/* single-product, cart, checkout and order-confirmation come from the plugin.
   Their <main> is a constrained-layout group with no shell class, so it ran
   edge to edge: measured on a product page, the gallery started at x=0 while
   the summary column started at x=370. Re-imposing the shell here puts every
   Woo page on the same margins as the storefront and the footer. */
.woocommerce-page main.wp-block-group,
.single-product main.wp-block-group,
.woocommerce-order-received main.wp-block-group {
  max-width: 1440px;
  margin-left: auto;
  margin-right: auto;
  padding-left: 32px;
  padding-right: 32px;
}

@media (max-width: 700px) {
  .woocommerce-page main.wp-block-group,
  .single-product main.wp-block-group,
  .woocommerce-order-received main.wp-block-group {
    padding-left: 20px;
    padding-right: 20px;
  }
}

/* Woo's templates open with their own breadcrumb block, which on a product
   page rendered "Home / Uncategorized / 10-inch half-rack, 9U" directly under
   the theme's own "~ / shop / 10-inch half-rack, 9u" bar. Two breadcrumbs is
   worse than either; the theme's is the one that matches the site, so Woo's
   is suppressed rather than restyled. */
.wp-block-woocommerce-breadcrumbs,
.woocommerce-breadcrumb {
  display: none;
}

/* Primary commerce actions.

   These were briefly given a solid accent fill, which was a mistake on two
   counts. The theme paints a button as four stacked background layers — the
   specular bloom, the body fill, the lit edge and the resting border — and
   the border itself is `1px solid transparent`, so layer 4 *is* the border.
   Setting background-image: none to make room for a flat fill therefore did
   not just remove the glass, it removed the border with it; that is why some
   buttons appeared to lose their outline entirely. The flat fill also took
   its own hover colour, so the login button went green on hover while every
   other control on the site glassed.

   So the primary actions keep the shared recipe untouched — glass, lit edge,
   border and all — and are distinguished only by the label. That is enough
   hierarchy on a page where they are the only accent-coloured control, and it
   costs nothing structurally. */
.single_add_to_cart_button,
.wc-block-cart__submit-button,
.wc-block-components-checkout-place-order-button,
.checkout-button,
.wc-block-components-totals-coupon__button {
  color: var(--ts-cyan);
  font-weight: 600;
}

.single_add_to_cart_button:hover,
.wc-block-cart__submit-button:hover,
.wc-block-components-checkout-place-order-button:hover,
.checkout-button:hover,
.wc-block-components-totals-coupon__button:hover {
  color: var(--ts-cyan);
}

/* The quantity stepper beside add-to-cart is a bare number input otherwise. */
.quantity input.qty,
.wc-block-components-quantity-selector input {
  font-family: var(--ts-mono);
  font-size: 13px;
  color: var(--ts-text);
  background: var(--ts-surface);
  border: 1px solid var(--ts-faint);
  border-radius: 3px;
  padding: 10px 8px;
}

/* Product summary column: Woo's price and title come in at core defaults. */
.single-product .product_title {
  font-family: var(--ts-serif);
  color: var(--ts-heading);
}

.single-product p.price,
.single-product .wc-block-components-product-price {
  font-family: var(--ts-mono);
  color: var(--ts-heading);
}

.single-product p.price del {
  color: var(--ts-dim);
}

.single-product p.price ins {
  text-decoration: none;
  color: var(--ts-turquoise);
}

/* Tab headers on the description/reviews panel. */
.woocommerce-tabs ul.tabs li a {
  font-family: var(--ts-mono);
  font-size: 12px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ts-dim);
}

.woocommerce-tabs ul.tabs li.active a {
  color: var(--ts-cyan);
}

/* Cart and checkout tables inherit the theme's table treatment through the
   :is() selector in the tables section; these are the parts outside it. */
.wc-block-components-totals-item,
.wc-block-components-order-summary-item {
  font-family: var(--ts-mono);
  font-size: 13px;
  color: var(--ts-text);
}

.wc-block-components-notice-banner {
  border-radius: 3px;
}

/* ── prose links: the underline sweep ────────────────── */

/* Ported back from the blog after being cut with the article rail it happened
   to live next to. Two stacked layers: a dim rule at full width that is always
   there, and the bright cyan bar that grows in over it from the left and
   exits right. box-decoration-break keeps both painted per line when a link
   wraps, instead of one background stretched across the fragments.

   The scope is wider than the blog's: on a storefront the running copy a
   visitor actually reads is spread across page content, the product
   description tabs and the my-account panels, so all three get it. Buttons,
   navigation, the footer strip and any link wrapping an image are excluded —
   each already carries its own affordance, and a 2px bar under a button label
   just looks like a mistake. */
.ts-article .wp-block-post-content a:not(.wp-element-button):not(.button):not(:has(img)):not(.woocommerce-MyAccount-navigation a),
.woocommerce-Tabs-panel a:not(.wp-element-button):not(.button):not(:has(img)),
.woocommerce-product-details__short-description a:not(:has(img)),
.woocommerce-MyAccount-content a:not(.wp-element-button):not(.button):not(:has(img)) {
  color: var(--ts-cyan);
  text-decoration: none;
  padding-bottom: 2px;
  background-image:
    linear-gradient(var(--ts-cyan), var(--ts-cyan)),
    linear-gradient(var(--ts-link-rest), var(--ts-link-rest));
  background-repeat: no-repeat;
  background-position: right bottom, left bottom;
  background-size: 0% 2px, 100% 1px;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
  transition: background-size .28s ease;
}

/* grows in from the left, exits to the right — the background-position jump
   is deliberately not transitioned, which is what makes the bar re-enter from
   the same edge every time instead of reversing back out. */
.ts-article .wp-block-post-content a:not(.wp-element-button):not(.button):not(:has(img)):not(.woocommerce-MyAccount-navigation a):hover,
.woocommerce-Tabs-panel a:not(.wp-element-button):not(.button):not(:has(img)):hover,
.woocommerce-product-details__short-description a:not(:has(img)):hover,
.woocommerce-MyAccount-content a:not(.wp-element-button):not(.button):not(:has(img)):hover {
  background-position: left bottom, left bottom;
  background-size: 100% 2px, 100% 1px;
}

/* ── my account ──────────────────────────────────────── */

/* Woo renders this area from its classic PHP templates, so none of the block
   styling above reaches it and it arrived as unstyled browser defaults: a
   bulleted navigation list, bare tables and system-font headings. It is laid
   out here as the sidebar-and-panel the templates already imply. */
/* Matched by what the container *holds*, not by `.woocommerce` alone: that
   class is not the account wrapper's own, it is sprinkled on nearly every
   block wrapper Woo emits. Selecting on it turned the store-notices block in
   the header into a two-column grid on every WooCommerce page — measured, the
   header notice became a 1440x40 grid container. :has() pins the rule to the
   one element that actually contains the account navigation. */
.woocommerce:has(> .woocommerce-MyAccount-navigation) {
  display: grid;
  grid-template-columns: 210px minmax(0, 1fr);
  gap: 40px;
  align-items: start;
  margin: 32px 0 64px;
}

@media (max-width: 782px) {
  .woocommerce:has(> .woocommerce-MyAccount-navigation) {
    grid-template-columns: minmax(0, 1fr);
    gap: 24px;
  }
}

/* Both panes pinned to their cell explicitly. Woo's classic stylesheet still
   lays this area out as floats with percentage widths (30% / 68%), which a
   grid item ignores for placement but not for sizing — the result measured as
   the navigation sitting in column 2 at 175px and the content wrapped onto a
   second row in column 1 at 209px, i.e. the two panes stacked and swapped.
   Naming the cells and clearing the float sizing puts them side by side
   regardless of what Woo's sheet asks for. */
.woocommerce:has(> .woocommerce-MyAccount-navigation) > .woocommerce-MyAccount-navigation {
  grid-column: 1;
  grid-row: 1;
  width: auto;
  float: none;
  margin: 0;
}

.woocommerce:has(> .woocommerce-MyAccount-navigation) > .woocommerce-MyAccount-content {
  grid-column: 2;
  grid-row: 1;
  width: auto;
  float: none;
  margin: 0;
}

@media (max-width: 782px) {
  .woocommerce:has(> .woocommerce-MyAccount-navigation) > .woocommerce-MyAccount-navigation,
  .woocommerce:has(> .woocommerce-MyAccount-navigation) > .woocommerce-MyAccount-content {
    grid-column: 1;
    grid-row: auto;
  }
}

/* Woo's own input skin (a 4px radius and a near-black border) sits on top of
   the theme's field recipe, so the glass layers painted but the frame around
   them stayed Woo's. Re-stated at a specificity that clears it. */
.woocommerce-page .woocommerce input.input-text:not(.qty),
.woocommerce-page .woocommerce textarea,
.woocommerce-page .woocommerce .woocommerce-Input:not(.qty),
.woocommerce-page .woocommerce input[type="password"],
.woocommerce-page .woocommerce input[type="email"],
.woocommerce-page .woocommerce input[type="text"]:not(.qty) {
  border: 1px solid transparent;
  border-radius: 3px;
  color: var(--ts-text);
  font-family: var(--ts-sans);
  font-size: 15px;
  width: 100%;
  box-sizing: border-box;
}

/* The quantity box carries .input-text too, and a blanket width:100% sized it
   against its own wrapper — which Woo leaves at its intrinsic width. Measured
   at 12px wide inside a 20px wrapper: a number field too narrow to show the
   number in it. It gets an explicit box instead. */
.woocommerce div.quantity,
.woocommerce .quantity {
  width: auto;
}

.woocommerce input.qty,
.wc-block-components-quantity-selector input {
  width: 4.5rem;
  min-width: 4.5rem;
  box-sizing: border-box;
  padding: 10px 8px;
  text-align: center;
  font-family: var(--ts-mono);
  font-size: 14px;
  color: var(--ts-text);
  border: 1px solid transparent;
  border-radius: 3px;
}

/* The login/register screen is a single centred form, not the two-pane
   dashboard — it has no navigation to sit beside, so it is selected the same
   way: by the form it contains. */
.woocommerce:has(> .woocommerce-form-login),
.woocommerce:has(> #customer_login) {
  max-width: 480px;
  margin-inline: auto;
}

/* Sidebar items as row blocks, the same construction the blog uses for its
   contents and glossary listings (.wp-block-post.glossary rows): a padded row
   with a hairline rule and a cursor-following glass overlay, rather than a
   text link with an underline.

   The link carries no underline of its own now. It had two — the sweep added
   for the masthead-nav treatment, and the prose sweep's resting 1px rule
   leaking in because the whole account area sits inside .wp-block-post-content
   — which is what showed as a double underline. The row is the affordance
   here, so both are switched off explicitly.

   The glass is painted on ::before rather than on the row itself, because the
   row is a flex container and its own background cannot carry the four-layer
   stack without disturbing the layout. --glass is declared on the
   pseudo-element: it is registered with inherits:false, so a value set on the
   <li> would never reach ::before. --mx/--my are seeded here because theme.js
   writes them to the row, and without a resting value the gradients are
   invalid at computed-value time and background-image collapses to none until
   the first pointermove. */
.woocommerce-MyAccount-navigation ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* WooCommerce pads these list items with `18px 0`, and the first one with
   `0 0 18px` — which is why the top item looked like it had all its air on
   the bottom while the rest looked evenly, hugely spaced. Measured, each row
   came to 90px tall for one word of text. The padding belongs to the link
   here (the whole row is the target), so the item's own is cleared. */
.woocommerce-MyAccount-navigation li {
  position: relative;
  isolation: isolate;
  margin: 0;
  border-bottom: 1px solid var(--ts-line-soft);
  --mx: 50%;
  --my: 50%;
}

/* Woo's own rules are `.woocommerce-account .woocommerce-MyAccount-navigation
   li { padding: 1em 0 }` plus a :first-child override that zeroes the top —
   (0,2,1) and (0,2,2), so a bare `.woocommerce-MyAccount-navigation li` here
   loses to both. The leading `body` carries this past them without
   importance, and the :first-child case is restated so the top item is not
   left with air on one side only. */
body.woocommerce-account .woocommerce-MyAccount-navigation li,
body.woocommerce-account .woocommerce-MyAccount-navigation li:first-child {
  padding: 0;
}

.woocommerce-MyAccount-navigation li:first-child {
  border-top: 1px solid var(--ts-line-soft);
}

/* Light-theme inversion of the blog's row glass: that one lifted a dark row
   toward --tn-surface, so this darkens a light row instead.

   Two layers, both clipped to the padding box. The blog's version had two
   more painted into the *border* box — a 240px "lit edge" radial that glowed
   through a 1px transparent border. Against a dark ground that reads as a rim
   light; against a light one the halo simply bleeds across the boundary, and
   the row looked soft-focus rather than lit. The edge is drawn as a crisp 1px
   inset ring below instead, so the row has a definite boundary and only its
   interior is a gradient. */
.woocommerce-MyAccount-navigation li::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  --glass: 0;
  background-image:
    radial-gradient(340px circle at var(--mx) var(--my),
      rgba(var(--ts-shade-rgb), calc(var(--glass) * .05)),
      rgba(var(--ts-shade-rgb), 0) 70%),
    linear-gradient(
      rgba(var(--ts-shade-rgb), calc(var(--glass) * .02)),
      rgba(var(--ts-shade-rgb), calc(var(--glass) * .02)));
  background-clip: padding-box, padding-box;
  background-repeat: no-repeat;
  border-radius: 3px;
  transition: --glass .3s ease;
}

/* Hover only. The current item used to keep --glass at 1 so it stayed lit,
   which after a click read as a hover that had got stuck on — the pointer had
   moved away but the row was still highlighted, and nothing distinguished
   "you are hovering this" from "you are on this". The current item is marked
   by the left bar and the darker label instead, and the glass is now purely a
   pointer affordance that always fades when the pointer leaves. */
.woocommerce-MyAccount-navigation li:hover::before { --glass: 1; }

/* Set like the blog's block items rather than like a nav bar: those rows put
   their titles in the display serif at weight 400 with a little tracking and
   normal case (.tn-contents .wp-block-post-title), which is what makes a list
   of them read as content rather than as chrome. The uppercase sans this
   started as belonged to the masthead nav, where the items are three short
   words in a horizontal strip — down a 210px column at .22em it read as a
   control panel. */
.woocommerce-MyAccount-navigation li a {
  display: block;
  padding: 9px 12px;
  line-height: 1.35;
  font-family: var(--ts-serif);
  font-size: 17px;
  font-weight: 400;
  letter-spacing: .025em;
  word-spacing: .06em;
  text-transform: none;
  color: var(--ts-muted);
  text-decoration: none;
  /* the row is the affordance — no underline of any kind on the label */
  background-image: none;
  transition: color .2s ease;
}

.woocommerce-MyAccount-navigation li:hover a { color: var(--ts-cyan); }
.woocommerce-MyAccount-navigation li.is-active a { color: var(--ts-heading); }

/* The crisp boundary the border-box layers used to smear, plus the current
   item's left marker. Both are inset shadows on the row, so the combined case
   is written out rather than left to whichever rule happens to win. */
.woocommerce-MyAccount-navigation li {
  transition: box-shadow .25s ease;
}

/* No ring on hover: the wash and the row's own rule are the affordance, and
   an outline appearing under the pointer made each item look like it had
   turned into a button. Only the current item keeps a marker. */
.woocommerce-MyAccount-navigation li.is-active,
.woocommerce-MyAccount-navigation li.is-active:hover {
  box-shadow: inset 2px 0 0 var(--ts-cyan);
}

.woocommerce-MyAccount-content {
  background: var(--ts-page);
  border: 1px solid var(--ts-line);
  border-radius: 3px;
  padding: 28px 32px 32px;
}

@media (max-width: 700px) {
  .woocommerce-MyAccount-content { padding: 20px 18px 24px; }
}

.woocommerce-MyAccount-content h2,
.woocommerce-MyAccount-content h3,
.woocommerce-account .woocommerce h2 {
  font-family: var(--ts-serif);
  color: var(--ts-heading);
  font-weight: 400;
}

/* Form furniture: labels read as field names, so they take the small-caps
   meta treatment the rest of the theme uses for them. */
.woocommerce form .form-row,
.woocommerce-EditAccountForm .form-row,
.woocommerce-address-fields .form-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0 0 18px;
}

.woocommerce form .form-row label,
.woocommerce-EditAccountForm label,
.woocommerce-address-fields label {
  font-family: var(--ts-mono);
  font-size: 11.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ts-dim);
}

.woocommerce form .form-row input,
.woocommerce form .form-row textarea,
.woocommerce-EditAccountForm input,
.woocommerce-address-fields input {
  padding: 10px 12px;
  font-size: 15px;
  color: var(--ts-text);
  width: 100%;
}

.woocommerce form .form-row .required {
  color: var(--ts-turquoise);
  text-decoration: none;
}

.woocommerce fieldset {
  border: 1px solid var(--ts-line);
  border-radius: 3px;
  padding: 20px 22px;
  margin: 28px 0 22px;
}

.woocommerce fieldset legend {
  font-family: var(--ts-mono);
  font-size: 11.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ts-dim);
  padding: 0 8px;
}

.woocommerce-form-login__rememberme,
.woocommerce-form__label-for-checkbox {
  font-family: var(--ts-sans);
  font-size: 14px;
  text-transform: none;
  letter-spacing: 0;
  color: var(--ts-text);
}

/* Orders / downloads / addresses tables. */
.woocommerce-MyAccount-content table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
}

.woocommerce-MyAccount-content table th {
  font-family: var(--ts-mono);
  font-size: 11.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ts-dim);
  text-align: left;
  padding: 10px 12px;
  border-bottom: 2px solid var(--ts-line);
}

.woocommerce-MyAccount-content table td {
  padding: 12px;
  border-bottom: 1px solid var(--ts-line-soft);
  color: var(--ts-text);
}

.woocommerce-MyAccount-content .woocommerce-Address {
  border: 1px solid var(--ts-line);
  border-radius: 3px;
  padding: 18px 20px;
}

/* Woo's own notices, which appear on nearly every account action. */
.woocommerce-message,
.woocommerce-info,
.woocommerce-error {
  background: var(--ts-surface);
  border: 1px solid var(--ts-line);
  border-left: 3px solid var(--ts-cyan);
  border-radius: 3px;
  padding: 14px 18px;
  margin: 0 0 22px;
  font-size: 14px;
  color: var(--ts-text);
  list-style: none;
}

.woocommerce-error {
  border-left-color: #B3261E;
}

.woocommerce-message a,
.woocommerce-info a {
  color: var(--ts-cyan);
}

/* All Woo <select>s, not just catalog sorting: same flat field with a drawn
   arrow, since appearance:none removes the native one. */
.woocommerce select,
.woocommerce-page select {
  appearance: none;
  -webkit-appearance: none;
  font-family: var(--ts-mono);
  font-size: 13px;
  color: var(--ts-text);
  background-color: var(--ts-surface);
  background-image:
    linear-gradient(45deg, transparent 50%, var(--ts-muted) 50%),
    linear-gradient(135deg, var(--ts-muted) 50%, transparent 50%);
  background-position:
    calc(100% - 15px) calc(50% + 1px),
    calc(100% - 10px) calc(50% + 1px);
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  border: 1px solid var(--ts-faint);
  border-radius: 3px;
  padding: 9px 32px 9px 12px;
  cursor: pointer;
}

/* Woo's inline field hints ("This will be how your name will be displayed…").
   They arrive as unstyled italic body copy at the same size as the value the
   user just typed, which reads as content rather than as help. */
.woocommerce form .form-row span.description,
.woocommerce-MyAccount-content span.description,
.woocommerce em {
  display: block;
  font-family: var(--ts-sans);
  font-size: 13px;
  font-style: normal;
  line-height: 1.5;
  color: var(--ts-dim);
  margin-top: 4px;
}

/* ── late corrections ────────────────────────────────── */

/* WooCommerce sets `border: none` on the product-card button. The theme's
   button border is not a border colour but the fourth background layer,
   painted into the border box — with the border width at 0 there is no
   border box to paint into, so the card buttons rendered frameless while
   every other button on the site kept its outline. Restoring the 1px
   transparent border gives the gradient somewhere to land again. */
.wc-block-product .wc-block-components-product-button__button,
.ts-grid .wc-block-product .wp-block-button__link,
.wc-block-components-product-button__button {
  border: 1px solid transparent;
}

/* Table headers. Deep (#ECEFF1) against the table's own surface (#F0F2F4) is
   a four-level step — technically a different colour, visually the same one,
   which is why the header row did not read as a header. It gets a tone below
   the body rows instead, heading-coloured text, and the thick-thin rule the
   theme uses elsewhere to close a section label. */
:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) thead th,
.woocommerce-MyAccount-content table th,
.woocommerce table.shop_table th {
  background: #DDE4E8;
  color: var(--ts-heading);
  border-bottom: 2px solid var(--ts-faint);
}

/* The dot of contrast the zebra striping adds is now redundant against a
   header that carries its own weight, so the even-row wash is lightened to
   keep the body calm under it. */
:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel) tbody tr:nth-child(even) {
  background-color: rgba(var(--ts-shade-rgb), .018);
}

/* Product meta links — the category and tag lists under a product summary,
   and the term links on a card. They are prose links in every sense the
   reader cares about, but they sit outside the containers the sweep was
   scoped to, so they were the one link style on the site with no hover
   affordance at all. */
.wp-block-post-terms a,
.product_meta a,
.posted_in a,
.tagged_as a,
.woocommerce-breadcrumb a {
  color: var(--ts-cyan);
  text-decoration: none;
  padding-bottom: 2px;
  background-image:
    linear-gradient(var(--ts-cyan), var(--ts-cyan)),
    linear-gradient(var(--ts-link-rest), var(--ts-link-rest));
  background-repeat: no-repeat;
  background-position: right bottom, left bottom;
  background-size: 0% 2px, 100% 1px;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
  transition: background-size .28s ease;
}

.wp-block-post-terms a:hover,
.product_meta a:hover,
.posted_in a:hover,
.tagged_as a:hover,
.woocommerce-breadcrumb a:hover {
  background-position: left bottom, left bottom;
  background-size: 100% 2px, 100% 1px;
}

/* Product tab headers: the rule below was losing to Woo's own tab styling, so
   the inactive and active tabs were the same colour. */
.woocommerce-tabs ul.tabs li a,
.woocommerce div.product .woocommerce-tabs ul.tabs li a {
  color: var(--ts-dim);
  text-decoration: none;
}

.woocommerce-tabs ul.tabs li.active a,
.woocommerce div.product .woocommerce-tabs ul.tabs li.active a {
  color: var(--ts-heading);
}

/* The open <select> list is drawn by the platform, and only a few properties
   cross that boundary — but background and colour do in Chromium and Firefox,
   which is enough to stop the sorting dropdown flashing a white system menu
   over a themed page. */
.woocommerce select option,
.ts-shop-bar select option,
.woocommerce-page select option {
  background-color: var(--ts-page);
  color: var(--ts-text);
}

/* Primary action labels. Stated after Woo's own button colours so the accent
   actually lands — measured before this, the single add-to-cart label was
   still inheriting the body text colour. */
.woocommerce button.single_add_to_cart_button,
.woocommerce .wc-block-cart__submit-button,
.wc-block-components-checkout-place-order-button,
.woocommerce a.checkout-button {
  color: var(--ts-cyan);
  font-weight: 600;
}


/* ── filled account and cart glyphs ──────────────────── */

/* WooCommerce ships both controls as thin outline strokes, which sat lighter
   than everything around them in the masthead. They are replaced with filled
   glyphs — but as masks rather than as inline SVG, so the icon is painted with
   background-color: currentColor and keeps inheriting the muted -> cyan hover
   and the lift from the rules above. Swapping the markup instead would have
   meant filtering Woo's block output and re-implementing its cart badge. */
.ts-shop-actions .wc-block-customer-account__link svg,
.ts-shop-actions .wc-block-mini-cart__button svg {
  display: none;
}

.ts-shop-actions .wc-block-customer-account__link::before,
.ts-shop-actions .wc-block-mini-cart__quantity-badge::before {
  content: "";
  display: block;
  width: 20px;
  height: 20px;
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}

.ts-shop-actions .wc-block-customer-account__link::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M12%2012c2.21%200%204-1.79%204-4s-1.79-4-4-4-4%201.79-4%204%201.79%204%204%204zm0%202c-2.67%200-8%201.34-8%204v2h16v-2c0-2.66-5.33-4-8-4z%27%2F%3E%3C%2Fsvg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M12%2012c2.21%200%204-1.79%204-4s-1.79-4-4-4-4%201.79-4%204%201.79%204%204%204zm0%202c-2.67%200-8%201.34-8%204v2h16v-2c0-2.66-5.33-4-8-4z%27%2F%3E%3C%2Fsvg%3E");
}

.ts-shop-actions .wc-block-mini-cart__quantity-badge::before {
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M7%2018c-1.1%200-1.99.9-1.99%202S5.9%2022%207%2022s2-.9%202-2-.9-2-2-2zM1%202v2h2l3.6%207.59-1.35%202.45c-.16.28-.25.61-.25.96%200%201.1.9%202%202%202h12v-2H7.42c-.14%200-.25-.11-.25-.25l.03-.12.9-1.63h7.45c.75%200%201.41-.41%201.75-1.03l3.58-6.49A1.003%201.003%200%200%200%2020%204H5.21l-.94-2H1zm16%2016c-1.1%200-1.99.9-1.99%202s.89%202%201.99%202%202-.9%202-2-.9-2-2-2z%27%2F%3E%3C%2Fsvg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M7%2018c-1.1%200-1.99.9-1.99%202S5.9%2022%207%2022s2-.9%202-2-.9-2-2-2zM1%202v2h2l3.6%207.59-1.35%202.45c-.16.28-.25.61-.25.96%200%201.1.9%202%202%202h12v-2H7.42c-.14%200-.25-.11-.25-.25l.03-.12.9-1.63h7.45c.75%200%201.41-.41%201.75-1.03l3.58-6.49A1.003%201.003%200%200%200%2020%204H5.21l-.94-2H1zm16%2016c-1.1%200-1.99.9-1.99%202s.89%202%201.99%202%202-.9%202-2-.9-2-2-2z%27%2F%3E%3C%2Fsvg%3E");
}

/* The stuck masthead shrinks the hit area; the glyph follows it down. */
html.ts-stuck .ts-shop-actions .wc-block-customer-account__link::before,
html.ts-stuck .ts-shop-actions .wc-block-mini-cart__quantity-badge::before {
  width: 18px;
  height: 18px;
}

/* the blog's social row spacing */
.ts-shop-actions { gap: 9px; }

/* ── quantity stepper ────────────────────────────────── */

/* The - and + buttons are built by theme.js; this styles the three parts as
   one joined control: two real buttons either side of a real field.

   The frame is --ts-line, the same colour the shared button recipe paints its
   border with (layer 4), so the stepper and the ADD TO CART button beside it
   are outlined identically rather than one in --ts-faint and one in --ts-line.
   Height is pinned to 42px, which is what that button measures, so the two sit
   on one baseline instead of the stepper riding 6px shorter.

   The native spin arrows are suppressed rather than left underneath: two
   controls for the same value, one of them 8px wide and drawn by the
   platform, is worse than either alone. */
.ts-qty {
  display: inline-flex;
  align-items: stretch;
  /* border-box, or the 1px frame is added to the 42px and the stepper stands
     2px taller than the button it is meant to sit level with. */
  box-sizing: border-box;
  height: 42px;
  border: 1px solid var(--ts-line);
  border-radius: 3px;
  background: var(--ts-surface);
  overflow: hidden;
  transition: border-color .2s ease, box-shadow .2s ease;
}

.ts-qty input.qty {
  --glass: 0;
  --mx: 50%;
  --my: 50%;
  width: 3.25rem;
  min-width: 3.25rem;
  border: 0;
  border-radius: 0;
  background-color: transparent;
  background-image:
    radial-gradient(90px circle at var(--mx) var(--my),
      rgba(var(--ts-glow-rgb), calc(var(--glass) * .16)),
      rgba(var(--ts-glow-rgb), 0) 65%),
    linear-gradient(
      rgba(var(--ts-shade-rgb), calc(var(--glass) * .04)),
      rgba(var(--ts-shade-rgb), calc(var(--glass) * .04)));
  background-repeat: no-repeat;
  padding: 0 4px;
  text-align: center;
  font-family: var(--ts-mono);
  font-size: 14px;
  color: var(--ts-text);
  -moz-appearance: textfield;
  appearance: textfield;
  transition: --glass .28s ease, background-color .2s ease;
}

.ts-qty input.qty::-webkit-outer-spin-button,
.ts-qty input.qty::-webkit-inner-spin-button {
  -webkit-appearance: none;
  appearance: none;
  margin: 0;
}

/* The number is a field, so it takes the field's cues — but it takes them the
   way everything else on the site does, as a wash that follows the pointer
   rather than a flat colour swap. It had neither for a while: the !important
   block below (needed to beat Woo's own sizing) carried
   `background: transparent !important`, which silently overrode every hover
   fill declared here. That declaration is gone; only the box metrics are
   forced now. */
.ts-qty input.qty:hover:not(:focus) {
  --glass: 1;
}

.ts-qty input.qty:focus {
  outline: none;
  --glass: 0;
  background-color: var(--ts-deep);
}

.ts-qty:focus-within {
  border-color: var(--ts-cyan-dim);
  box-shadow: 0 0 0 3px rgba(var(--ts-glow-rgb), .20);
}

/* The steppers are buttons, so they take the button's cursor-following glass
   — the same two padding-box layers the shared recipe uses, minus its border
   layers, since the frame belongs to the control around them. --mx/--my are
   fed by theme.js, which now tracks these too. */
.ts-qty-btn {
  --glass: 0;
  --mx: 50%;
  --my: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  border: 0;
  background-color: transparent;
  background-image:
    radial-gradient(90px circle at var(--mx) var(--my),
      rgba(var(--ts-glow-rgb), calc(var(--glass) * .20)),
      rgba(var(--ts-glow-rgb), 0) 65%),
    linear-gradient(
      rgba(var(--ts-shade-rgb), calc(var(--glass) * .05)),
      rgba(var(--ts-shade-rgb), calc(var(--glass) * .05)));
  background-repeat: no-repeat;
  color: var(--ts-dim);
  font-family: var(--ts-mono);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  transition: --glass .28s ease, color .2s ease;
}

/* The shared button recipe lights its border under the cursor, and does so as
   a single 1px line: its border is transparent and the gradient paints inside
   that same 1px band, so nothing doubles up.

   An inset ring on these buttons cannot behave that way — the button sits
   flush inside the control's own frame, so a 1px ring lands immediately
   against the wrapper's 1px border and the two read as one 2px edge. The
   accent is put on the frame itself instead: hovering either stepper lights
   the control's outline and the divider beside the field, which is one
   continuous 1px line and no thicker than any other button's. */
.ts-qty-btn:hover:not(:disabled) {
  --glass: 1;
  color: var(--ts-cyan);
}

/* Hovering any part of the control lights its outline — the two buttons and
   the field between them are one control, so the frame responds as one. Plain
   :hover rather than :has(), since it covers every part and needs no support
   caveat. Focus still overrides this with the stronger cyan-dim + ring below. */
.ts-qty:hover {
  border-color: var(--ts-cyan-dim);
}

.ts-qty-minus:hover:not(:disabled) { border-right-color: var(--ts-cyan-dim); }
.ts-qty-plus:hover:not(:disabled)  { border-left-color: var(--ts-cyan-dim); }

.ts-qty-btn:focus-visible {
  outline: none;
  color: var(--ts-cyan);
  box-shadow: inset 0 0 0 2px rgba(var(--ts-glow-rgb), .35);
}

.ts-qty-btn:disabled {
  color: var(--ts-faint);
  cursor: not-allowed;
}

.ts-qty-minus { border-right: 1px solid var(--ts-line); }
.ts-qty-plus  { border-left: 1px solid var(--ts-line); }

/* The add-to-cart row: stepper and button on one baseline. */
.woocommerce div.product form.cart {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

/* ── quantity stepper: winning against Woo's own sizing ── */

/* WooCommerce sizes this field with
     .wp-block-woocommerce-add-to-cart-form form.cart>.quantity:not(.wc-block-components-quantity-selector) .qty
   — six compound selectors, specificity (0,5,1), and its stylesheet is
   printed after the theme's, so an equally specific rule here loses the tie.
   Matching that with a longer selector would mean mirroring the plugin's
   internal block structure in the theme and re-checking it on every Woo
   release.

   These few declarations are marked important instead, scoped to .ts-qty —
   a class that exists only on a control this theme's own script built, and
   which is therefore never a surface anyone else is styling. It is the
   narrow case importance is for: reclaiming an element we replaced. */
.ts-qty input.qty {
  padding: 9px 4px !important;
  margin: 0 !important;
  width: 3.25rem !important;
  min-width: 3.25rem !important;
  border: 0 !important;
  text-align: center !important;
}

/* ── authored table header rows ──────────────────────── */

/* A table whose first row was authored as plain cells (no <thead>) is
   promoted by theme.js where it can reach it, but the styling must not
   depend on that having happened — a table outside the script's scope, or
   any page before the deferred bundle runs, still needs its header row to
   read as one. Same tone as the promoted <thead> above. */
:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel)
  table:not(:has(thead)) tbody tr:first-child td,
:is(.ts-article .wp-block-post-content, .woocommerce-Tabs-panel)
  table:not(:has(thead)) tbody tr:first-child th {
  background: #DDE4E8;
  color: var(--ts-heading);
  border-bottom: 2px solid var(--ts-faint);
}

/* The single-product sale flash is a different element from the grid card's
   badge (span.onsale vs .wc-block-components-product-sale-badge) and kept
   Woo's default olive circle, which is the one colour on the page that
   belongs to no palette here. Matched to the card badge instead. */
.woocommerce span.onsale,
.woocommerce-page span.onsale {
  position: absolute;
  top: 10px;
  left: 10px;
  min-height: 0;
  min-width: 0;
  line-height: 1;
  padding: 5px 10px;
  border-radius: 2px;
  background: var(--ts-turquoise);
  color: #fff;
  font-family: var(--ts-mono);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
}

/* The static cart link substituted on cart/checkout (see functions.php). It
   reuses the mini-cart classes so the icon mask and hover apply, but it is an
   <a>, so the badge needs its positioning context restated here. */
.ts-cart-static .wc-block-mini-cart__button {
  text-decoration: none;
  background-image: none;
}

.ts-cart-static .wc-block-mini-cart__quantity-badge {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* WooCommerce underlines the current account item itself, with
   `.woocommerce-account .woocommerce-MyAccount-navigation li.is-active a`
   — specificity (0,3,2), and its stylesheet is printed after the theme's, so
   an equal selector here loses. That underline was the second of the two the
   sidebar was showing. The row's glass and the left marker already say which
   item is current, so it is turned off; the leading `body` is what carries
   this past Woo's rule without resorting to importance. */
body.woocommerce-account .woocommerce-MyAccount-navigation li.is-active a,
body.woocommerce-account .woocommerce-MyAccount-navigation li a {
  text-decoration: none;
}

/* ── mini-cart drawer ────────────────────────────────── */

/* The panel that slides in from the right after adding to the cart. It is
   rendered entirely by WooCommerce's block runtime, so none of the theme's
   block styling reaches it and it arrived as browser defaults sitting on a
   plain white sheet: an underlined product link, a grey "Save …" chip and a
   stepper that looked nothing like the one on the product page. */
/* Frosted like the masthead. Same recipe — saturate(180%) blur(20px) over a
   translucent tint — because blurring an opaque fill is a no-op: the panel
   has to let some of the page through before there is anything to blur. It is
   painted on an overlay rather than on the panel itself so the filter's
   sampling boundary sits behind the content, which is what keeps text on the
   panel from being resampled as it slides in. */
.wc-block-mini-cart__drawer .wc-block-components-drawer__content {
  position: relative;
  isolation: isolate;
  background: rgba(255, 255, 255, .78);
  border-left: 1px solid var(--ts-line);
  box-shadow: -8px 0 28px rgba(var(--ts-shade-rgb), .10);
}

.wc-block-mini-cart__drawer .wc-block-components-drawer__content::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  transform: translateZ(0);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
}

/* The footer sits on the same translucent stack, so its opaque surface fill
   would punch a solid block through the frost. */
.wc-block-mini-cart__drawer .wc-block-mini-cart__footer {
  background: rgba(240, 242, 244, .72);
}

.wc-block-mini-cart__title {
  font-family: var(--ts-serif);
  font-weight: 400;
  color: var(--ts-heading);
  border-bottom: 1px solid var(--ts-line);
  padding-bottom: 14px;
}

.wp-block-woocommerce-mini-cart-title-items-counter-block {
  font-family: var(--ts-mono);
  font-size: 13px;
  color: var(--ts-dim);
}

/* Product name: the theme's link treatment rather than a default underline. */
.wc-block-mini-cart__drawer .wc-block-components-product-name {
  font-family: var(--ts-sans);
  font-size: 15px;
  color: var(--ts-heading);
  text-decoration: none;
  background-image:
    linear-gradient(var(--ts-cyan), var(--ts-cyan)),
    linear-gradient(var(--ts-link-rest), var(--ts-link-rest));
  background-repeat: no-repeat;
  background-position: right bottom, left bottom;
  background-size: 0% 2px, 100% 1px;
  padding-bottom: 2px;
  transition: background-size .28s ease, color .2s ease;
}

.wc-block-mini-cart__drawer .wc-block-components-product-name:hover {
  color: var(--ts-cyan);
  background-position: left bottom, left bottom;
  background-size: 100% 2px, 100% 1px;
}

/* Prices: mono, with the same struck-original / turquoise-offer pairing the
   product cards use, so a discount reads the same wherever it appears. */
.wc-block-mini-cart__drawer .wc-block-components-product-price,
.wc-block-mini-cart__drawer .wc-block-formatted-money-amount,
.wc-block-mini-cart__drawer .wc-block-components-product-metadata {
  font-family: var(--ts-mono);
  color: var(--ts-heading);
}

.wc-block-mini-cart__drawer .wc-block-components-product-price del,
.wc-block-mini-cart__drawer .wc-block-components-product-price del * {
  color: var(--ts-dim);
}

.wc-block-mini-cart__drawer .wc-block-components-product-price ins,
.wc-block-mini-cart__drawer .wc-block-components-product-price ins * {
  text-decoration: none;
  color: var(--ts-turquoise);
}

.wc-block-mini-cart__drawer .wc-block-components-product-metadata__description {
  font-family: var(--ts-sans);
  font-size: 13px;
  color: var(--ts-dim);
}

/* The "Save 30,00 €" chip arrived as a default grey box. */
.wc-block-components-sale-badge,
.wc-block-components-product-badge {
  background: var(--ts-turquoise);
  color: #fff;
  border: none;
  border-radius: 2px;
  padding: 3px 8px;
  font-family: var(--ts-mono);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
}

/* Woo's own block stepper, matched to the .ts-qty control on the product
   page so the same action looks the same in both places. */
.wc-block-components-quantity-selector {
  border: 1px solid var(--ts-faint);
  border-radius: 3px;
  background: var(--ts-surface);
  overflow: hidden;
}

.wc-block-components-quantity-selector__button {
  border: 0;
  background: transparent;
  color: var(--ts-dim);
  font-family: var(--ts-mono);
  transition: color .2s ease, background-color .2s ease;
}

.wc-block-components-quantity-selector__button:hover:not(:disabled) {
  color: var(--ts-cyan);
  background-color: var(--ts-deep);
}

.wc-block-components-quantity-selector__input {
  font-family: var(--ts-mono);
  color: var(--ts-text);
  background: transparent;
  border: 0;
}

/* Remove-item control: a glyph, so it follows the icon vocabulary. */
.wc-block-cart-item__remove-link,
.wc-block-components-product-remove {
  font-family: var(--ts-mono);
  font-size: 12px;
  color: var(--ts-dim);
  transition: color .2s ease;
}

.wc-block-cart-item__remove-link:hover,
.wc-block-components-product-remove:hover {
  color: var(--ts-cyan);
}

/* Footer: subtotal in mono, sitting above a rule. */
.wc-block-mini-cart__footer {
  border-top: 1px solid var(--ts-line);
  background: var(--ts-surface);
}

.wc-block-mini-cart__footer .wc-block-components-totals-item__label {
  font-family: var(--ts-mono);
  font-size: 12px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ts-dim);
}

.wc-block-mini-cart__footer .wc-block-components-totals-item__description {
  font-family: var(--ts-sans);
  font-size: 12px;
  color: var(--ts-dim);
}

.wc-block-mini-cart__footer-actions {
  gap: 10px;
}

/* ── store notices and banners ───────────────────────── */

/* The banners Woo raises for "added to cart", coupon results and checkout
   errors. Left-accented like the classic notices above, with the accent
   carrying the meaning rather than a flood fill. */
.wc-block-components-notice-banner {
  background: var(--ts-surface);
  border: 1px solid var(--ts-line);
  border-left: 3px solid var(--ts-cyan);
  border-radius: 3px;
  padding: 13px 16px;
  font-family: var(--ts-sans);
  font-size: 14px;
  color: var(--ts-text);
}

.wc-block-components-notice-banner.is-success { border-left-color: var(--ts-turquoise); }
.wc-block-components-notice-banner.is-error   { border-left-color: #B3261E; }
.wc-block-components-notice-banner.is-warning { border-left-color: #A66300; }
.wc-block-components-notice-banner.is-info    { border-left-color: var(--ts-blue); }



/* Links inside a banner. They were left as a bare colour change, which is the
   one link style the site does not otherwise use — everything else either
   sweeps an underline or is a button. Action links ("View cart", "Confirm
   email address") take the same sweep as prose links so they read as links,
   and any link Woo renders as a button in there keeps the button recipe. */
.wc-block-components-notice-banner a:not(.wp-element-button):not(.button),
.woocommerce-message a:not(.wp-element-button):not(.button),
.woocommerce-info a:not(.wp-element-button):not(.button),
.woocommerce-error a:not(.wp-element-button):not(.button) {
  color: var(--ts-cyan);
  text-decoration: none;
  padding-bottom: 2px;
  background-image:
    linear-gradient(var(--ts-cyan), var(--ts-cyan)),
    linear-gradient(var(--ts-link-rest), var(--ts-link-rest));
  background-repeat: no-repeat;
  background-position: right bottom, left bottom;
  background-size: 0% 2px, 100% 1px;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
  transition: background-size .28s ease;
}

.wc-block-components-notice-banner a:not(.wp-element-button):not(.button):hover,
.woocommerce-message a:not(.wp-element-button):not(.button):hover,
.woocommerce-info a:not(.wp-element-button):not(.button):hover,
.woocommerce-error a:not(.wp-element-button):not(.button):hover {
  background-position: left bottom, left bottom;
  background-size: 100% 2px, 100% 1px;
}

/* The "View cart" link WooCommerce appends beside a card's add-to-cart button
   once the item is in the cart. It was the one control on the grid still set
   in the browser's default face. */
.added_to_cart {
  display: inline-block;
  margin-top: 8px;
  font-family: var(--ts-mono);
  font-size: 12px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ts-cyan);
  text-decoration: none;
  background-image: linear-gradient(var(--ts-cyan), var(--ts-cyan));
  background-repeat: no-repeat;
  background-position: right bottom;
  background-size: 0% 2px;
  transition: background-size .28s ease;
}

.added_to_cart:hover {
  background-position: left bottom;
  background-size: 100% 2px;
}

/* In the drawer footer the two actions arrived weighted the wrong way round:
   "View my cart" drew a heavier frame than "Go to checkout", so the secondary
   action read as the primary one. The fills stay identical — only the label
   accent marks which is the forward step, the same way it does on the product
   page and in the cart. */
.wc-block-mini-cart__footer-actions .wc-block-mini-cart__footer-checkout,
.wc-block-mini-cart__footer-actions a:last-child {
  color: var(--ts-cyan);
  font-weight: 600;
}

/* Woo paints its own tinted fills on these banners — a mint #F4FFF7 for
   success, pink for error — with `.wc-block-components-notice-banner.is-*`
   at (0,2,0), which beat the single-class rule above and left an off-palette
   green panel sitting in the middle of the storefront. The element qualifier
   takes these to (0,2,1) without importance. The colour still carries the
   meaning; it does so in the left accent, where the rest of the site puts it. */
div.wc-block-components-notice-banner,
div.wc-block-components-notice-banner.is-success,
div.wc-block-components-notice-banner.is-error,
div.wc-block-components-notice-banner.is-warning,
div.wc-block-components-notice-banner.is-info {
  background-color: var(--ts-surface);
  color: var(--ts-text);
  padding: 13px 16px;
  /* Woo rings the whole banner in the status colour. Only the left edge
     should carry it — the other three go back to the ordinary panel hairline,
     and the border-left-color rules below then re-apply the accent. */
  border-color: var(--ts-line);
  border-left-width: 3px;
}

div.wc-block-components-notice-banner.is-success { border-left-color: var(--ts-turquoise); }
div.wc-block-components-notice-banner.is-error   { border-left-color: #B3261E; }
div.wc-block-components-notice-banner.is-warning { border-left-color: #A66300; }
div.wc-block-components-notice-banner.is-info    { border-left-color: var(--ts-blue); }

/* ── notice banner icons ─────────────────────────────── */

/* WooCommerce's own status glyphs are thin outlines, and the theme was then
   dimming them to .75 opacity — measured on the Orders panel, the info glyph
   came out as #1F6BB0 at 75% over the surface fill, which is well under the
   3:1 a meaningful icon needs. They are replaced with filled glyphs drawn from
   the palette, at full strength.

   Masks rather than inline SVG, for the same reason as the masthead icons:
   the glyph is painted with background-color, so each variant just sets a
   colour and the shape follows. Woo's own svg is hidden rather than restyled,
   because its path is an outline and no fill will thicken it. */
.wc-block-components-notice-banner > svg {
  display: none;
}

.wc-block-components-notice-banner::before {
  content: "";
  flex: none;
  align-self: flex-start;
  width: 20px;
  height: 20px;
  margin-right: 12px;
  background-color: var(--ts-cyan);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}

div.wc-block-components-notice-banner.is-success::before {
  background-color: var(--ts-turquoise);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M12%202a10%2010%200%201%200%200%2020%2010%2010%200%200%200%200-20zm-1.2%2014.4-4.2-4.2%201.7-1.7%202.5%202.5%205.1-5.1%201.7%201.7-6.8%206.8z%27%2F%3E%3C%2Fsvg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M12%202a10%2010%200%201%200%200%2020%2010%2010%200%200%200%200-20zm-1.2%2014.4-4.2-4.2%201.7-1.7%202.5%202.5%205.1-5.1%201.7%201.7-6.8%206.8z%27%2F%3E%3C%2Fsvg%3E");
}

div.wc-block-components-notice-banner.is-info::before {
  background-color: var(--ts-blue);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M12%202a10%2010%200%201%200%200%2020%2010%2010%200%200%200%200-20zm1.2%2015.2h-2.4v-7.2h2.4v7.2zm0-8.8h-2.4V6h2.4v2.4z%27%2F%3E%3C%2Fsvg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M12%202a10%2010%200%201%200%200%2020%2010%2010%200%200%200%200-20zm1.2%2015.2h-2.4v-7.2h2.4v7.2zm0-8.8h-2.4V6h2.4v2.4z%27%2F%3E%3C%2Fsvg%3E");
}

div.wc-block-components-notice-banner.is-warning::before {
  background-color: #A66300;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M1%2021h22L12%202%201%2021zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z%27%2F%3E%3C%2Fsvg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M1%2021h22L12%202%201%2021zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z%27%2F%3E%3C%2Fsvg%3E");
}

div.wc-block-components-notice-banner.is-error::before {
  background-color: #B3261E;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M12%202a10%2010%200%201%200%200%2020%2010%2010%200%200%200%200-20zm5%2013.6L15.6%2017%2012%2013.4%208.4%2017%207%2015.6%2010.6%2012%207%208.4%208.4%207%2012%2010.6%2015.6%207%2017%208.4%2013.4%2012%2017%2015.6z%27%2F%3E%3C%2Fsvg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M12%202a10%2010%200%201%200%200%2020%2010%2010%200%200%200%200-20zm5%2013.6L15.6%2017%2012%2013.4%208.4%2017%207%2015.6%2010.6%2012%207%208.4%208.4%207%2012%2010.6%2015.6%207%2017%208.4%2013.4%2012%2017%2015.6z%27%2F%3E%3C%2Fsvg%3E");
}

/* The classic (non-block) notices carry no icon of their own at all. */
.woocommerce-message::before,
.woocommerce-info::before,
.woocommerce-error::before {
  content: "";
  display: inline-block;
  vertical-align: -4px;
  width: 18px;
  height: 18px;
  margin-right: 10px;
  background-color: var(--ts-cyan);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}

.woocommerce-message::before {
  background-color: var(--ts-turquoise);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M12%202a10%2010%200%201%200%200%2020%2010%2010%200%200%200%200-20zm-1.2%2014.4-4.2-4.2%201.7-1.7%202.5%202.5%205.1-5.1%201.7%201.7-6.8%206.8z%27%2F%3E%3C%2Fsvg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M12%202a10%2010%200%201%200%200%2020%2010%2010%200%200%200%200-20zm-1.2%2014.4-4.2-4.2%201.7-1.7%202.5%202.5%205.1-5.1%201.7%201.7-6.8%206.8z%27%2F%3E%3C%2Fsvg%3E");
}

.woocommerce-info::before {
  background-color: var(--ts-blue);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M12%202a10%2010%200%201%200%200%2020%2010%2010%200%200%200%200-20zm1.2%2015.2h-2.4v-7.2h2.4v7.2zm0-8.8h-2.4V6h2.4v2.4z%27%2F%3E%3C%2Fsvg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M12%202a10%2010%200%201%200%200%2020%2010%2010%200%200%200%200-20zm1.2%2015.2h-2.4v-7.2h2.4v7.2zm0-8.8h-2.4V6h2.4v2.4z%27%2F%3E%3C%2Fsvg%3E");
}

.woocommerce-error::before {
  background-color: #B3261E;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M12%202a10%2010%200%201%200%200%2020%2010%2010%200%200%200%200-20zm5%2013.6L15.6%2017%2012%2013.4%208.4%2017%207%2015.6%2010.6%2012%207%208.4%208.4%207%2012%2010.6%2015.6%207%2017%208.4%2013.4%2012%2017%2015.6z%27%2F%3E%3C%2Fsvg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M12%202a10%2010%200%201%200%200%2020%2010%2010%200%200%200%200-20zm5%2013.6L15.6%2017%2012%2013.4%208.4%2017%207%2015.6%2010.6%2012%207%208.4%208.4%207%2012%2010.6%2015.6%207%2017%208.4%2013.4%2012%2017%2015.6z%27%2F%3E%3C%2Fsvg%3E");
}

/* ── notice toasts ───────────────────────────────────── */

/* The fixed layer the store notices are moved into by theme.js. Offset far
   enough down to clear the masthead in both its resting and collapsed states,
   so a toast never covers the cart control the shopper is most likely to want
   immediately after adding something. */
.ts-toasts {
  position: fixed;
  top: 96px;
  right: 24px;
  z-index: 999;
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: min(420px, calc(100vw - 48px));
  pointer-events: none;
}

@media (max-width: 700px) {
  .ts-toasts {
    top: 84px;
    right: 12px;
    left: 12px;
    width: auto;
  }
}

/* Frosted like the masthead and the cart drawer: a translucent tint with the
   same saturate(180%) blur(20px) behind it. The filter goes on the element
   itself here rather than on an overlay — a toast is small, short-lived and
   never scrolls, so the sampling-edge flicker that made the header need an
   overshooting pseudo-element does not arise. */
.ts-toasts div.wc-block-components-notice-banner,
.ts-toasts div.wc-block-components-notice-banner.is-success,
.ts-toasts div.wc-block-components-notice-banner.is-error,
.ts-toasts div.wc-block-components-notice-banner.is-warning,
.ts-toasts div.wc-block-components-notice-banner.is-info {
  position: relative;
  pointer-events: auto;
  margin: 0;
  padding-right: 42px;
  background-color: rgba(240, 242, 244, .82);
  -webkit-backdrop-filter: saturate(180%) blur(20px);
  backdrop-filter: saturate(180%) blur(20px);
  box-shadow: 0 6px 24px rgba(var(--ts-shade-rgb), .14);
  /* entrance: slide in from the right it is anchored to */
  opacity: 0;
  transform: translateX(12px);
  transition: opacity .26s ease, transform .26s ease;
}

/* These must out-specify the block above, which is (0,3,1) because the fill
   had to beat `div.wc-block-components-notice-banner`. At (0,3,0) the
   entrance state lost to its own base rule's `opacity: 0`, so a toast was
   moved out of the header and then never faded in — it flashed once where it
   was rendered and was invisible thereafter. */
.ts-toasts div.wc-block-components-notice-banner.is-in {
  opacity: 1;
  transform: none;
}

.ts-toasts div.wc-block-components-notice-banner.is-leaving {
  opacity: 0;
  transform: translateX(12px);
}

@media (prefers-reduced-motion: reduce) {
  .ts-toasts div.wc-block-components-notice-banner {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* Close control, top-right inside the banner. Drawn as a mask so it inherits
   the muted -> cyan treatment the other icon controls use. */
.ts-toast-close {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: 3px;
  background-color: transparent;
  background-image: none;
  cursor: pointer;
  color: var(--ts-muted);
  transition: color .2s ease, background-color .2s ease;
}

.ts-toast-close::before {
  content: "";
  position: absolute;
  inset: 5px;
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A//www.w3.org/2000/svg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M19%206.41%2017.59%205%2012%2010.59%206.41%205%205%206.41%2010.59%2012%205%2017.59%206.41%2019%2012%2013.41%2017.59%2019%2019%2017.59%2013.41%2012z%27/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A//www.w3.org/2000/svg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M19%206.41%2017.59%205%2012%2010.59%206.41%205%205%206.41%2010.59%2012%205%2017.59%206.41%2019%2012%2013.41%2017.59%2019%2019%2017.59%2013.41%2012z%27/%3E%3C/svg%3E");
}

.ts-toast-close:hover {
  color: var(--ts-cyan);
  background-color: rgba(var(--ts-shade-rgb), .05);
}

.ts-toast-close:focus-visible {
  outline: none;
  color: var(--ts-cyan);
  box-shadow: 0 0 0 3px rgba(var(--ts-glow-rgb), .28);
}

/* Once the notices are lifted out, the block they came from must not keep
   reserving space in the masthead — and with scripting available it must not
   paint there even for the frame before theme.js moves them, which is what
   the .ts-js flag (set inline in <head>, see functions.php) is for. Without
   JavaScript the class is never set and the notices stay where WooCommerce
   rendered them. */
.ts-header .wc-block-store-notices:empty,
.ts-header .wp-block-woocommerce-store-notices:empty,
.ts-js .ts-header .wc-block-store-notices,
.ts-js .ts-header .wp-block-woocommerce-store-notices {
  display: none;
}

/* ── remaining Woo controls ──────────────────────────── */

/* The collapsible panel toggle — "Add a coupon code" on the cart, the order
   summary on checkout. It is a div acting as a button, so it inherits nothing
   from the button recipe and arrived in the browser's default face. */
.wc-block-components-panel__button {
  font-family: var(--ts-mono);
  font-size: 12px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ts-cyan);
  background: none;
  border: 0;
  padding: 10px 0;
  cursor: pointer;
  transition: color .2s ease;
}

.wc-block-components-panel__button:hover {
  color: var(--ts-turquoise);
}

.wc-block-components-panel__button svg {
  fill: currentColor;
}

/* Remove-item control. Restated at a specificity that clears Woo's own
   button reset, which was winning and leaving it in the default face. */
button.wc-block-cart-item__remove-link,
.wc-block-cart-item__remove-link,
a.wc-block-cart-item__remove-link {
  font-family: var(--ts-mono);
  font-size: 11.5px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ts-dim);
  background: none;
  border: 0;
  cursor: pointer;
  transition: color .2s ease;
}

button.wc-block-cart-item__remove-link:hover,
.wc-block-cart-item__remove-link:hover,
a.wc-block-cart-item__remove-link:hover {
  color: #B3261E;
}

/* Checkout's step headings and the order-summary rows, which sit outside
   every selector above. */
.wc-block-components-title,
.wc-block-components-checkout-step__title {
  font-family: var(--ts-serif);
  font-weight: 400;
  color: var(--ts-heading);
}

.wc-block-components-checkout-step__description,
.wc-block-components-address-card,
.wc-block-components-totals-footer-item {
  font-family: var(--ts-sans);
  color: var(--ts-text);
}

.wc-block-components-address-card {
  border: 1px solid var(--ts-line);
  border-radius: 3px;
}

/* Radio and checkbox controls (shipping methods, payment methods, terms). */
.wc-block-components-radio-control__option,
.wc-block-components-checkbox__label {
  font-family: var(--ts-sans);
  color: var(--ts-text);
}

.wc-block-components-radio-control__option-layout {
  font-family: var(--ts-mono);
  font-size: 13px;
}

.wc-block-components-checkbox__input,
.wc-block-components-radio-control__input {
  accent-color: var(--ts-cyan);
}

input[type="checkbox"],
input[type="radio"] {
  accent-color: var(--ts-cyan);
}

/* The validation text Woo shows under a bad field. */
.wc-block-components-validation-error {
  font-family: var(--ts-sans);
  font-size: 12.5px;
  color: #B3261E;
}

/* Checkout's own select component. It carries none of the classes the shop
   selects above are matched on, so the country and state fields were the last
   two controls still rendering as the browser's widget. Same flat field with
   a drawn arrow, since appearance:none removes the native one. */
.wc-blocks-components-select select.wc-blocks-components-select__select,
.wc-block-components-form select.wc-blocks-components-select__select,
select.wc-blocks-components-select__select,
.wc-blocks-components-select select {
  appearance: none;
  -webkit-appearance: none;
  font-family: var(--ts-mono);
  font-size: 13px;
  color: var(--ts-text);
  background-color: var(--ts-surface);
  background-image:
    linear-gradient(45deg, transparent 50%, var(--ts-muted) 50%),
    linear-gradient(135deg, var(--ts-muted) 50%, transparent 50%);
  background-position:
    calc(100% - 15px) calc(50% + 1px),
    calc(100% - 10px) calc(50% + 1px);
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  border: 1px solid var(--ts-faint);
  border-radius: 3px;
  padding: 11px 32px 11px 12px;
  cursor: pointer;
}

.wc-blocks-components-select select.wc-blocks-components-select__select:focus-visible,
select.wc-blocks-components-select__select:focus-visible,
.wc-blocks-components-select select:focus-visible {
  outline: none;
  border-color: var(--ts-cyan-dim);
  box-shadow: 0 0 0 3px rgba(var(--ts-glow-rgb), .20);
}

/* "+ Add apartment, suite, etc." — a span acting as a link. */
.wc-block-components-address-form__address_2-toggle {
  font-family: var(--ts-mono);
  font-size: 12px;
  letter-spacing: .04em;
  color: var(--ts-cyan);
  background: none;
  border: 0;
  cursor: pointer;
  transition: color .2s ease;
}

.wc-block-components-address-form__address_2-toggle:hover {
  color: var(--ts-turquoise);
}

/* Woo renders its checkbox and radio with appearance:none and draws the
   control itself, so accent-color never reaches them — the theme was setting
   a colour that could not apply. Drawn here instead, from the same palette as
   the fields: a hairline box that fills with the accent when checked. */
.wc-block-components-checkbox__input[type="checkbox"],
.wc-block-components-radio-control__input[type="radio"] {
  appearance: none;
  -webkit-appearance: none;
  width: 18px;
  height: 18px;
  margin: 0 10px 0 0;
  border: 1px solid var(--ts-faint);
  background-color: var(--ts-page);
  cursor: pointer;
  transition: border-color .2s ease, background-color .2s ease;
}

.wc-block-components-checkbox__input[type="checkbox"] { border-radius: 3px; }
.wc-block-components-radio-control__input[type="radio"] { border-radius: 50%; }

.wc-block-components-checkbox__input[type="checkbox"]:hover,
.wc-block-components-radio-control__input[type="radio"]:hover {
  border-color: var(--ts-cyan-dim);
}

.wc-block-components-checkbox__input[type="checkbox"]:checked,
.wc-block-components-radio-control__input[type="radio"]:checked {
  background-color: var(--ts-cyan);
  border-color: var(--ts-cyan);
}

/* The tick is drawn by Woo as a separate svg; where it is not, this draws it. */
.wc-block-components-checkbox__input[type="checkbox"]:checked::after {
  content: "";
  display: block;
  width: 100%;
  height: 100%;
  background-color: #fff;
  -webkit-mask: center / 12px no-repeat url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A//www.w3.org/2000/svg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M9%2016.2%204.8%2012l-1.4%201.4L9%2019%2021%207l-1.4-1.4z%27/%3E%3C/svg%3E");
  mask: center / 12px no-repeat url("data:image/svg+xml,%3Csvg%20xmlns%3D%27http%3A//www.w3.org/2000/svg%27%20viewBox%3D%270%200%2024%2024%27%3E%3Cpath%20d%3D%27M9%2016.2%204.8%2012l-1.4%201.4L9%2019%2021%207l-1.4-1.4z%27/%3E%3C/svg%3E");
}

.wc-block-components-radio-control__input[type="radio"]:checked::after {
  content: "";
  display: block;
  width: 6px;
  height: 6px;
  margin: 5px auto;
  border-radius: 50%;
  background: #fff;
}

.wc-block-components-checkbox__input[type="checkbox"]:focus-visible,
.wc-block-components-radio-control__input[type="radio"]:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgba(var(--ts-glow-rgb), .25);
}

/* ── drawer frost: clearing the opaque layers above it ── */

/* The frost was declared on the drawer's content element, but WooCommerce
   paints an opaque fill on the panel *and* on three blocks inside it — the
   contents wrapper, the filled-cart block and the title — each at #F7F8F9.
   A backdrop-filter only shows through what is translucent above it, so the
   effect was there and completely covered. These are cleared to transparent
   so the single tint on the content element is the only fill in the stack. */
.wc-block-mini-cart__drawer,
.wc-block-mini-cart__drawer .wp-block-woocommerce-mini-cart-contents,
.wc-block-mini-cart__drawer .wp-block-woocommerce-filled-mini-cart-contents-block,
.wc-block-mini-cart__drawer .wp-block-woocommerce-empty-mini-cart-contents-block,
.wc-block-mini-cart__drawer .wc-block-mini-cart__title,
.wc-block-mini-cart__drawer .wc-block-mini-cart__items,
.wc-block-mini-cart__drawer .wc-block-mini-cart__products-table,
.wc-block-mini-cart__drawer table.wc-block-cart-items,
.wc-block-mini-cart__drawer .wc-block-cart-items__row {
  background: transparent;
}

/* ── links inside notices ────────────────────────────── */

/* WooCommerce marks the action link in a notice as a button
   (a.button.wc-forward.wp-element-button), so the earlier rule — which
   excluded buttons on purpose, to leave genuine buttons alone — skipped it,
   and it fell through to Woo's own dark grey with a browser underline.
   Inside a notice it is a text link in a sentence, not a control, so it is
   styled as one: the theme's accent and the same underline sweep as prose. */
.ts-toasts a,
.wc-block-components-notice-banner a,
.woocommerce-message a,
.woocommerce-info a,
.woocommerce-error a {
  color: var(--ts-cyan);
  font-family: var(--ts-mono);
  font-size: 12px;
  letter-spacing: .06em;
  text-transform: uppercase;
  text-decoration: none;
  padding: 0 0 2px;
  border: 0;
  background-color: transparent;
  background-image:
    linear-gradient(var(--ts-cyan), var(--ts-cyan)),
    linear-gradient(var(--ts-link-rest), var(--ts-link-rest));
  background-repeat: no-repeat;
  background-position: right bottom, left bottom;
  background-size: 0% 2px, 100% 1px;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
  transition: background-size .28s ease, color .2s ease;
}

.ts-toasts a:hover,
.wc-block-components-notice-banner a:hover,
.woocommerce-message a:hover,
.woocommerce-info a:hover,
.woocommerce-error a:hover {
  color: var(--ts-turquoise);
  background-position: left bottom, left bottom;
  background-size: 100% 2px, 100% 1px;
}

/* Woo's editor stylesheet leaks a five-class rule onto the front end that
   resets this control's face to `inherit`; it is icon-only, so the effect is
   invisible, but it is the last control on the page not carrying the theme. */
.wc-block-mini-cart__drawer table.wc-block-cart-items .wc-block-cart-items__row button.wc-block-cart-item__remove-link,
table.wc-block-cart-items .wc-block-cart-items__row button.wc-block-cart-item__remove-link {
  font-family: var(--ts-mono);
  color: var(--ts-dim);
}

.wc-block-mini-cart__drawer table.wc-block-cart-items .wc-block-cart-items__row button.wc-block-cart-item__remove-link:hover,
table.wc-block-cart-items .wc-block-cart-items__row button.wc-block-cart-item__remove-link:hover {
  color: #B3261E;
}

/* Two vendor overrides that cannot be won on specificity, both marked
   important deliberately and scoped as tightly as the markup allows.

   1. The mini-cart contents block carries an *inline*
      `background-color: rgb(247,248,249)`, written onto the element by
      WooCommerce itself. An inline declaration outranks every author rule, so
      the frost behind it stayed invisible no matter how specific the selector
      — importance is the only lever CSS offers against it.

   2. WooCommerce colours the action link inside a notice with
      `color: #2f2f2f !important`. An author !important at equal specificity is
      the defined way to answer a vendor !important; nothing weaker applies. */
.wc-block-mini-cart__drawer .wp-block-woocommerce-mini-cart-contents {
  background-color: transparent !important;
}

.wc-block-components-notice-banner > .wc-block-components-notice-banner__content .wc-forward,
.wc-block-components-notice-banner .wc-forward,
.ts-toasts .wc-forward {
  color: var(--ts-cyan) !important;
}

.wc-block-components-notice-banner > .wc-block-components-notice-banner__content .wc-forward:hover,
.wc-block-components-notice-banner .wc-forward:hover,
.ts-toasts .wc-forward:hover {
  color: var(--ts-turquoise) !important;
}

/* Woo also clears the link's background with `background: transparent
   !important` on the same selector, which takes the underline sweep with it —
   the sweep is painted as two background gradients. Restated with importance
   for the same reason as the colour above; the resting rule and the hover
   state are declared separately so only the sizes animate. */
.wc-block-components-notice-banner > .wc-block-components-notice-banner__content .wc-forward,
.wc-block-components-notice-banner .wc-forward,
.ts-toasts .wc-forward {
  background-image:
    linear-gradient(var(--ts-cyan), var(--ts-cyan)),
    linear-gradient(var(--ts-link-rest), var(--ts-link-rest)) !important;
  background-repeat: no-repeat !important;
  background-position: right bottom, left bottom !important;
  background-size: 0% 2px, 100% 1px !important;
  transition: background-size .28s ease, color .2s ease;
}

.wc-block-components-notice-banner > .wc-block-components-notice-banner__content .wc-forward:hover,
.wc-block-components-notice-banner .wc-forward:hover,
.ts-toasts .wc-forward:hover {
  background-position: left bottom, left bottom !important;
  background-size: 100% 2px, 100% 1px !important;
}

/* ── account addresses ───────────────────────────────── */

/* Woo lays these two out as floats at width:48% each. The theme gives each a
   border and 20px of side padding, and Woo's width is content-box — so each
   card measured 291px inside a 520px column, the pair no longer fit on one
   line, and the right-hand float dropped underneath the left. They read as
   stacked.

   A two-column grid removes the arithmetic entirely: the columns are what is
   left after the gap, and the cards fill them whatever padding they carry.
   The float and the percentage width are cleared so nothing competes. */
body.woocommerce-account .woocommerce-Addresses.col2-set,
body.woocommerce-account .u-columns.woocommerce-Addresses {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 24px;
}

body.woocommerce-account .woocommerce-Addresses .col-1,
body.woocommerce-account .woocommerce-Addresses .col-2,
body.woocommerce-account .woocommerce-Addresses .woocommerce-Address {
  float: none;
  width: auto;
  box-sizing: border-box;
  margin: 0;
}

/* Woo clears its floats with `.col2-set::before/::after { content: "" }`.
   Those pseudo-elements are children, and a child of a grid container is a
   grid item — so the clearfix silently took the first cell and pushed the two
   real cards into the wrong columns and onto different rows (measured: col-1
   landed right, col-2 landed a row below and left). With the floats gone the
   clearfix has nothing left to clear, so it is removed from the layout, and
   the two cards are placed explicitly rather than left to auto-placement. */
body.woocommerce-account .woocommerce-Addresses.col2-set::before,
body.woocommerce-account .woocommerce-Addresses.col2-set::after,
body.woocommerce-account .u-columns.woocommerce-Addresses::before,
body.woocommerce-account .u-columns.woocommerce-Addresses::after {
  display: none;
}

body.woocommerce-account .woocommerce-Addresses .col-1 {
  grid-column: 1;
  grid-row: 1;
}

body.woocommerce-account .woocommerce-Addresses .col-2 {
  grid-column: 2;
  grid-row: 1;
}

@media (max-width: 700px) {
  body.woocommerce-account .woocommerce-Addresses .col-1,
  body.woocommerce-account .woocommerce-Addresses .col-2 {
    grid-column: 1;
    grid-row: auto;
  }
}

@media (max-width: 700px) {
  body.woocommerce-account .woocommerce-Addresses.col2-set,
  body.woocommerce-account .u-columns.woocommerce-Addresses {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* The heading inside each card, so the two line up rather than each starting
   at whatever margin Woo's default h3 carries. */
body.woocommerce-account .woocommerce-Address h3,
body.woocommerce-account .woocommerce-Address .woocommerce-Address-title h3 {
  margin-top: 0;
}

/* ── every select on the site ────────────────────────── */

/* The select treatment had been added three times for three sets of class
   names — the catalog sorting bar, Woo's classic selects, and the checkout's
   own component — and a fourth kept turning up (the Product Collection sort
   control, the shipping-method chooser, the reviews sorter). Rather than
   chase each new class name as it appears, the base element is styled once
   here, and the rules above remain only where a variant needs different
   metrics. Anything WooCommerce or WordPress renders as a <select> from now
   on inherits the field automatically.

   Scoped away from the admin bar, which is not the theme's to restyle. */
.ts-header select,
.ts-footer select,
main select,
.woocommerce select,
.wc-block-sort-select__select,
.wc-block-product-sort-select select,
.wc-block-components-sort-select__select {
  appearance: none;
  -webkit-appearance: none;
  font-family: var(--ts-mono);
  font-size: 13px;
  letter-spacing: .03em;
  color: var(--ts-text);
  background-color: var(--ts-surface);
  background-image:
    linear-gradient(45deg, transparent 50%, var(--ts-muted) 50%),
    linear-gradient(135deg, var(--ts-muted) 50%, transparent 50%);
  background-position:
    calc(100% - 15px) calc(50% + 1px),
    calc(100% - 10px) calc(50% + 1px);
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  border: 1px solid var(--ts-faint);
  border-radius: 3px;
  padding: 9px 32px 9px 12px;
  cursor: pointer;
  transition: border-color .2s ease, box-shadow .2s ease;
}

.ts-header select:hover,
.ts-footer select:hover,
main select:hover,
.woocommerce select:hover,
.wc-block-sort-select__select:hover,
.wc-block-product-sort-select select:hover,
.wc-block-components-sort-select__select:hover {
  border-color: var(--ts-cyan-dim);
}

.ts-header select:focus-visible,
.ts-footer select:focus-visible,
main select:focus-visible,
.woocommerce select:focus-visible,
.wc-block-sort-select__select:focus-visible,
.wc-block-product-sort-select select:focus-visible,
.wc-block-components-sort-select__select:focus-visible {
  outline: none;
  border-color: var(--ts-cyan-dim);
  box-shadow: 0 0 0 3px rgba(var(--ts-glow-rgb), .20);
}

/* The open list is drawn by the platform; background and colour are the two
   properties that reliably cross that boundary in Chromium and Firefox. */
main select option,
.woocommerce select option,
.wc-block-sort-select__select option {
  background-color: var(--ts-page);
  color: var(--ts-text);
}
