/*
  Desk Mat Co. design tokens.
  Source: design/Desk Mat Homepage Design/Desk Mat Co Design Spec.dc.html — do not hand-tune
  a value here without checking the spec section it maps to (referenced inline).
*/

:root {
  /* §3 Colour */
  --dm-page: #F2EFEA;
  --dm-card: #FFFFFF;
  --dm-inset: #F4F1EC;
  --dm-band: #EFEBE5;
  --dm-ink: #14120F;
  --dm-accent: #FF6A3D;
  --dm-accent-hover: #E85A2E;
  --dm-body-dim: #5C554C;
  --dm-muted: #7A7167;
  --dm-faint: #9A9188;
  --dm-on-dark-body: #A79E93;
  --dm-on-dark-faint: #6F675E;
  --dm-hairline: rgba(20,18,15,0.08);
  --dm-hairline-strong: rgba(20,18,15,0.12);
  --dm-hairline-dark: rgba(255,255,255,0.10);

  /* §4 Typography */
  --dm-font-display: 'Archivo', Helvetica, Arial, sans-serif;
  --dm-font-mono: 'DM Mono', monospace;

  /* §5 Layout, radii, elevation */
  --dm-container-max: 1280px;
  --dm-container-min: 1240px;
  --dm-container-pad: 32px;
  --dm-radius-hero: 28px;
  --dm-radius-editorial: 26px;
  --dm-radius-card: 22px;
  --dm-radius-callout: 20px;
  --dm-radius-well: 16px;
  --dm-radius-tile: 14px;
  --dm-radius-pill: 999px;

  --dm-shadow-card-rest: 0 8px 30px rgba(20,18,15,.05);
  --dm-shadow-card-hover: 0 26px 54px rgba(20,18,15,.12);
  --dm-shadow-callout: 0 18px 44px rgba(20,18,15,.13);
  --dm-shadow-nav: 0 6px 24px rgba(20,18,15,.05);

  --dm-ease-standard: cubic-bezier(.2,.8,.2,1);
}

body.dmds-page {
  margin: 0;
  background: var(--dm-page);
  color: var(--dm-ink);
  font-family: var(--dm-font-display);
  -webkit-font-smoothing: antialiased;
}

.dmds-page a {
  color: var(--dm-ink);
  text-decoration: none;
}
.dmds-page a:hover {
  color: var(--dm-accent);
}

/* §7 Motion keyframes */
@keyframes dmFloat {
  0%, 100% { transform: translateY(-10px); }
  50% { transform: translateY(10px); }
}
@keyframes dmShadow {
  0%, 100% { transform: scaleX(0.96); opacity: 0.20; }
  50% { transform: scaleX(1.04); opacity: 0.28; }
}
@keyframes dmMarquee {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* Reveal-on-scroll initial state (§7); JS toggles to the "revealed" class */
[data-reveal] {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity .7s var(--dm-ease-standard), transform .7s var(--dm-ease-standard);
}
[data-reveal].dmds-revealed {
  opacity: 1;
  transform: none;
}

/* Shop filter chips (real, functional filtering — PRD FR-2) */
[data-dmds-chip] {
  background: var(--dm-inset);
  color: #4A443C;
  transition: background .2s, color .2s;
}
[data-dmds-chip].dmds-chip-active {
  background: var(--dm-ink);
  color: #FFFFFF;
}

/* AD-9: hover-dependent interactions only fire on fine-pointer/hover-capable devices */
@media (hover: hover) and (pointer: fine) {
  .dmds-hover-lift:hover {
    transform: translateY(-6px);
    box-shadow: var(--dm-shadow-card-hover);
  }
  .dmds-hover-lift-sm:hover {
    transform: translateY(-4px);
  }
}

/* prefers-reduced-motion branch (design spec §12 "Known gaps", closed here rather than left open) */
@media (prefers-reduced-motion: reduce) {
  [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
  [data-mat3d-hero],
  [data-mat3d-plane],
  .dmds-marquee-track {
    animation: none !important;
  }
}

/* =========================================================================
   AD-9 Responsive layer.
   The design spec (§1 Canvas) is desktop-only, 1240px minimum, no breakpoints.
   Owner requires a full responsive build regardless — this section derives
   mobile/tablet layouts from the SAME tokens above (color, radii, spacing
   scale, motion timing) per AD-9's rule: only layout and hover-dependent
   interactions fork by viewport, tokens never do.

   Breakpoints: >1240px desktop (spec's own canvas, untouched below),
   768–1240px tablet, <768px mobile.
   ========================================================================= */

/* Root container no longer forces a hard 1240px minimum below desktop */
@media (max-width: 1239px) {
  .dmds-page {
    min-width: 0 !important;
  }
}

/* ---- Hero (homepage section 2) ---- */
@media (max-width: 1239px) {
  .dmds-hero-h1 {
    font-size: 48px !important;
    line-height: 1.05 !important;
    max-width: 100% !important;
  }
  /* The CTA lives INSIDE the <h1> (see build-homepage.php) so that on desktop it tucks
     neatly beside the headline's last line — a deliberate design detail worth keeping
     there. Below desktop it must drop onto its own centred line instead.

     `display: inline-flex` did NOT do that. An inline-level box stays on the current
     line box, so `margin: 18px 0 0` only pushed it down WITHIN that line, leaving it
     parked beside the final words. At 390px the headline happens to wrap to three
     lines and the button lands alone by luck, which is why the fault was invisible on
     a phone; at tablet widths (the ~600-900px band) the headline wraps to two lines
     and the button sat next to "Every Single Day", shoving the whole headline
     off-centre and cramping both.

     `display: flex` makes it a BLOCK-level flex container: it forces a line break,
     and `margin-inline: auto` with a content-width box centres it under the text. */
  .dmds-hero-h1 .dmds-btn-primary {
    display: flex !important;
    width: fit-content;
    margin: 22px auto 0 !important;
    top: 0 !important;
    /* The h1 sets a huge display line-height (0.98) and heavy weight; the button must
       not inherit either or it renders as a squashed, over-bold pill. */
    line-height: 1.2 !important;
    font-weight: 600 !important;
    /* Declared here because this button is exempted from the site-wide coarse-pointer
       CTA rule (see the `:not(.dmds-hero-h1 *)` note there) — that is where every other
       CTA gets its 44px floor, so this one has to carry its own. */
    min-height: 44px;
    align-items: center;
    justify-content: center;
  }
  .dmds-hero-mat {
    width: 320px !important;
  }
  .dmds-hero-glow {
    width: 640px !important;
    height: 300px !important;
  }
  /* Floating callout cards leave absolute positioning and stack under the hero image;
     the stage switches to auto height and static flow to accommodate the stack.

     `overflow: hidden` is load-bearing, not cosmetic. The glow above is a 640px
     decorative blob translated -320px, and the hero mat plane is a transformed
     absolutely-positioned element ~513px wide; on a 390px viewport both extended past
     the right edge and dragged the body's scrollWidth to 711px. They are decoration, so
     the correct treatment is to clip the stage rather than shrink the artwork. */
  .dmds-hero-stage {
    height: auto !important;
    min-height: 0 !important;
    /* The callout cards' own box-shadow (`0 18px 44px rgba(20,18,15,.13)`) needs ~44px
       of room below the card to render its blur without being cut off. The
       overflow:hidden this stage needs (to clip the oversized glow/mat, see above)
       clips that shadow too if the stage doesn't have enough bottom padding to spare —
       20px left only 20px of clearance, so the soft shadow's blur got hard-clipped
       into a flat-edged grey band instead of fading out. 48px covers the full blur. */
    padding-bottom: 48px;
    overflow: hidden !important;
  }
  /* The blurred shadow ellipse under the mat is pinned `bottom:112px` against what was
     a 560px stage; with the stage now auto-height it detaches and floats over the
     stacked callout cards. It is pure decoration, so drop it rather than re-pin it. */
  .dmds-hero-stage > div[style*="filter:blur"] {
    display: none !important;
  }
  .dmds-hero-callout-left,
  .dmds-hero-callout-right {
    position: static !important;
    width: 100% !important;
    max-width: 420px !important;
    margin: 14px auto 0 !important;
  }
  .dmds-hero-arrows {
    display: none !important;
  }

  /* The mat image sits in an unclassed `position:absolute; left:50%; top:50%` wrapper
     (see build-homepage.php). Once the callouts above drop OUT of absolute positioning
     and stack in normal flow, that wrapper stays pinned to the stage centre and the mat
     renders ON TOP of them — on a 390px viewport it covered "Match Your Workspace
     Aesthetic", the "Start Your Design" CTA and the product title underneath.
     Returning the wrapper to flow puts the mat above the stacked cards, which is the
     order the design shows. Selected via :has() because the wrapper carries no class
     and must not be confused with the glow or the blurred shadow ellipse. */
  .dmds-hero-stage > div:has(> div > .dmds-hero-mat) {
    position: static !important;
    transform: none !important;
    display: flex !important;
    justify-content: center !important;
    margin-bottom: 4px;
  }
}

@media (max-width: 640px) {
  .dmds-hero-h1 {
    font-size: 34px !important;
  }
  .dmds-hero-mat {
    width: 220px !important;
  }
}

/* ---- Section header pattern (eyebrow / H2 / See More — grids used across most sections) ---- */
@media (max-width: 1239px) {
  .dmds-section-header {
    grid-template-columns: 1fr !important;
    justify-items: start !important;
    gap: 16px !important;
    text-align: left !important;
  }
  .dmds-section-header h2 {
    font-size: 32px !important;
    text-align: left !important;
    max-width: 100% !important;
  }
  .dmds-section-header .dmds-btn-primary {
    justify-self: start !important;
  }

  /* STANDALONE section H2s — the two homepage headings that are NOT wrapped in a
     .dmds-section-header / -flex grid and so were missed by the rules above:

       "Design Your Perfect Personalized Mat"  (build-homepage.php:301, 52px)
       "Spin It. Read the Surface."            (build-homepage.php:279, 44px)

     Both carry their size inline with no class, so nothing scaled them: they rendered
     at a fixed 52px/44px from 390px all the way to 1440px. At desktop the SCALED
     headings are 46px, so "Design Your Perfect…" was actually LARGER on a phone than
     the site's own biggest heading is on a monitor — it burned four lines at 390px and
     broke into a lopsided two-line block at tablet widths, which is the "split and
     broken" header in the report.

     Matched on the inline declaration rather than by adding classes, because the
     markup is generated by build-homepage.php and porting rules keep fixes in CSS.
     Sizes mirror the .dmds-section-header scale (32px) with a little more room for
     the two hero-adjacent headings, which are deliberately more prominent. */
  .dmds-page h2[style*="font-size:52px"] {
    font-size: 34px !important;
    line-height: 1.08 !important;
    max-width: 100% !important;
  }

  .dmds-page h2[style*="font-size:44px"] {
    font-size: 32px !important;
    line-height: 1.1 !important;
    max-width: 100% !important;
  }

  /* The same fault on other pages, found by sweeping every h1/h2/h3 at 390px for a
     computed size still above 36px:
       Contact  "Talk to the Workshop"            64px  (h1, inline)
       Shop     "Every Mat We Make, in One Place" 40px
       Shop     "Print Your Own Design Instead"   44px  (covered above)
       Journal  "Put It Under Your Keyboard"      44px  (covered above)
     A 64px heading on a 390px screen is roughly six characters per line. */
  .dmds-page h1[style*="font-size:64px"],
  .dmds-page h2[style*="font-size:64px"] {
    font-size: 34px !important;
    line-height: 1.08 !important;
    max-width: 100% !important;
  }

  .dmds-page h1[style*="font-size:40px"],
  .dmds-page h2[style*="font-size:40px"] {
    font-size: 30px !important;
    line-height: 1.12 !important;
    max-width: 100% !important;
  }
  /* Flex variant (Collections, Material & Build headers) stacks the same way */
  .dmds-section-header-flex {
    flex-direction: column !important;
    align-items: flex-start !important;
    gap: 16px !important;
  }
  .dmds-section-header-flex h2 {
    font-size: 32px !important;
  }
  section {
    padding-left: 20px !important;
    padding-right: 20px !important;
  }
}

/* ---- Product grid (3-up -> 1-up) ---- */
@media (max-width: 1239px) {
  .dmds-grid-3up {
    grid-template-columns: repeat(2, 1fr) !important;
  }
}
@media (max-width: 640px) {
  .dmds-grid-3up {
    grid-template-columns: 1fr !important;
  }
}

/* The viewer's "Drag to rotate" / "900 × 400 × 4 mm" caption is the ONLY affordance
   telling a user the 3D mat is interactive, and it hardcodes `color:#6F675E` on a
   `#14120F` background — literally the `--dm-on-dark-faint` token, which is too low
   for small (11px) text: measured contrast 3.36:1, below WCAG AA's 4.5:1 minimum for
   normal text. Not a mobile-only concern, so applied at every width, not inside the
   responsive media queries below. `--dm-on-dark-body` is the token already defined
   for exactly this case (body text on dark) and measures 7.08:1. */
.dmds-hero-stage + div[style*="justify-content:space-between"][style*="color:#6F675E"],
.dmds-viewer-stage + div[style*="justify-content:space-between"][style*="color:#6F675E"] {
  color: var(--dm-on-dark-body) !important;
}

/* ---- 3D viewer dark panel ---- */
@media (max-width: 1239px) {
  .dmds-viewer-stage {
    height: 300px !important;
  }
  .dmds-viewer-plane {
    width: 92vw !important;
    max-width: 460px !important;
    height: 220px !important;
  }
  /* The viewer's mono meta row ("Drag to rotate" / "900 × 400 × 4 mm") is an unclassed
     space-between flex row. At 390px both labels wrapped mid-value — the dimension read
     "900 × 400 × 4" then "MM" on its own line, which misreads as a separate figure.
     Stacking the row keeps each label whole; a spec value must never be broken. */
  .dmds-viewer-stage + div[style*="justify-content:space-between"] {
    flex-direction: column !important;
    align-items: flex-start !important;
    gap: 6px !important;
  }
  .dmds-viewer-stage + div[style*="justify-content:space-between"] > span {
    white-space: nowrap !important;
  }
}

/* ---- Personalization annotation section ---- */
@media (max-width: 1239px) {
  .dmds-anno-photo {
    height: 360px !important;
  }
  .dmds-anno-callouts {
    position: static !important;
    display: flex !important;
    flex-direction: column !important;
    gap: 12px !important;
    margin-top: 16px !important;
  }
  .dmds-anno-callout {
    position: static !important;
    width: 100% !important;
  }
  .dmds-signature-card {
    grid-template-columns: 1fr !important;
  }
}

/* ---- Collections asymmetric rows -> stack ---- */
@media (max-width: 1239px) {
  .dmds-collections-row {
    flex-direction: column !important;
  }
  .dmds-collections-row > a {
    flex: 1 1 auto !important;
  }
}

/* ---- Material & Build (image column + accordion) ---- */
@media (max-width: 1239px) {
  .dmds-material-grid {
    grid-template-columns: 1fr !important;
  }
  .dmds-material-image {
    height: 260px !important;
  }
}

/* ---- Testimonials (3-column -> stack) ---- */
@media (max-width: 1239px) {
  .dmds-testimonial-grid {
    grid-template-columns: 1fr !important;
  }
}

/* ---- Closing statement ---- */
@media (max-width: 1239px) {
  .dmds-closing-h2 {
    font-size: 34px !important;
  }
  .dmds-closing-h2 .dmds-btn-primary {
    display: inline-flex !important;
    margin: 16px 0 0 !important;
    top: 0 !important;
  }
}

/* ---- Shop page: header row, filter bar, grid ---- */
@media (max-width: 1239px) {
  .dmds-shop-header-row {
    flex-direction: column !important;
    align-items: flex-start !important;
    gap: 16px !important;
  }
  .dmds-shop-header-row h1 {
    font-size: 40px !important;
  }
  .dmds-filter-bar {
    flex-direction: column !important;
    align-items: stretch !important;
    border-radius: 22px !important;
    gap: 14px !important;
    padding: 14px !important;
  }
  .dmds-filter-chips {
    flex-wrap: wrap !important;
  }
  .dmds-filter-meta {
    justify-content: space-between !important;
    padding-right: 0 !important;
  }
  .dmds-shop-promo {
    grid-template-columns: 1fr !important;
    /* Desktop's 52px inner padding (~27% of the panel width per side on a phone) was
       never reduced when the grid collapsed to one column, squeezing every child
       (eyebrow, h2, paragraph, button, image) into a 206px column inside a 310px
       panel. That forced the headline onto 4 lines with "Own" orphaned alone, and the
       paragraph broke as "Upload artwork, a photo or a / logo." */
    padding: 28px !important;
  }
}

/* ---- Journal (roadmap A1) ----
   The Journal is hand-built from the component language, NOT one of the 10 ported
   pages, so its body carries `dmds-page` but not `dmds-ported` — none of the blunt
   ported-page rules reach it, exactly like the Contact page below.

   Its grid shipped as a hard `repeat(3, 1fr)` and laid the document out at 543px on
   a 390px viewport (AD-9 violation). `minmax(0, 1fr)` rather than plain `1fr` is
   deliberate and is the actual fix: a grid track's default minimum is `auto`, which
   refuses to shrink below its content's intrinsic width, so a long unbroken article
   title would still push the column wider than the screen even at one column.

   Two breakpoints, matching the shop grid's own steps: 2 columns on tablet, 1 on
   phone. */
@media (max-width: 1239px) {
  .dmds-journal-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
  }
  .dmds-journal-header-row {
    flex-direction: column !important;
    align-items: flex-start !important;
    gap: 16px !important;
  }
  .dmds-journal-header-row h1 {
    font-size: 40px !important;
  }
  /* The rail already scrolls horizontally on purpose (overflow-x:auto), which the
     suite's overflow probe explicitly exempts — but it must not force the PAGE wide,
     so it needs a max-width it can actually scroll inside of. */
  .dmds-journal-chips {
    max-width: 100% !important;
  }
  .dmds-journal-promo {
    grid-template-columns: minmax(0, 1fr) !important;
    /* Same reasoning as .dmds-shop-promo: the 52px desktop inset is ~27% of the
       panel per side on a phone and squeezes every child into a narrow column. */
    padding: 28px !important;
  }
  .dmds-journal-promo-media {
    height: 200px !important;
  }
}

@media (max-width: 767px) {
  .dmds-journal-grid {
    grid-template-columns: minmax(0, 1fr) !important;
  }
  .dmds-journal-header-row h1 {
    font-size: 34px !important;
  }
}

/* ---- Hero carousel arrows ----
   They shipped as <div>s with no handler and no states. Now that they are real <button>s
   driving the colourway carousel they need to behave like controls: lift on hover, take
   a visible focus ring for keyboard users, and depress on click. Behind a fine-pointer
   query for the same reason .dmds-hover-lift is — a lift that triggers on tap is a state
   that gets stuck on touch. */
@media (hover: hover) and (pointer: fine) {
  .dmds-hero-arrows button:hover {
    transform: translateY(-2px);
  }
}

.dmds-hero-arrows button:active {
  transform: translateY(0) scale(0.94);
}

/* Not `outline: none`. These are the only controls in the hero, and a keyboard user
   tabbing to them needs to see where they are — the ring is offset so it reads around
   the circle rather than clipping it. */
.dmds-hero-arrows button:focus-visible {
  outline: 2px solid var(--dm-accent);
  outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
  .dmds-hero-arrows button,
  .dmds-hero-arrows button:hover,
  .dmds-hero-arrows button:active {
    transform: none;
  }
}

/* ---- Journal article prose ----
   The article template injects post_content through `the_content` and gave it NO
   typographic rules at all, so every tag fell through to GeneratePress's defaults:
   16px/1.5 body at full 900px measure, and an <h2> rendered at the theme's scale
   rather than the display scale §4 sets. That was survivable while the articles were
   100-word stubs; at full length it is the difference between a page you read and a
   page you skim off.

   Scoped to `.dmds-journal-body` — the wrapper the article shortcode emits — so none
   of this reaches product descriptions or the ported pages.

   MEASURE, not container width. The container stays 900px because the hero image
   wants it; the TEXT is capped at 68ch (~660px), because a 900px line at 17px runs to
   roughly 110 characters and the eye loses the line return. The h2s are deliberately
   left at full width so they read as structure against the narrower column. */
.dmds-journal-body > p,
.dmds-journal-body > ul,
.dmds-journal-body > ol,
.dmds-journal-body > blockquote {
  max-width: 68ch;
}

.dmds-journal-body p {
  margin: 0 0 22px;
  font-size: 17px;
  line-height: 1.75;
  color: var(--dm-body-dim);
  letter-spacing: -0.003em;
}

/* The lede. The first paragraph of every article is written as a standalone summary,
   so it is set larger and in full ink to carry the weight of a deck. */
.dmds-journal-body > p:first-child {
  font-size: 21px;
  line-height: 1.6;
  color: var(--dm-ink);
  letter-spacing: -0.017em;
  margin-bottom: 30px;
}

.dmds-journal-body h2 {
  margin: 48px 0 16px;
  font-size: 30px;
  line-height: 1.15;
  letter-spacing: -0.028em;
  font-weight: 700;
  color: var(--dm-ink);
}

.dmds-journal-body h3 {
  margin: 32px 0 10px;
  font-size: 19px;
  line-height: 1.3;
  letter-spacing: -0.015em;
  font-weight: 700;
  color: var(--dm-ink);
}

.dmds-journal-body ul,
.dmds-journal-body ol {
  margin: 0 0 22px;
  padding-left: 20px;
}

.dmds-journal-body li {
  margin-bottom: 9px;
  font-size: 17px;
  line-height: 1.7;
  color: var(--dm-body-dim);
}

/* The one place the accent appears in running text. A bullet is a structural mark,
   not emphasis, so colouring the marker keeps the list scannable without putting
   orange inside a sentence. */
.dmds-journal-body li::marker {
  color: var(--dm-accent);
}

.dmds-journal-body strong {
  font-weight: 600;
  color: var(--dm-ink);
}

/* Pull-quote: the accent rule on the left is the same 2px hairline idiom the spec
   uses for the active nav item, turned vertical. No quotation marks — the indent and
   the rule already say what it is. */
.dmds-journal-body blockquote {
  margin: 34px 0;
  padding: 2px 0 2px 26px;
  border-left: 2px solid var(--dm-accent);
}

.dmds-journal-body blockquote p {
  font-size: 21px;
  line-height: 1.5;
  letter-spacing: -0.02em;
  color: var(--dm-ink);
  margin-bottom: 0;
}

/* In-body links are the only underlined text in an article, which is what tells a
   reader it is a link when the colour alone might read as emphasis. */
.dmds-journal-body a {
  color: var(--dm-accent);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}

.dmds-journal-body a:hover {
  color: var(--dm-accent-hover);
}

/* Section break between the body and the closing note. */
.dmds-journal-body hr {
  margin: 44px 0;
  border: 0;
  border-top: 1px solid var(--dm-hairline);
  max-width: 68ch;
}

@media (max-width: 767px) {
  .dmds-journal-body > p:first-child {
    font-size: 19px;
  }
  .dmds-journal-body h2 {
    font-size: 25px;
    margin-top: 38px;
  }
  .dmds-journal-body blockquote p {
    font-size: 19px;
  }
}

/* The article measure is a fixed 900px container with 32px padding, which overflows
   any viewport narrower than that. Cap it instead of letting the fixed value win. */
@media (max-width: 1239px) {
  .dmds-journal-body,
  .dmds-journal-body img {
    max-width: 100% !important;
  }
}

/* ---- Contact page (T6) ----
   The contact page is hand-built, NOT one of the 10 ported pages, so its body carries
   `dmds-page` but not `dmds-ported` — none of the blunt ported-page rules above reach
   it. `.dmds-contact-grid` had no responsive rule of any kind: it stayed a hard
   `1fr 1fr 1fr` at every width, and its third column overflowed a 390px viewport.
   The form itself is an Elementor Pro widget and stacks on its own. */
@media (max-width: 1239px) {
  .dmds-contact-grid {
    grid-template-columns: minmax(0, 1fr) !important;
  }
  .dmds-contact-grid > * {
    min-width: 0 !important;
  }
}

/* ---- Nav (shared) ---- */
@media (max-width: 1239px) {
  .dmds-nav-links,
  .dmds-nav-search {
    display: none !important;
  }
}

/* The header pill itself must not wrap. Its outer wrapper keeps the design's desktop
   `padding: 20px 32px 0` and the pill adds `padding: 10px 12px 10px 24px` on top, which
   at 390px left only 286px of usable width. Logo (114) + flex spacer (50) + cart (38) +
   hamburger (40) + three 24px gaps = 338 > 286, so the row wrapped and the hamburger
   dropped onto a second line UNDER the logo, leaving a broken two-storey header on
   every page. Tighten the gutters and the gap at mobile; nothing here changes tokens,
   only layout (AD-9). */
@media (max-width: 767px) {
  .dmds-page header nav {
    gap: 10px !important;
    padding: 10px 10px 10px 16px !important;
    flex-wrap: nowrap !important;
  }
  /* The wrapper's 32px side padding is a desktop canvas gutter. */
  .dmds-page header nav {
    min-width: 0;
  }
  .dmds-page header > div > div,
  .dmds-page header [style*="padding:20px 32px"] {
    padding-left: 14px !important;
    padding-right: 14px !important;
  }
  /* The wordmark must SURVIVE, not merely avoid forcing overflow.
     `min-width: 0; overflow: hidden` let it shrink without limit, and on every page
     carrying the "Custom Design →" CTA (156px) the flex solver took the whole
     shortfall out of the logo: it collapsed to 12px — a bare vertical sliver where
     the brand should be, on shop, cart, checkout, my-account, journal and contact.
     Those are precisely the purchase-path pages, so the site had no visible brand and
     no working link home exactly where trust matters most.
     `flex: 0 0 auto` makes the wordmark an unshrinkable fixed item. */
  .dmds-page header nav > a:first-child {
    flex: 0 0 auto !important;
    min-width: max-content !important;
    overflow: visible !important;
  }

  /* Something still has to absorb the shortfall. The "Custom Design →" CTA is the
     right candidate: it is a re-statable action — the same link sits in the menu
     behind the hamburger — whereas the logo is the page's identity.

     It is HIDDEN, not shrunk. Letting it shrink with an ellipsis was tried first and
     is visibly worse than removing it: at 390px the pill clipped to "om Design",
     a button whose own label is unreadable, which reads as a rendering fault rather
     than a deliberate truncation. Four items (logo, cart, CTA, hamburger) simply do
     not fit a 390px pill at a legible size; three do, comfortably.

     Matching on the class prefix covers both CTA variants the header builder emits
     (`dmds-nav-cta-desktop` / `-mobile`). */
  .dmds-page header nav > a[class*="dmds-nav-cta"],
  .dmds-page header nav > a.dmds-btn-primary {
    display: none !important;
  }

  .dmds-menu-toggle,
  .dmds-page header nav > a[href*="cart"] {
    flex: none !important;
  }
}

/* ---- Touch target sizes (FR-26 / NFR4) ----
   Measured, not assumed: at 390px the hamburger was 40x38 and the cart link 38x38 —
   both under the 44x44 minimum, and the hamburger is the ONLY way to reach navigation
   on a phone. DIAGNOSTIC previously recorded "44px touch targets" as verified under
   FR-26; that was true of the variation pills and accordion rows but not of these.
   Scoped to coarse pointers so a desktop mouse keeps the design's exact 38px circles. */
@media (pointer: coarse) {
  .dmds-menu-toggle,
  .dmds-page header nav > a[href*="cart"] {
    width: 44px !important;
    height: 44px !important;
  }

  /* Footer link columns were 19px tall and stacked ~11px apart — four social links in a
     row ("Instagram YouTube X TikTok") is a dense mis-tap zone. Padding rather than
     height so the visual rhythm of the column is preserved. */
  .dmds-page footer a {
    display: inline-block;
    padding-block: 13px;
    min-height: 22px;
  }

  /* Breadcrumb rows are unclassed inline-styled divs in the ported markup, so they are
     matched by their mono/uppercase signature rather than a class. Measured 32x14. */
  .dmds-page [style*="text-transform:uppercase"] > a,
  .dmds-page .woocommerce-breadcrumb a,
  .dmds-page nav[aria-label*="readcrumb"] a {
    display: inline-block;
    padding-block: 14px;
  }

  /* FR-9's rights-confirmation checkbox is a legally meaningful control and rendered at
     16x16. `height` alone lost to the UA stylesheet's own sizing, so this sets both
     dimensions with !important and disables the native appearance box model. */
  .dmds-page input[type="checkbox"],
  .dmds-page input[type="radio"] {
    width: 22px !important;
    height: 22px !important;
    min-width: 22px !important;
    min-height: 22px !important;
    flex: none !important;
    margin-block: 2px;
  }

  /* WooCommerce's pagination (27x40) and the variation "Clear" link (34x15). */
  .dmds-page .page-numbers,
  .dmds-page a.reset_variations {
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
    min-height: 44px !important;
    min-width: 44px;
  }

  /* The gallery zoom trigger (36x36) opens a hover-zoom that is already suppressed on
     touch (see the zoomImg rule), so on a coarse pointer it is a control that does
     nothing. Remove it rather than enlarge a no-op. */
  .dmds-page .woocommerce-product-gallery__trigger {
    display: none !important;
  }

  /* The wordmark is the header's "home" link; it measured 114x24 inside a 44px row. */
  .dmds-page header nav > a:first-child {
    padding-block: 10px;
  }

  /* Primary CTAs and WooCommerce's "Select options" landed at 40-43px — a hair under.
     inline-flex + centring so the extra height does not shift the label off-centre.

     `:not(.dmds-hero-h1 *)` — the homepage hero's CTA lives INSIDE the <h1> and has to
     be BLOCK-level below desktop so it drops onto its own centred line instead of
     sitting beside the headline's last words. This rule matched it with equal
     specificity but later source order, so on any touch device it silently forced the
     button back to inline-flex and re-broke the hero. The hero rule sets its own
     min-height, so nothing is lost by exempting it here.

     `:not(header *)` — the header's "Custom Design" CTA is DELIBERATELY hidden below
     768px (four items do not fit the header pill legibly; the same link sits in the
     hamburger menu). This rule's `display: inline-flex !important` un-hid it, and the
     restored 156px pill shoved the hamburger clean off the right edge — leaving a
     phone with no way to open navigation at all. A hidden element needs no tap size. */
  .dmds-page .dmds-btn-primary:not(.dmds-hero-h1 *):not(header *),
  .dmds-page .dmds-callout-btn,
  .dmds-page ul.products li.product a.button,
  .dmds-page a.button,
  .dmds-page .wp-block-button__link,
  .dmds-page .wc-block-components-button,
  .dmds-page .product_meta a,
  .dmds-page button.single_add_to_cart_button {
    min-height: 44px !important;
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
  }

  /* Shop filter chips were 40px tall. */
  .dmds-page [data-dmds-chip],
  .dmds-page .dmds-pill {
    min-height: 44px !important;
  }

  /* Cart line items (WooCommerce Blocks). The remove control was 24x24 — the smallest
     control on the site and a DESTRUCTIVE one, sitting a few px from the quantity
     stepper. The product name and thumbnail links were 18-19px tall. */
  .dmds-page .wc-block-cart-item__remove-link {
    min-width: 44px !important;
    min-height: 44px !important;
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
  }
  .dmds-page .wc-block-components-product-name,
  .dmds-page .wc-block-cart-item__image a {
    display: inline-block;
    padding-block: 13px;
  }

  /* NOT enlarged, deliberately: the checkout's "Terms and Conditions" / "Privacy Policy"
     links and the "Log in" prompt are inline links inside running sentences. Giving them
     a 44px box would break the line they sit in. WCAG 2.5.5 exempts targets in a line of
     text for exactly this reason. */
}

/* ---- Footer (shared) ---- */
@media (max-width: 1239px) {
  .dmds-footer-grid {
    grid-template-columns: 1fr 1fr !important;
    gap: 28px !important;
  }
  .dmds-footer-outline {
    font-size: clamp(32px, 12vw, 64px) !important;
  }
}
@media (max-width: 640px) {
  .dmds-footer-grid {
    grid-template-columns: 1fr !important;
  }
  .dmds-footer-bottom {
    flex-direction: column !important;
    align-items: flex-start !important;
    gap: 8px !important;
  }
}

/* ============================================================================
   Ported design pages — responsive layer (Architecture AD-9)
   ============================================================================
   Collections, Custom Design, About, the support pages and the legal pages are
   ported verbatim from the design deliverables (see build-scripts/lib-design-port.php).
   Those files are explicitly desktop-only — their own root declares min-width:1240px
   and the spec's §1 states "no mobile breakpoints in v1.0" — so every fixed-px grid
   and absolutely-positioned decoration carries straight across and overflows a phone.
   Owner requires a full responsive build in v1 regardless, so the layout (and only the
   layout) forks by viewport here. Tokens — colour, type roles, radii, elevation, motion
   — are untouched at every breakpoint, exactly as AD-9 requires.

   Scoped to .dmds-ported so the hand-built homepage and shop archive, which already
   carry their own tuned breakpoints above, are never affected by these blunt rules. */

@media (max-width: 1239px) {
  /* The design's own root wrapper pins a desktop minimum; release it. */
  .dmds-ported [style*="min-width:1240px"] {
    min-width: 0 !important;
  }

  /* Two-plus column grids collapse to a single column. Individual pages differ in
     their exact track definitions, so match on the declaration rather than a class.

     `minmax(0, 1fr)`, NOT `1fr`. A bare `1fr` is shorthand for `minmax(auto, 1fr)`, and
     a grid item's automatic minimum size is its *content's* intrinsic minimum — so a
     track refuses to shrink below whatever its widest indivisible child needs. On
     /custom-design/ that produced the exact pathology this rule was written to prevent:
     the collapsed grid computed a single 395px track inside a 310px container, pushing
     the body to 435px. `minmax(0, …)` lets the track actually reach the container width.
     The same fix applies to flex children below for the identical reason. */
  .dmds-ported [style*="grid-template-columns"] {
    grid-template-columns: minmax(0, 1fr) !important;
  }

  /* EXCEPTION: the artwork placement picker (built by initCustomDesign, not ported
     from the design deliverable). The blunt rule above exists to collapse the
     DESIGN'S fixed desktop grids, which have no business surviving on a phone. This
     grid is the opposite case — it is already responsive by construction
     (`auto-fit` + `minmax`), and forcing it to one column turns ten short labels
     into a ten-row tower that buries the size/edge/thickness cards below it.

     Its ten items are short words in small tiles, so two-to-three per row is both
     legible and thumb-sized. Declared with !important to beat the rule above, which
     is itself !important and matches the same element via its inline style. */
  .dmds-ported [data-dmds-placement] [role="radiogroup"] {
    grid-template-columns: repeat(auto-fit, minmax(88px, 1fr)) !important;
  }

  /* EXCEPTION 2: the "Start from a print" swatches, for the same reason. Collapsed to
     one column, four image tiles became a ~1200px tower that pushed "Add your own
     artwork" — the step this page exists for — entirely below the fold. Two per row
     keeps every swatch big enough to judge the print by while the card stays a
     glanceable block, consistent with the size tiles and placement picker. */
  .dmds-ported [style*="grid-template-columns:repeat(4,1fr)"]:has([data-print]) {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
  }

  /* Grid and flex children may shrink below their intrinsic content width; without this
     a long unbroken string (a URL, a SKU, a mono spec value) sets a floor no track can
     go under and the whole page widens. */
  .dmds-ported [style*="grid-template-columns"] > *,
  .dmds-ported [style*="display:flex"] > * {
    min-width: 0 !important;
  }

  /* …with one exception: pill CHIPS. The rule above lets any flex child shrink, which on
     Collections squeezed the spec chips until their labels broke mid-word — "Stitched"
     rendered as "Stitc / hed" and "900 × 400" split across two lines. A chip is an
     atomic label: it must keep its content on one line and wrap to the next row instead.
     Matched on the design's pill signature (`border-radius:999px` + padding) rather than
     a class, since the ported markup carries only inline styles.

     EXCLUDES `input` — the custom-design name/handle field is ALSO pill-shaped
     (`border-radius:999px`) but is a real growing text field, not a label. Without this
     exclusion it was caught by the same heuristic and forced to `flex:0 0 auto`, which
     silently defeated its own inline `flex:1` and kept it squeezed to a fixed width
     that was too narrow for its placeholder — the opposite of what this rule intends. */
  .dmds-ported :not(input)[style*="border-radius:999px"] {
    min-width: auto !important;
    flex: 0 0 auto !important;
    white-space: nowrap !important;
  }
  /* `:not(header nav)` — the header is a pill-shaped bar CONTAINING pill-shaped
     children (cart bubble, "Custom Design" CTA, hamburger), so it matches this
     chip-row selector by accident. Allowing it to wrap broke the header into two
     storeys on the ported pages: the CTA and hamburger dropped onto a second row
     under the logo. The header has its own nowrap rule above and must keep it. */
  .dmds-ported [style*="display:flex"]:has(> :not(input)[style*="border-radius:999px"]):not(header nav):not(nav) {
    flex-wrap: wrap !important;
  }

  /* Related-links chip rows on the support/legal pages (Returns "Shipping / Care
     instructions / Help centre", Terms "Privacy policy / Licensing", and the same
     pattern on Shipping/Care/Privacy Policy/Licensing) are `justify-content:center`,
     which is invisible on desktop where all chips fit one line, but on 390px the last
     chip(s) wrap onto their own row and are individually centred while every other
     element on the page — breadcrumb, H1, card text, footer — starts flush at the same
     left edge. Left-align to match. */
  .dmds-ported [style*="display:flex; gap:12px; justify-content:center"] {
    justify-content: flex-start !important;
    flex-wrap: wrap;
  }

  /* Two-digit DM Mono index markers ("01", "02"...) as the FIRST child of an unlabelled
     flex row (About's "Three rules we have never broken", Licensing's numbered
     confirmation list). No `flex-shrink:0`/`white-space:nowrap` was set, so the flex
     algorithm shrank the marker to a single-glyph min-content width under pressure from
     its long sibling paragraph, wrapping "01" into "0" / "1" on two lines — confirmed
     live: marker width measured ~4-5px (room for half a monospace glyph) with the digit
     literally split across two lines. Matched on the mono/accent-orange signature these
     markers share rather than a class, since both are unclassed inline-styled spans. */
  .dmds-ported [style*="display:flex"] > span:first-child[style*="DM Mono"] {
    flex: none !important;
    white-space: nowrap !important;
  }

  /* Accordion chevrons: the ▾ marker is the last flex child of a space-between header
     row. When the question wraps to two lines the marker was pushed onto a third line
     of its own, detached from the question it belongs to. It is a fixed-size affordance,
     so it must never shrink or wrap. */
  .dmds-ported [style*="justify-content:space-between"] > span:last-child {
    flex: none !important;
    min-width: auto !important;
    align-self: flex-start;
  }

  /* Configurator "Pick a size" tiles (`display:flex; gap:10px; flex-wrap:wrap`, 5
     `[data-size]` tiles) size to their own content (~95-102px each), so at 390px two
     narrow tiles fit per row and the 5th ("Full Desk") wraps alone onto its own row,
     leaving ~58px+ of dead white space beside it — visibly misaligned against the
     Edge/Thickness option rows directly below, which stretch to fill the full card
     width. `flex: 1 1 40%` makes two tiles per row share the row evenly (own content
     plus growth), so the lone wrapped tile also stretches to fill its row instead of
     sitting at its bare content width. */
  .dmds-ported [data-size] {
    flex: 1 1 40% !important;
    text-align: center;
  }

  /* Custom-design upload dropzone (`.dmcus-h1`): a fixed 52x52 icon + 18px gap + 26px
     padding left only ~134px of a 258px box for the helper text, wrapping the single
     unbroken sentence "PNG, JPG, JPEG, PDF · minimum 3000 px on the long edge ·
     max 25MB · +$7.00" onto 6 lines with the "·" separators stranded at line ends/
     starts. Stack icon above text instead of beside it, so the text gets the box's
     full width. */
  .dmcus-h1 {
    flex-direction: column !important;
    align-items: flex-start !important;
    text-align: left;
  }

  /* ---- Custom-design live preview ------------------------------------------
     The preview stage was the one part of this page with NO responsive handling
     at all, and it is the page's centrepiece.

     Two separate problems, one visual symptom (a mat overflowing its dark card):

     1. HEIGHT. [data-stage] is a hard `height:470px` from the design deliverable.
        On a phone that is over half the viewport spent on a preview before a
        single control is reachable, and it does not shrink when the mat inside it
        does. `min()` against viewport height caps it without pinning it.

     2. WIDTH. [data-plane] gets its width in PIXELS from JavaScript (up to 660px
        for "Full Desk"), so CSS cannot fix it here — the JS now scales the plane
        to fit (see computeFit() in motion.js). What CSS must still do is stop the
        3D-rotated mat's corners from painting outside the rounded card while it
        is being dragged, which no amount of scaling fully prevents at extreme
        angles. `overflow:hidden` on the stage clips it to the card.

     `position:sticky` is also released: a sticky preview is a desktop affordance
     that keeps the mat in view beside a long control column. Stacked vertically on
     a phone there is no column beside it, and sticky merely pins the mat over the
     controls the customer is trying to reach. */
  .dmds-ported [data-stage] {
    /* Height tracks the card's own WIDTH, not the viewport: the mat is a wide, flat
       object seen at an angle, so its natural footprint is much wider than it is
       tall. A viewport-height-based box left a portrait-shaped card with the mat
       stranded in the middle of it. 62% of width lands close to the rotated mat's
       real aspect, so the dark card hugs what it contains at every phone width.
       The 470px cap preserves the design's desktop figure. */
    height: min(470px, 62vw) !important;
    min-height: 190px;
    overflow: hidden;
    /* The plane is centred by the design's own flex rules; keep that while the
       stage itself becomes the clipping boundary. */
    max-width: 100%;
  }

  .dmds-ported [data-stage] > [data-plane] {
    /* Belt and braces against the JS fit: never let the unrotated box exceed the
       stage, even for the one frame before computeFit() first runs. */
    max-width: 100%;
  }

  /* The dark preview card carries `position:sticky; top:24px` inline. */
  .dmds-ported [style*="position:sticky"] {
    position: static !important;
  }

  /* [data-name-input]'s placeholder ("Print a name or handle on the corner", 35
     characters) is design copy ported verbatim from Customize.dc.html — per the
     porting rule (§4 of the handoff), the fix belongs in CSS, not by shortening the
     source text. The input shares its row with a fixed "Free" label, leaving only
     ~176px for the text at 390px. Measured with canvas.measureText before choosing a
     fix: even at 10px (uncomfortably small) the string is 174px, a 2px margin that
     would break on the next slightly-longer name/handle a user types. Rather than
     chase a shrinking font size, use `text-overflow:ellipsis` as a real, honest
     truncation ("Print a name or handle o…") instead of the previous hard clip with no
     ellipsis at all ("...handle on th" — indistinguishable from a rendering bug). This
     affects the empty-state PLACEHOLDER only; once a user types their own name/handle
     (max 18 characters per design spec §7), it fits with room to spare. */
  /* text-overflow:ellipsis was tried first and confirmed NOT viable: Chromium ships a
     forced User-Agent rule (`input:not(...) { overflow-x/-y: clip !important }`,
     confirmed via CSS.getMatchedStylesForNode to have no author stylesheet at all —
     `styleSheetId: undefined` — meaning it is not an authored/GeneratePress rule that
     any selector specificity or !important can beat; it is how Chromium renders a
     native text input's internal scrolling box, full stop). Shrinking the font was
     tried second and rejected too: the full 35-character placeholder only just fits
     at 10px (canvas.measureText: 174px of 176px available, a 2px margin too fragile to
     rely on across font-rendering/zoom variance) and reads poorly at that size.
     Real fix: give the input the FULL row width by moving "Free" below it instead of
     beside it — the row was a fixed-width label squeezing a flex:1 input into
     whatever was left; stacking removes that constraint entirely rather than
     fighting it with a shrinking budget. */
  .dmds-ported [data-name-input] {
    /* The inline style is `flex:1`, i.e. shorthand `flex:1 1 0%` — its own `0%` basis
       needs `!important` to move, a plain `flex-basis:100%` loses to it. */
    flex: 1 1 100% !important;
    /* Stacking "Free" below (rather than beside) the input recovered most of the row
       width (176px -> 222px available), but the full 35-character placeholder still
       needs 235px at the design's 13.5px — 13px short. Rather than push font-size
       lower still (12.5px was the next step and reads noticeably small on a phone),
       accept a graceful partial reveal: this is disposable hint text, not user data,
       and "Print a name or handle on the corn…" clearly communicates the field's
       purpose, unlike the original "...handle on th" which read as a rendering bug. */
    font-size: 12.5px !important;
  }
  .dmds-ported [data-name-input] + span {
    margin-top: 6px;
  }
  .dmds-ported div:has(> [data-name-input]) {
    flex-wrap: wrap !important;
  }

  /* Configurator live-preview summary: name/spec column vs. "Total"/price column in a
     `justify-content:space-between; flex-wrap:wrap` row. At 390px the name column
     claims the full width and the price column wraps onto its own line beneath it —
     but keeps its desktop `text-align:right`, so on a narrow wrapped line the short
     word "TOTAL" hangs near the box's right edge (~38px in), reading as an
     unexplained indent relative to the flush-left name/spec above it, with no gap
     separating it from the wrapped spec text. Left-align and add breathing room once
     it has dropped to its own row. */
  .dmds-ported [style*="justify-content:space-between"] > div[style*="text-align:right"] {
    text-align: left !important;
    margin-top: 10px;
  }

  /* ---- Shipping's "Zones, times and cost" table (the ONE 4-column data grid in the
     ported pages, /shipping/ only) ----
     The generic single-column collapse above is correct for two-column layouts, but
     this grid is 4 columns x (1 header row + 4 data rows) authored as 20 sibling divs
     with no per-row wrapper — collapsing it to one column just stacks all 20 in source
     order. The result read as "ZONE / CARRIER / TRANSIT / COST" (four bare labels) then
     four unlabelled values per zone, with no visual link between a label and its value.

     Matched on the grid's exact desktop column signature so nothing else on the site
     can be affected. Selects the header cells (`:nth-child(-n+4)`, no border-top) and
     hides them, then re-labels each data cell with the matching column name via
     `::before`, using `:nth-child(4n+1..4n+4)` to know which column a cell belongs to
     — the same maths a table's `nth-child` addressing would use, applied to a grid
     with the row structure flattened out of it. */
  .dmds-ported [style*="grid-template-columns:1.2fr 1fr 0.8fr 0.8fr"] {
    grid-template-columns: 1fr !important;
    row-gap: 2px;
  }
  .dmds-ported [style*="grid-template-columns:1.2fr 1fr 0.8fr 0.8fr"] > div:nth-child(-n + 4) {
    display: none !important;
  }
  .dmds-ported [style*="grid-template-columns:1.2fr 1fr 0.8fr 0.8fr"] > div:nth-child(n + 5) {
    display: flex !important;
    justify-content: space-between !important;
    gap: 12px;
  }
  .dmds-ported [style*="grid-template-columns:1.2fr 1fr 0.8fr 0.8fr"] > div:nth-child(4n + 5)::before {
    content: 'Zone'; color: var(--dm-faint); font-family: var(--dm-font-mono); font-size: 11px;
    text-transform: uppercase; letter-spacing: 0.08em; flex: none;
  }
  .dmds-ported [style*="grid-template-columns:1.2fr 1fr 0.8fr 0.8fr"] > div:nth-child(4n + 6)::before {
    content: 'Carrier'; color: var(--dm-faint); font-family: var(--dm-font-mono); font-size: 11px;
    text-transform: uppercase; letter-spacing: 0.08em; flex: none;
  }
  .dmds-ported [style*="grid-template-columns:1.2fr 1fr 0.8fr 0.8fr"] > div:nth-child(4n + 7)::before {
    content: 'Transit'; color: var(--dm-faint); font-family: var(--dm-font-mono); font-size: 11px;
    text-transform: uppercase; letter-spacing: 0.08em; flex: none;
  }
  .dmds-ported [style*="grid-template-columns:1.2fr 1fr 0.8fr 0.8fr"] > div:nth-child(4n + 8)::before {
    content: 'Cost'; color: var(--dm-faint); font-family: var(--dm-font-mono); font-size: 11px;
    text-transform: uppercase; letter-spacing: 0.08em; flex: none;
  }
  /* Only the first cell of each 4-row group (the zone name) carries the border-top
     rule that used to separate grid ROWS; on a stacked list that reads as a rule
     before every value instead of before every zone. Suppress it on the other three
     and let the zone-name row alone carry the divider. */
  .dmds-ported [style*="grid-template-columns:1.2fr 1fr 0.8fr 0.8fr"] > div:nth-child(4n + 6),
  .dmds-ported [style*="grid-template-columns:1.2fr 1fr 0.8fr 0.8fr"] > div:nth-child(4n + 7),
  .dmds-ported [style*="grid-template-columns:1.2fr 1fr 0.8fr 0.8fr"] > div:nth-child(4n + 8) {
    border-top: none !important;
    padding-top: 2px !important;
  }
  .dmds-ported [style*="grid-template-columns:1.2fr 1fr 0.8fr 0.8fr"] > div:nth-child(4n + 8) {
    padding-bottom: 14px !important;
  }

  /* CTA button embedded inline INSIDE a centred <h2>/<h1> (About and Collections closing
     sections: "...The Rest of the Desk Follows. [Start Your Design ->]"). At desktop
     width the heading is short enough that the button trails the last word on the same
     line. At 390px `text-wrap:balance` rewraps the heading across more lines, and the
     inline-flex button — still part of the same text run — lands on top of whichever
     line it now falls beside, overlapping the heading text (confirmed live: button top
     fell inside the line-above's vertical range on both pages).
     Forcing the anchor onto its own line below the heading is the same treatment
     `.dmds-hero-h1 .dmds-btn-primary` already gets on the homepage hero for the
     identical authoring pattern; this generalises it to any heading, since the two
     instances here have no shared class. */
  .dmds-ported h1[style*="text-align:center"] > a[style*="display:inline-flex"],
  .dmds-ported h2[style*="text-align:center"] > a[style*="display:inline-flex"] {
    display: flex !important;
    width: max-content !important;
    position: static !important;
    top: auto !important;
    margin: 20px auto 0 !important;
  }

  /* Fixed-px section widths become fluid. */
  .dmds-ported [style*="max-width:1280px"] {
    max-width: 100% !important;
  }

  /* Wide flex rows (hero header + intro paragraph, spec rows) stack. */
  .dmds-ported [style*="justify-content:space-between"][style*="display:flex"] {
    flex-wrap: wrap !important;
  }
}

@media (max-width: 640px) {
  /* Nothing may exceed the viewport; images and media stay inside their box.

     EXCLUDES `img[style*="height:100%"]`. Several hero/photo cards are authored as a
     fixed-height `overflow:hidden` well with a child `<img style="width:100%;
     height:100%; object-fit:cover">` deliberately cropping a photo to fill the box —
     the About and Care page heroes, and two of the four Collections cards. The blanket
     `height:auto !important` here overrode that inline height, so the image reverted to
     its own intrinsic aspect ratio and rendered short, leaving 190-345px of the well's
     background colour exposed below it as a hard-edged, seemingly "broken image" band
     (confirmed on live pages, not just the screenshots). Those images already have their
     own `max-width:100%` and are already contained by the parent well's fixed height and
     `overflow:hidden`, so excluding them from this rule cannot cause new overflow. */
  .dmds-ported img:not([style*="height:100%"]),
  .dmds-ported video,
  .dmds-ported svg {
    max-width: 100% !important;
    height: auto !important;
  }

  /* Generous desktop padding would leave almost no content width on a phone. */
  .dmds-ported section[style*="padding"] {
    padding-left: 20px !important;
    padding-right: 20px !important;
  }

  /* Card padding normalisation for the `padding:44px 48px` body card shared by Terms,
     Privacy Policy and Licensing — the SAME inline style, but each page needs a
     DIFFERENT target, because each sits beside a different sibling card:

     - Licensing: this card sits below two `padding:36px` cards ("Your artwork" /
       "Our prints") with an otherwise identical look. 36px matches them exactly.
     - Terms/Privacy: this card sits below a sticky "On this page" nav card that uses
       `padding:24px`, not 36px — matched via :has() on that exact nav-card padding +
       "On this page" text, since neither page has a distinguishing body class. Using
       36px here (Licensing's answer) would leave a new, smaller mismatch instead of
       fixing this one (checked live before finalising: Licensing normalises cleanly at
       36/36, but Terms/Privacy's own nav card is 24px, not 36px).

     Whichever target applies, the underlying bug is the same: the 96px of desktop
     padding (44px 48px) squeezes the mobile text column enough that most section
     headings orphan-wrap onto two lines ("2. Orders and / acceptance"), where the
     identical string fits one line in the narrower-padded sibling card. */
  .dmds-ported [style*="padding:44px 48px"] {
    padding: 36px !important;
  }
  .dmds-ported div:has(> div[style*="padding:24px"] > div:first-child) + [style*="padding:44px 48px"],
  .dmds-ported [style*="padding:24px"]:has(> div:first-child) ~ [style*="padding:44px 48px"] {
    padding: 24px !important;
  }

  /* Display type is set in fixed px for a 1240px canvas; scale it fluidly instead of
     redefining the type scale (AD-9: same tokens, layout-only fork). */
  .dmds-ported h1 { font-size: clamp(34px, 9vw, 64px) !important; }
  .dmds-ported h2 { font-size: clamp(26px, 7vw, 46px) !important; }
  .dmds-ported h3 { font-size: clamp(19px, 5vw, 30px) !important; }

  /* Fixed-height decorative panels and media wells.

     `height:auto` + `min-height:240px` (NOT a real height) was the root cause of
     Collections' "photo cut off with a blank band underneath" bug on 2 of its 4 cards.
     A `min-height` is not a definite height for CSS percentage resolution — a child's
     `height:100%` cannot resolve against a parent whose size comes only from
     min-height, so the img fell back to its own intrinsic aspect ratio instead of
     filling the well. Cards 1/3 happened to use images whose intrinsic-ratio height
     exceeds 240px, so the auto-height container grew to fit them and looked correct
     by coincidence; cards 2/4's images are shorter, leaving a 77-88px gap of exposed
     background with the img's square corners visible against the well's rounded ones.
     A real (non-auto) `height` restores percentage resolution for any child that
     needs it, while still being far shorter than the fixed desktop values. */
  .dmds-ported [style*="height:400px"],
  .dmds-ported [style*="height:470px"],
  .dmds-ported [style*="height:560px"] {
    height: 240px !important;
  }

  /* Absolutely-positioned floating cards return to normal flow, per AD-9's rule that
     absolute desktop decoration converts to a responsive stack.

     MUST be scoped away from the shared header and footer. `.dmds-ported` sits on the
     BODY, so an unscoped descendant selector also matched the site header — which is
     shared by every page, ported or not. It hit the cart badge's 8px notification dot
     (`position:absolute; width:8px`), turning it `position:static; width:100%`, so the
     dot became a flex item and stretched into a 32px orange BAR beside the count on
     every ported page. Excluding header/footer keeps this rule on page content, which
     is all it was ever written for.

     Excluded with :not(:is(header *, footer *)) rather than by scoping to a content
     wrapper: the ported pages render straight into Elementor's own container, with no
     `main`/`.site-content` ancestor to hang the rule on, so a container-scoped version
     silently matched NOTHING and disabled the fix entirely. Audited across all ten
     ported pages: 9 had the cart dot as the ONLY match, /custom-design/ has one genuine
     page-body element, which this still reaches. */
  .dmds-ported [style*="position:absolute"][style*="width:"]:not(:is(header *, footer *)) {
    position: static !important;
    width: 100% !important;
  }
}

/* The configurator's 3D preview plane is sized in JS from the design's own preview-plane
   table; cap it so a Full Desk selection cannot push past a phone viewport. */
@media (max-width: 767px) {
  .dmds-ported [data-plane] {
    max-width: 88vw !important;
  }
  .dmds-ported [data-stage] {
    overflow: hidden !important;
  }
}

/* ---- Deliberately-wider-than-viewport rows ----
   Some rows are meant to exceed the viewport: the marquee ticker (design spec §6.11)
   scrolls a `width:max-content` track, and Collections' proportional size-scale is a row
   of fixed-px cards whose whole point is their relative width. These must be CLIPPED or
   made swipeable — never squashed, which would destroy the comparison they exist to make.
   Without this the page reports horizontal overflow and the whole document scrolls
   sideways, which is the actual bug.

   `.dmds-marquee` MATCHED NOTHING. The rendered homepage contains exactly one marquee
   node — `.dmds-marquee-track` — and no `.dmds-marquee` wrapper at any level, so this
   rule never applied to anything and the 832px duplicated ticker track pushed the body
   to 711px on a 390px viewport. (Fourth instance in this project of "CSS scoped to an
   ancestor that never existed"; verified with
   `grep -o 'class="[^"]*marquee[^"]*"'` against the live page before changing it.)

   The track must NOT be clipped on itself — it is the thing that translates. Clipping
   goes on its parent, which is an unclassed Elementor wrapper div, so the parent is
   selected via :has(). The track keeps max-content sizing so the animation still runs. */
/* Collections' proportional size-scale row: clip the fixed-px card row at its box.
   Deliberately EXCLUDES the marquee track — see below. */
.dmds-page [style*="width:max-content"]:not(.dmds-marquee-track) {
  max-width: 100% !important;
  overflow: hidden !important;
}

/* The marquee: clip the PARENT, never the track.
   Constraining the track itself to `max-width:100%` is what made the ticker wrap to
   three lines and read "ITCHED / FOUR / DES" on a phone — a flex row squeezed to 350px
   wraps its items instead of scrolling them. The track must keep its intrinsic
   max-content width for the dmMarquee translate to have anything to travel across;
   the overflow is the point, so the containing box is what hides it. */
.dmds-marquee,
.dmds-page :has(> .dmds-marquee-track) {
  max-width: 100% !important;
  overflow: hidden !important;
}

.dmds-page .dmds-marquee-track {
  max-width: none !important;
  width: max-content !important;
  flex-wrap: nowrap !important;
}
.dmds-page .dmds-marquee-track > * {
  flex: none !important;
}
.dmds-page .dmds-marquee-track span {
  white-space: nowrap !important;
}

@media (max-width: 1239px) {
  /* Size-scale style rows become horizontally swipeable within their own box, so the
     document itself never scrolls sideways (AD-9's touch-appropriate equivalent). */
  .dmds-ported [style*="display:flex"][style*="align-items:flex-end"] {
    overflow-x: auto !important;
    -webkit-overflow-scrolling: touch !important;
    scrollbar-width: none;
  }
  .dmds-ported [style*="display:flex"][style*="align-items:flex-end"]::-webkit-scrollbar {
    display: none;
  }
}

/* Final backstop: nothing may scroll the document sideways on any page. Individual
   components above are given a real responsive treatment; this only guarantees that a
   value missed in a ported file cannot break the whole page.

   `clip` rather than `hidden`, deliberately but modestly:

     `overflow-x: hidden` forces the other axis to compute as `auto` rather than
     `visible` (CSS Overflow §3), so the previous `overflow-x: hidden` on html AND
     body left the root reporting overflow-y:auto. `overflow-x: clip` prevents
     horizontal overflow WITHOUT establishing a scroll container, so the y axis stays
     `visible`. That is the tidier primitive and avoids a known class of root-scroller
     pitfalls.

   HONEST SCOPE OF THIS CHANGE: it did NOT fix a user-visible bug. It was changed
   while chasing an apparent "document only scrolls half way" failure, which turned
   out to be the TEST measuring a `scroll-behavior: smooth` animation mid-flight —
   measured with behavior:'instant', the old `hidden` and the new `clip` both scroll
   to the full height, and position:sticky resolves under both. The stylesheet is
   marginally better this way; the bug was in the harness, and that is recorded here
   so nobody later "restores" hidden believing it broke scrolling, or trusts this
   comment as evidence of a fix that never happened.

   The !important flags are dropped because nothing was overriding these. */
html,
body.dmds-page {
  overflow-x: clip;
  max-width: 100%;
}

/* ============================================================================
   CONTACT FORM (T6 — FR-14)

   THERE IS NO DESIGN FILE FOR THIS PAGE either (see build-content.php's header
   comment) — the surrounding info cards were hand-built to the component language,
   but the form itself was left as Elementor Pro's own default widget CSS, which never
   inherits the theme: Roboto instead of Archivo, square 0-radius fields with a plain
   dark border, and Elementor's stock green (#61CE70) submit button. Every other
   element on the page — cards, header, footer — is rounded/cream/black/orange, so the
   form reads as if it were embedded from a different site. Restyled to the same
   token language as the rest of the storefront; the form's behaviour (validation,
   required fields, email delivery) is untouched. */
body.dmds-page .elementor-form .elementor-field-label {
  font-family: var(--dm-font-display);
  font-size: 13px;
  font-weight: 600;
  color: var(--dm-ink);
  margin-bottom: 6px;
}
body.dmds-page .elementor-form .elementor-field {
  font-family: var(--dm-font-display);
  font-size: 14px;
  color: var(--dm-ink);
  background: var(--dm-inset);
  border: 1px solid var(--dm-hairline-strong);
  border-radius: var(--dm-radius-well);
  padding: 12px 16px;
  min-height: 48px;
  box-sizing: border-box;
}
body.dmds-page .elementor-form textarea.elementor-field {
  min-height: 120px;
  resize: vertical;
}
body.dmds-page .elementor-form .elementor-field:focus {
  outline: none;
  border-color: var(--dm-accent);
  background: var(--dm-card);
}
body.dmds-page .elementor-form .elementor-field::placeholder {
  color: var(--dm-faint);
}
body.dmds-page .elementor-form .elementor-button {
  font-family: var(--dm-font-display);
  font-size: 13.5px;
  font-weight: 600;
  background: var(--dm-accent) !important;
  color: #FFFFFF;
  border-radius: var(--dm-radius-pill) !important;
  padding: 14px 24px;
  min-height: 44px;
  border: none;
  transition: background .2s var(--dm-ease-standard);
}
@media (hover: hover) and (pointer: fine) {
  body.dmds-page .elementor-form .elementor-button:hover {
    background: var(--dm-accent-hover) !important;
  }
}
/* Real client-side validation errors, not decoration — kept legible, not themed away. */
body.dmds-page .elementor-form .elementor-error .elementor-field {
  border-color: #C0392B;
}
body.dmds-page .elementor-form .elementor-message {
  font-family: var(--dm-font-display);
  font-size: 13px;
}

/* The form's own Elementor section is a separate widget from the hand-built info-card
   HTML above it (see build-content.php) and never picked up that block's
   `max-width:1280px; padding:… 32px 0` inset, so on mobile it sat 16-20px further out on
   both sides than the cards and heading above it — a visible step in the left/right
   margin scrolling from the FAQ card into the form. Match the same inset at both
   breakpoints. The Send button also landed flush against the footer with 0px gap
   (every other section break on the page has 14-50px) because the form section itself
   had no bottom padding. */
body.dmds-page .elementor-widget-form {
  max-width: 1280px;
  margin: 0 auto;
  padding: 0 32px 48px;
  box-sizing: border-box;
}
@media (max-width: 640px) {
  body.dmds-page .elementor-widget-form {
    padding: 0 20px 40px;
  }
}

/* The EMAIL info card's address has inline `word-break:break-word`, which breaks
   ANYWHERE a line is tight — including mid-domain-label — so "...@ensem.ac.ma" split
   as "...@ense" / "m.ac.ma", a meaningless fragment a user could mis-transcribe.

   `overflow-wrap` alone cannot fix this: verified live that `anywhere` produces the
   identical break point, because both properties are willing to fracture a word
   wherever needed once nothing else fits at 17px/600 in a 258px card. Real fix is in
   build-content.php: a `<wbr>` after `@` gives the browser an explicit, meaningful break
   opportunity, so wrapping (still sometimes necessary at this width) happens at
   "mourad.elbouhemi.etu24@" / "ensem.ac.ma" instead of mid-label. Non-break-anywhere
   restored here so `<wbr>` is the ONLY place a break can occur. */
.dmds-contact-grid [style*="word-break:break-word"] {
  word-break: normal !important;
  overflow-wrap: normal !important;
}

/* ============================================================================
   CART & CHECKOUT (T3 — FR-10, FR-11)

   THERE IS NO DESIGN FILE FOR THESE TWO PAGES. design/Desk Mat Homepage Design/
   ships 13 .dc.html deliverables; Cart and Checkout are not among them. So unlike
   every other page in this build, these rules are NOT a transcription of a
   specification — they extend the existing token/component language (card radius,
   shadow, pill button, mono microcopy, inset wells) to WooCommerce's own markup.
   Any pixel difference from "the design" here is unavoidable, not an oversight.

   THESE PAGES ARE WOOCOMMERCE BLOCKS, NOT THE CLASSIC SHORTCODES. Cart page 6 and
   Checkout page 7 contain wp:woocommerce/cart and wp:woocommerce/checkout, which
   render client-side through React. Two consequences that cost time if unknown:
     1. curl CANNOT verify these pages. The served HTML is a skeleton that reads
        "Your cart is currently empty" regardless of cart contents; the real markup
        only exists after hydration. Verify in a browser or not at all.
     2. Styling must target .wc-block-* classes. Classic selectors like
        .woocommerce-cart-form or .shop_table are never emitted here and any rule
        written against them silently does nothing — the same class of bug that
        already shipped twice in this project.
   ============================================================================ */

.dmds-page .wp-block-woocommerce-cart,
.dmds-page .wp-block-woocommerce-checkout {
  max-width: var(--dm-container-max);
  margin: 0 auto;
  padding: 48px var(--dm-container-pad) 96px;
  font-family: var(--dm-font-display);
  color: var(--dm-ink);
}

/* Panels: the same white card the rest of the site uses. */
.dmds-page .wc-block-cart-items,
.dmds-page .wc-block-components-sidebar,
.dmds-page .wc-block-checkout__form,
.dmds-page .wc-block-components-order-summary {
  background: var(--dm-card);
  border-radius: var(--dm-radius-card);
  box-shadow: var(--dm-shadow-card-rest);
  padding: 26px;
  border: none;
}

.dmds-page .wc-block-components-sidebar {
  position: sticky;
  top: 24px;
}

/* Headings pick up the display face and the design's tight tracking. */
.dmds-page .wp-block-woocommerce-cart h1,
.dmds-page .wp-block-woocommerce-checkout h1,
.dmds-page .wc-block-components-title,
.dmds-page .wc-block-cart__totals-title,
.dmds-page .wc-block-components-checkout-step__title {
  font-family: var(--dm-font-display);
  font-weight: 700;
  letter-spacing: -0.03em;
  color: var(--dm-ink);
}

/* Section eyebrows / totals labels use the mono microcopy voice (spec §4). */
.dmds-page .wc-block-components-totals-item__label,
.dmds-page .wc-block-components-checkout-step__description,
.dmds-page .wc-block-components-product-metadata {
  font-family: var(--dm-font-mono);
  font-size: 11.5px;
  letter-spacing: 0.04em;
  color: var(--dm-muted);
}

.dmds-page .wc-block-components-product-name {
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--dm-ink);
}

/* Line-item thumbnails match the product-card radius. */
.dmds-page .wc-block-cart-item__image img,
.dmds-page .wc-block-components-order-summary-item__image img {
  border-radius: var(--dm-radius-tile);
}

/* Hairlines rather than WooCommerce's default heavy rules. */
.dmds-page .wc-block-cart-items__row,
.dmds-page .wc-block-components-totals-wrapper {
  border-color: var(--dm-hairline) !important;
}

/* Buttons: the design's pill CTA, accent fill, lift on hover. */
.dmds-page .wc-block-components-button:not(.is-link),
.dmds-page .wc-block-cart__submit-button,
.dmds-page .wc-block-components-checkout-place-order-button {
  background: var(--dm-accent);
  color: #FFFFFF;
  border: none;
  border-radius: var(--dm-radius-pill);
  font-family: var(--dm-font-display);
  font-weight: 600;
  font-size: 14px;
  padding: 14px 26px;
  transition: background .25s var(--dm-ease-standard), transform .25s var(--dm-ease-standard);
}

/* AD-9: lift only where a real pointer exists, so touch users do not get a
   sticky stuck-hover state. */
@media (hover: hover) and (pointer: fine) {
  .dmds-page .wc-block-components-button:not(.is-link):hover,
  .dmds-page .wc-block-cart__submit-button:hover,
  .dmds-page .wc-block-components-checkout-place-order-button:hover {
    background: var(--dm-accent-hover);
    transform: translateY(-2px);
  }
}

/* Inputs: the design's rounded well, accent focus ring. */
.dmds-page .wc-block-components-text-input input,
.dmds-page .wc-block-components-textarea,
.dmds-page .wc-block-components-combobox input,
.dmds-page .wc-block-components-select__container select {
  border: 1px solid var(--dm-hairline-strong);
  border-radius: var(--dm-radius-well);
  background: var(--dm-card);
  font-family: var(--dm-font-display);
  font-size: 14px;
  color: var(--dm-ink);
}

/* FR-26: focus must be visible, and must not rely on colour alone — hence the
   outline offset as well as the accent colour. */
.dmds-page .wc-block-components-text-input input:focus,
.dmds-page .wc-block-components-textarea:focus,
.dmds-page .wc-block-components-combobox input:focus,
.dmds-page .wc-block-components-button:focus-visible,
.dmds-page .wc-block-components-checkout-place-order-button:focus-visible {
  outline: 2px solid var(--dm-accent);
  outline-offset: 2px;
  border-color: var(--dm-accent);
}

/* Shipping/payment option rows read as the design's inset wells.

   The left padding is NOT decorative. WooCommerce positions the radio input
   ABSOLUTELY inside this row and relies on the row's own left padding to reserve
   space for it. Setting a symmetric `padding: 12px 16px` here (as this rule first
   did) collapsed that space, so the radio marker rendered directly on top of the
   label and the selected shipping method read as "◉ndard shipping (3–5 days)".
   Keep the left padding and the input's `left` in step if either changes. */
.dmds-page .wc-block-components-radio-control__option {
  background: var(--dm-inset);
  border-radius: var(--dm-radius-tile);
  padding: 12px 16px 12px 46px;
  margin-bottom: 10px;
  border: none;
}

.dmds-page .wc-block-components-radio-control__input {
  left: 16px;
}
.dmds-page .wc-block-components-radio-control__option--checked-option-highlighted,
.dmds-page .wc-block-components-radio-control__option:has(input:checked) {
  background: var(--dm-ink);
  color: #FFFFFF;
}

/* The "no payment methods" state is currently REAL on this install (PRD OQ-3 is
   open, Stripe has no keys, so zero gateways resolve). Make it read as a designed
   notice rather than a raw error, but do not hide it — hiding it would disguise a
   genuine blocker. */
.dmds-page .wc-block-checkout__payment-method .wc-block-components-notice-banner {
  border-radius: var(--dm-radius-well);
  font-family: var(--dm-font-display);
}

/* ---- Mobile (FR-11: no horizontal scrolling, no broken widgets at 390px) ---- */
@media (max-width: 900px) {
  .dmds-page .wp-block-woocommerce-cart,
  .dmds-page .wp-block-woocommerce-checkout {
    padding: 28px 18px 64px;
  }

  /* Two-column cart/checkout collapses to one; the sidebar must stop being sticky
     or it pins mid-scroll on a short viewport. */
  .dmds-page .wc-block-cart,
  .dmds-page .wc-block-checkout__main,
  .dmds-page .wc-block-components-sidebar-layout {
    display: block !important;
  }
  .dmds-page .wc-block-components-sidebar {
    position: static;
    width: 100% !important;
    margin-top: 18px;
  }
  .dmds-page .wc-block-components-main {
    width: 100% !important;
  }

  /* CHECKOUT ONLY: the order summary is rendered TWICE. WooCommerce puts a full summary
     inline in the checkout step list AND another in the sidebar column that sits beside
     the form on desktop. Once the sidebar stacks underneath, the customer scrolls past
     the totals, the Place Order button, and then meets a second "Order summary $68.90"
     — which reads like a second charge. Keep the inline one (it precedes Place Order,
     so the total is visible before committing, per FR-11) and drop the stacked repeat.
     The CART page keeps its sidebar: there the summary appears only once. */
  .dmds-page .wc-block-checkout .wc-block-components-sidebar {
    display: none !important;
  }

  .dmds-page .wc-block-cart-items,
  .dmds-page .wc-block-components-sidebar,
  .dmds-page .wc-block-checkout__form {
    padding: 18px;
  }

  /* The cart line-item table is the classic overflow culprit at 390px. */
  .dmds-page .wc-block-cart-items,
  .dmds-page .wc-block-cart-items__row {
    max-width: 100%;
    box-sizing: border-box;
  }
  .dmds-page .wc-block-cart-item__image {
    width: 72px;
  }

  /* FR-26: 44px minimum touch target. */
  .dmds-page .wc-block-components-button:not(.is-link),
  .dmds-page .wc-block-cart__submit-button,
  .dmds-page .wc-block-components-checkout-place-order-button,
  .dmds-page .wc-block-components-quantity-selector__button {
    min-height: 44px;
  }
  .dmds-page .wc-block-cart__submit-button,
  .dmds-page .wc-block-components-checkout-place-order-button {
    width: 100%;
  }
}

/* ============================================================================
   PRODUCT PAGE — variation pills, FR-5 size guide, spec accordions (T5)
   Design spec §10.5. The pills and accordions transcribe the spec; the size
   guide does NOT exist in the design deliverable and is built from these same
   tokens to satisfy FR-5, which requires a graphic rather than a dimension table.
   ============================================================================ */

/* The native select stays in the DOM (WooCommerce owns it) but out of sight.
   Clip rather than display:none — WooCommerce reads/writes the control, and it
   stays available to assistive tech that prefers the native widget. */
.dmds-page .dmds-visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

.dmds-page .dmds-pill-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 8px 0 4px;
}

.dmds-page .dmds-pill {
  cursor: pointer;
  background: var(--dm-card);
  color: #4A443C;
  border: 1px solid var(--dm-hairline-strong);
  border-radius: var(--dm-radius-pill);
  padding: 10px 16px;
  font-family: var(--dm-font-display);
  font-size: 13px;
  font-weight: 600;
  line-height: 1;
  min-height: 40px;
  transition: background .2s var(--dm-ease-standard), color .2s var(--dm-ease-standard);
  /* The pills are <button>s, and GeneratePress styles bare buttons as display:block.
     As flex items that still resolved to block-level boxes they each claimed a full
     line, so the size/thickness/edge rows rendered as a ragged vertical staircase
     instead of the design's wrapping pill rows. inline-flex restores the intended row;
     `flex: 0 0 auto` stops a long label ("Full Desk 1200×600") from being shrunk. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: auto;
  max-width: 100%;
  white-space: nowrap;
  margin: 0;
}

/* Elementor Pro's add-to-cart widget ships:
     ….elementor-widget-woocommerce-product-add-to-cart form.cart button:where(:not(:first-child))
       { margin-inline-start: var(--button-spacing, 10px) }
   It is meant to space a quantity control from the Add-to-Cart button, but it matches
   EVERY variation pill after the first. Two consequences on a 390px viewport: 10px was
   added on top of the row's own `gap: 8px`, pushing the second pill past the wrap point
   so each size took its own line; and each wrapped pill then sat 10px further in than
   the first, producing a ragged staircase down the page.

   Overriding it needs care — a plain `margin: 0` on `.dmds-pill` loses on specificity,
   and because the widget sets the LOGICAL property, a physical `margin-left` does not
   reliably win either. So: match the same logical property, and use `--button-spacing`
   which the widget itself reads, at a specificity that beats the vendor selector. */
body.dmds-page .elementor-widget-woocommerce-product-add-to-cart form.cart .dmds-pill-row .dmds-pill,
body.dmds-page .elementor-widget-wc-add-to-cart form.cart .dmds-pill-row .dmds-pill,
body.dmds-page form.cart .dmds-pill-row .dmds-pill:where(:not(:first-child)) {
  --button-spacing: 0px;
  margin-inline-start: 0 !important;
  margin-inline-end: 0 !important;
}

.dmds-page .dmds-pill-active {
  background: var(--dm-ink);
  color: #FFFFFF;
  border-color: var(--dm-ink);
}

/* FR-4: an unavailable combination must never look purchasable. Strike-through
   plus reduced opacity plus a title — not colour alone (FR-26). */
.dmds-page .dmds-pill-disabled {
  opacity: .4;
  cursor: not-allowed;
  text-decoration: line-through;
}

.dmds-page .dmds-pill:focus-visible {
  outline: 2px solid var(--dm-accent);
  outline-offset: 2px;
}

@media (hover: hover) and (pointer: fine) {
  .dmds-page .dmds-pill:not(.dmds-pill-active):not(.dmds-pill-disabled):hover {
    border-color: var(--dm-ink);
  }
}

/* ---- FR-5 size-comparison guide ---- */
.dmds-page .dmds-size-guide {
  background: var(--dm-card);
  border-radius: var(--dm-radius-card);
  box-shadow: var(--dm-shadow-card-rest);
  padding: 20px 22px 18px;
  margin: 22px 0;
}

.dmds-page .dmds-size-guide__eyebrow {
  font-family: var(--dm-font-mono);
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dm-faint);
}

.dmds-page .dmds-size-guide__stage { margin-top: 14px; }

/* The desk is drawn at a fixed aspect ratio (140x70cm) so the mat inside it can be
   sized as a straight percentage — that is what makes the comparison honest. */
.dmds-page .dmds-size-guide__desk {
  position: relative;
  width: 100%;
  aspect-ratio: 2 / 1;
  background: var(--dm-inset);
  border-radius: var(--dm-radius-well);
  border: 1px solid var(--dm-hairline);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.dmds-page .dmds-size-guide__mat {
  background: var(--dm-ink);
  border-radius: 6px;
  transition: width .35s var(--dm-ease-standard), height .35s var(--dm-ease-standard);
  box-shadow: 0 8px 18px rgba(20,18,15,.25);
}

.dmds-page .dmds-size-guide__caption {
  margin-top: 12px;
  font-family: var(--dm-font-mono);
  font-size: 11.5px;
  color: var(--dm-muted);
}

/* ---- Specification / Shipping / Care accordions ----

   initProductAccordions() rewrites WooCommerce's tab strip into rows, and the rules
   below used to style only the CHROME — the row and the panel box. Nothing styled what
   is INSIDE a panel, so every panel's contents fell straight through to GeneratePress:
   the `Material` / `Care` / `Production & shipping time` sub-heads rendered as 29px
   weight-400 theme h3s, and the attributes table kept WooCommerce's stock centred <th>s
   and 8px cell padding. The chrome matched the design system and everything it framed
   did not, which reads worse than leaving the whole block unstyled.

   The sub-heads are set in the mono uppercase eyebrow (spec §4) rather than the display
   face on purpose: the SKU and CATEGORY labels sitting directly above this block already
   use exactly that treatment, so the whole specification region now reads as one system
   instead of a styled header sitting on top of unstyled prose. */

/* The block is a card in its own right — the same white / 22px / resting-shadow idiom
   as every other panel in the design system. It previously had no background at all;
   the white came from the rows themselves, which is why the corners were square and the
   text ran to the container edge. */
.dmds-page .dmds-accordion-tabs {
  background: var(--dm-card);
  border-radius: var(--dm-radius-card);
  box-shadow: var(--dm-shadow-card-rest);
  padding: 2px 30px 12px;
}

.dmds-page .dmds-acc-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  width: 100%;
  background: none;
  border: none;
  border-top: 1px solid var(--dm-hairline);
  padding: 21px 0;
  font-family: var(--dm-font-display);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--dm-ink);
  cursor: pointer;
  text-align: left;
  transition: color .25s var(--dm-ease-standard);
}

/* The card's own top edge already separates the first row from what is above it. A rule
   there reads as an empty row rather than as a divider. */
.dmds-page .dmds-accordion-tabs .dmds-acc-row:first-of-type {
  border-top: 0;
}

@media (hover: hover) and (pointer: fine) {
  .dmds-page .dmds-acc-row:hover,
  .dmds-page .dmds-acc-row:hover .dmds-acc-chevron {
    color: var(--dm-accent);
  }
}

.dmds-page .dmds-acc-row:focus-visible {
  outline: 2px solid var(--dm-accent);
  outline-offset: -2px;
}

.dmds-page .dmds-acc-chevron {
  color: var(--dm-faint);
  font-size: 12px;
  transition: transform .25s var(--dm-ease-standard), color .25s var(--dm-ease-standard);
}
.dmds-page .dmds-acc-row[aria-expanded="true"] .dmds-acc-chevron {
  transform: rotate(180deg);
}

.dmds-page .dmds-acc-panel {
  padding: 2px 0 28px;
  font-size: 14px;
  line-height: 1.75;
  color: var(--dm-body-dim);
  /* Measure, not container width. The card runs the full 1280px content width because
     the accordion rows want it; a 1280px line of 14px text is ~170 characters. */
  max-width: 72ch;
}

.dmds-page .dmds-acc-panel p {
  margin: 0 0 16px;
  font-size: 14px;
  line-height: 1.75;
  color: var(--dm-body-dim);
}
.dmds-page .dmds-acc-panel p:last-child {
  margin-bottom: 0;
}

/* Sub-heads inside a panel. Mono uppercase, matching the SKU / CATEGORY labels above. */
.dmds-page .dmds-acc-panel h2,
.dmds-page .dmds-acc-panel h3,
.dmds-page .dmds-acc-panel h4 {
  margin: 28px 0 10px;
  font-family: var(--dm-font-mono);
  font-size: 11px;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dm-faint);
}

.dmds-page .dmds-acc-panel > *:first-child {
  margin-top: 0;
}

.dmds-page .dmds-acc-panel ul,
.dmds-page .dmds-acc-panel ol {
  margin: 0 0 16px;
  padding-left: 18px;
}
.dmds-page .dmds-acc-panel li {
  margin-bottom: 6px;
}
.dmds-page .dmds-acc-panel li::marker {
  color: var(--dm-accent);
}

.dmds-page .dmds-acc-panel strong {
  font-weight: 600;
  color: var(--dm-ink);
}

.dmds-page .dmds-acc-panel a {
  color: var(--dm-accent);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ---- Additional information: the product attributes table ----
   WooCommerce ships this as a bordered data table with centred <th>s, which is the
   single most out-of-place element on the product page. Rebuilt as label/value rows:
   mono uppercase label in the same treatment as the sub-heads, value in body text, one
   hairline between rows and none around the outside. */
.dmds-page .dmds-acc-panel .shop_attributes {
  width: 100%;
  max-width: 640px;
  margin: 0;
  border: 0;
  border-collapse: collapse;
}

.dmds-page .dmds-acc-panel .shop_attributes tr {
  border-bottom: 1px solid var(--dm-hairline);
}
.dmds-page .dmds-acc-panel .shop_attributes tr:last-child {
  border-bottom: 0;
}

.dmds-page .dmds-acc-panel .shop_attributes th,
.dmds-page .dmds-acc-panel .shop_attributes td {
  border: 0 !important;
  background: none !important;
  padding: 15px 0 !important;
  text-align: left !important;
  vertical-align: top;
}

.dmds-page .dmds-acc-panel .shop_attributes th {
  width: 180px;
  font-family: var(--dm-font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dm-faint);
  /* +2px over the value cell: 11px mono caps sit lower than a 14px display line, so
     matching the box padding leaves the label looking like it has slipped. */
  padding-top: 17px !important;
}

/* font-style is reset because WooCommerce italicises attribute values in its own
   stylesheet. Italic is the design system's emphasis, not its default body face, and a
   whole column of italic reads as a quotation rather than as specification data. */
.dmds-page .dmds-acc-panel .shop_attributes td {
  font-size: 14px;
  font-style: normal;
  color: var(--dm-body-dim);
}

/* WooCommerce wraps every attribute value in its own <p>. */
.dmds-page .dmds-acc-panel .shop_attributes td p {
  margin: 0;
  font-size: inherit;
  font-style: normal;
  line-height: 1.6;
}

/* ---- Reviews panel ----
   Both of these ship as bare <p>s that pick up whatever the theme does to notices. */
.dmds-page .dmds-acc-panel .woocommerce-noreviews,
.dmds-page .dmds-acc-panel .woocommerce-verification-required {
  margin: 0 0 10px;
  padding: 0;
  border: 0;
  background: none;
  font-size: 14px;
  color: var(--dm-muted);
}
.dmds-page .dmds-acc-panel .woocommerce-verification-required {
  margin-bottom: 0;
  color: var(--dm-faint);
}

/* WooCommerce's own tab chrome, now redundant. */
.dmds-page .dmds-accordion-tabs ul.tabs { display: none !important; }

@media (max-width: 767px) {
  .dmds-page .dmds-accordion-tabs {
    padding: 2px 20px 10px;
  }

  /* 180px of label plus a value column does not fit a phone, and a table that does not
     fit is a table that scrolls the page sideways. Stack label over value instead. */
  .dmds-page .dmds-acc-panel .shop_attributes th,
  .dmds-page .dmds-acc-panel .shop_attributes td {
    display: block;
    width: auto;
  }
  .dmds-page .dmds-acc-panel .shop_attributes th {
    padding: 15px 0 3px !important;
  }
  .dmds-page .dmds-acc-panel .shop_attributes td {
    padding: 0 0 15px !important;
  }
}

/* ============================================================================
   WOOCOMMERCE PRODUCT GRID — cards, result count, ordering, pagination

   MOVED HERE from the inline <style> inside build-shop-archive.php, and the
   `.woocommerce` ancestor requirement DROPPED. Both changes fix the same real bug.

   The rules used to read `body.dmds-page .woocommerce ul.products li.product` and
   lived only in the Shop archive template. That meant:
     - they were only ever loaded on /shop/, so any other product grid got raw
       theme defaults, and
     - they required a `.woocommerce` ancestor that does not exist everywhere.

   The related-products grid on a single product page nests as
       div.elementor-element > section.related.products > ul.products > li.product
   with NO `.woocommerce` wrapper anywhere in the chain. Every card rule therefore
   missed, and related products rendered as transparent boxes with GeneratePress's
   blue link colour on the price (rgb(110,193,228)) while the shop page looked
   perfect. This is the third instance in this project of "CSS scoped to an
   ancestor that never existed" — the failure mode is always that the page it was
   authored against looks right, so nothing flags it.

   Scoping to `body.dmds-page ul.products li.product` covers the shop archive,
   related products, up-sells, cross-sells and category archives with one rule set.
   ============================================================================ */

body.dmds-page ul.products {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 22px;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Specificity note: Elementor Pro's own archive-products widget ships
   `.elementor-products-grid ul.products.elementor-grid li.product { padding: 0 }`
   (3 class selectors) which outranks a plain `body.dmds-page ul.products li.product`
   (1 class + 2 tags) regardless of source order, so the declared `padding: 18px` below
   was silently overridden to 0 on every axis. Effect: the "Select options" button sat
   flush against the card's bottom edge on all 9 cards — its flat bottom colliding with
   the panel's own 22px-radius rounded corner — while every other side still looked
   padded because the image/title/price stack happened to fill the rest of the card.
   `!important` is the correct tool here (not a selector-specificity arms race against a
   vendor plugin's own widget CSS), scoped narrowly to this one property. */
body.dmds-page ul.products li.product {
  background: var(--dm-card);
  border-radius: var(--dm-radius-card);
  padding: 18px !important;
  box-shadow: var(--dm-shadow-card-rest);
  transition: transform .4s var(--dm-ease-standard), box-shadow .4s var(--dm-ease-standard);
  list-style: none;
}

/* AD-9: lift only where a real pointer exists. */
@media (hover: hover) and (pointer: fine) {
  body.dmds-page ul.products li.product:hover {
    transform: translateY(-6px);
    box-shadow: var(--dm-shadow-card-hover);
  }
}

body.dmds-page ul.products li.product a {
  text-decoration: none;
  color: var(--dm-ink);
  display: block;
}

body.dmds-page ul.products li.product img {
  width: 100%;
  border-radius: 10px;
  background: var(--dm-inset);
  padding: 14px;
  box-sizing: border-box;
  height: 230px;
  object-fit: cover;
}

/* Same colour bleed as the price below: the title sits inside WooCommerce's loop <a>,
   and the theme's link rule is more specific than this one, so without !important the
   card titles render in GeneratePress blue (rgb(110,193,228)) instead of ink. Caught on
   the mobile shop screenshot — the desktop homepage looked fine because its cards are
   hand-built design markup, not the WooCommerce loop. */
body.dmds-page ul.products li.product h2,
body.dmds-page ul.products li.product .woocommerce-loop-product__title,
body.dmds-page ul.products li.product a h2,
body.dmds-page ul.products li.product a .woocommerce-loop-product__title {
  font-size: 14.5px;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.015em;
  margin: 16px 0 6px;
  color: var(--dm-ink) !important;
}

/* Prices inherit the theme's blue link colour otherwise — the design specifies ink.
   This is the rule that was visibly missing on related products.

   `!important` on the colour is deliberate and narrow. The price sits inside
   WooCommerce's loop <a>, and GeneratePress/Elementor colour that link with a
   selector more specific than this one. Without it, the AMOUNT spans go ink (they
   are matched directly) while the bare `span.price` wrapper stays blue — which
   shows up as a blue en-dash between the two halves of a price range,
   "$17.00 <blue –> $75.00". Raising specificity by chaining more theme classes
   would just re-couple this rule to markup we do not control, which is the mistake
   that produced the `.woocommerce`-ancestor bug above. */
body.dmds-page ul.products li.product .price,
body.dmds-page ul.products li.product .price *,
body.dmds-page ul.products li.product a .price {
  font-size: 13px;
  color: var(--dm-ink) !important;
  font-weight: 600;
}
body.dmds-page ul.products li.product .price del { opacity: .5; font-weight: 400; }
body.dmds-page ul.products li.product .price ins { text-decoration: none; }

body.dmds-page ul.products li.product a:hover .woocommerce-loop-product__title {
  color: var(--dm-ink);
}

/* "Add to cart" / "Select options" — the design's accent pill, not a grey theme button. */
body.dmds-page ul.products li.product .button,
body.dmds-page ul.products li.product a.button {
  background: var(--dm-accent);
  color: #FFFFFF;
  border-radius: var(--dm-radius-pill);
  padding: 10px 18px;
  font-size: 13px;
  font-weight: 600;
  display: inline-block;
  margin-top: 10px;
  border: none;
}
@media (hover: hover) and (pointer: fine) {
  body.dmds-page ul.products li.product .button:hover {
    background: var(--dm-accent-hover);
    color: #FFFFFF;
  }
}

/* Star ratings: the design's accent, and hidden entirely when there are no reviews
   (FR-13 allows only verified reviews, so an all-grey row of stars on every card is
   noise that implies a rating that does not exist). */
body.dmds-page ul.products li.product .star-rating {
  color: var(--dm-accent);
  font-size: 12px;
  /* The card centres its title, price and button; the rating row was the one element
     left at the default left edge, which read as a misalignment on a narrow card. */
  margin: 4px auto;
}
/* `:not([title])` MATCHED NOTHING and so hid nothing: an unrated product still emits
   `<div class="star-rating" title="Not yet rated">`, i.e. the attribute is always
   present — only its VALUE differs. Every card therefore showed a row of empty grey
   stars, implying a rating that does not exist (0 real reviews — see DIAGNOSTIC §3).

   Hide by the ABSENCE of a real average instead of by the title text: a rated card is
   `li.product` carrying a `.star-rating` whose parent also has a rating count link.
   WooCommerce omits `.woocommerce-review-link` and adds no `[class*="rated-"]` when
   there are no reviews, so this stays correct in any locale, unlike matching the
   translated string "Not yet rated". */
body.dmds-page ul.products li.product:not(:has(.woocommerce-review-link)) .star-rating {
  display: none;
}

/* Related / up-sell section headings pick up the display face. */
body.dmds-page section.related > h2,
body.dmds-page section.up-sells > h2,
body.dmds-page .cross-sells > h2 {
  font-size: 40px;
  line-height: 1.05;
  letter-spacing: -0.035em;
  font-weight: 700;
  margin: 0 0 26px;
}

body.dmds-page .woocommerce-result-count {
  font-family: var(--dm-font-mono);
  font-size: 12px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--dm-faint);
  margin: 0 0 18px;
}

/* WooCommerce's own `.woocommerce.columns-3` wrapper carries a default
   `padding-left/right: 32px`, which stacks on top of the section's own 20px inset to
   put the sort bar and product grid at a 52px-from-viewport left edge — 12px further
   in than the filter card, hero heading and promo panel on the same page, which are
   all hand-built at a 40px inset (`padding:40px 32px 0 32px` sections). The two are
   only ~34px apart vertically, so the step was clearly visible as a mis-stacked edge.
   Overriding to 20px lets the existing 20px section padding stand alone, landing the
   grid at the same 40px total the rest of the page uses. */
body.dmds-page .woocommerce.columns-3 {
  padding-left: 20px !important;
  padding-right: 20px !important;
}

body.dmds-page .woocommerce-ordering { margin: 0 0 22px; }
body.dmds-page .woocommerce-ordering select {
  appearance: none;
  -webkit-appearance: none;
  background: var(--dm-card);
  border: 0;
  border-radius: var(--dm-radius-pill);
  padding: 11px 38px 11px 18px;
  font-size: 13px;
  font-weight: 600;
  color: var(--dm-ink);
  box-shadow: var(--dm-shadow-nav);
  cursor: pointer;
}

body.dmds-page nav.woocommerce-pagination { text-align: center; margin-top: 44px; }
body.dmds-page nav.woocommerce-pagination ul { display: inline-flex; gap: 8px; list-style: none; padding: 0; border: 0; }
body.dmds-page nav.woocommerce-pagination li { border: 0; }
body.dmds-page nav.woocommerce-pagination a,
body.dmds-page nav.woocommerce-pagination span.current {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 40px; min-height: 40px; padding: 0 12px;
  border-radius: var(--dm-radius-pill);
  font-size: 13px; font-weight: 600;
  background: var(--dm-card); color: var(--dm-ink);
  box-shadow: var(--dm-shadow-nav);
}
body.dmds-page nav.woocommerce-pagination span.current {
  background: var(--dm-ink); color: #FFFFFF;
}

/* Container width/gutter for the shop archive's own wrapper. */
body.dmds-page .woocommerce { max-width: var(--dm-container-max); margin: 0 auto; padding: 34px var(--dm-container-pad) 0; }
/* ...but NOT when the grid is nested inside a single product page's related section,
   where the surrounding Elementor section already supplies the container. */
body.dmds-page .related .woocommerce,
body.dmds-page .up-sells .woocommerce { padding: 0; }

@media (max-width: 1239px) {
  body.dmds-page ul.products { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Phones keep TWO columns, not one.
   A single column turned the 9-product shop archive into an 8-screen scroll where
   only one mat was ever visible at a time — the shopper could not compare products,
   which is the entire job of an archive page. Two columns fit comfortably: at the
   narrowest supported width (320px) each card is ~134px, and the card's own content
   (image, title, price, button) stays legible because the rules below scale its
   padding and button down to match.

   `minmax(0, 1fr)` rather than `1fr`: a bare `1fr` means `minmax(auto, 1fr)`, whose
   automatic minimum is the content's intrinsic width — a long unbroken product title
   would then refuse to shrink and push the grid wider than the screen. */
@media (max-width: 640px) {
  body.dmds-page ul.products {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 12px;
  }

  /* 18px padding on a ~150px card spends a quarter of its width on empty margin. */
  body.dmds-page ul.products li.product {
    padding: 10px !important;
  }

  /* "Select options" wraps to two lines in a half-width card. Shrinking the type a
     little and trimming the horizontal padding keeps it on one line, which also
     keeps every card's button at the same height. */
  body.dmds-page ul.products li.product a.button {
    font-size: 12.5px;
    padding: 12px 10px;
    width: 100%;
    text-align: center;
    /* 44px is the accessibility floor for a tap target; the smaller type would
       otherwise let the button shrink below it. */
    min-height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  /* Cards in a row are only as tall as their content, so a two-line product title
     ("Contour Black Extended Mat") next to a one-line one ("Blank Slate Wide") left
     their "Select options" buttons on different baselines — visibly ragged across
     every row of the archive.

     Make each card a full-height flex column and push the button to the bottom with
     `margin-top: auto`. `!important` on display/height because both GeneratePress and
     Elementor's product-grid widget set `li.product` themselves with higher
     specificity than a `body.dmds-page ul.products li.product` chain. */
  body.dmds-page ul.products li.product {
    display: flex !important;
    /* !important on the DIRECTION too. Without it the theme's own `li.product`
       rule kept `row`, which laid the card out horizontally: the button became a
       circle floating beside the title instead of a full-width pill beneath it. */
    flex-direction: column !important;
    align-items: stretch !important;
    height: 100% !important;
  }

  /* The button is the last child, so auto margin above it absorbs all slack. */
  body.dmds-page ul.products li.product a.button {
    margin-top: auto !important;
  }
}

/* Below ~360px two product columns stop being readable — the title wraps to three
   lines and the price competes with it for space. Fall back to one column there
   only, which is a genuinely narrow-phone case rather than the whole phone range. */
@media (max-width: 359px) {
  body.dmds-page ul.products { grid-template-columns: minmax(0, 1fr); }
}

/* ============================================================================
   SEARCH RESULTS (FR-3)
   Elementor's archive-posts widget renders `.elementor-posts > .elementor-post`.
   These rules give those entries the SAME card vocabulary as ul.products above,
   because FR-3 requires search results to "respect the same product-card
   conventions as the Shop page".
   ============================================================================ */

body.dmds-page .elementor-posts { gap: 22px; }

body.dmds-page .elementor-posts .elementor-post {
  background: var(--dm-card);
  border-radius: var(--dm-radius-card);
  padding: 18px;
  box-shadow: var(--dm-shadow-card-rest);
  transition: transform .4s var(--dm-ease-standard), box-shadow .4s var(--dm-ease-standard);
  overflow: hidden;
}

@media (hover: hover) and (pointer: fine) {
  body.dmds-page .elementor-posts .elementor-post:hover {
    transform: translateY(-6px);
    box-shadow: var(--dm-shadow-card-hover);
  }
}

body.dmds-page .elementor-posts .elementor-post__thumbnail {
  border-radius: 10px;
  background: var(--dm-inset);
}

body.dmds-page .elementor-posts .elementor-post__title,
body.dmds-page .elementor-posts .elementor-post__title a {
  font-family: var(--dm-font-display);
  font-size: 15px;
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.015em;
  color: var(--dm-ink);
}

body.dmds-page .elementor-posts .elementor-post__excerpt p {
  font-size: 13px;
  line-height: 1.6;
  color: var(--dm-muted);
}

/* Excerpts of product descriptions contain links that would otherwise render in the
   theme's blue — the same bleed already fixed on the product grid price. */
body.dmds-page .elementor-posts .elementor-post__excerpt a { color: var(--dm-muted); }

body.dmds-page .elementor-posts .elementor-post__meta-data {
  font-family: var(--dm-font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  color: var(--dm-faint);
}

body.dmds-page .elementor-pagination .page-numbers {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 40px; min-height: 40px; padding: 0 12px;
  border-radius: var(--dm-radius-pill);
  background: var(--dm-card); color: var(--dm-ink);
  box-shadow: var(--dm-shadow-nav);
  font-size: 13px; font-weight: 600; margin: 0 4px;
}
body.dmds-page .elementor-pagination .page-numbers.current {
  background: var(--dm-ink); color: #FFFFFF;
}

/* The Shop archive's own pagination is WooCommerce's NATIVE
   `nav.woocommerce-pagination ul.page-numbers`, a different component from the
   Elementor "Posts" widget pagination styled above (`.elementor-pagination`) — the
   selector above never matches it, so it rendered fully unstyled: GeneratePress's
   default `border-right: 1px solid #CFC8D8` on every `li` (including the last, leaving
   a dangling hairline after the final arrow with nothing to its right), three
   different link widths under the same border-radius (min-width 17px + padding
   giving "1"=40px/circle, "2"=27px/narrow-oval, "→"=34px/oval — reading as
   mismatched, distorted shapes), and the active page in the theme's default lavender
   (#E9E6ED bg / #816F98 text) — low-contrast and off the site's cream/black/orange
   palette. Same pill treatment as the Elementor variant above, so both pagination
   components read as one design language. */
body.dmds-page nav.woocommerce-pagination ul.page-numbers {
  display: flex; flex-wrap: wrap; gap: 8px; list-style: none; padding: 0; margin: 24px 0 0;
}
body.dmds-page nav.woocommerce-pagination ul.page-numbers li {
  border-right: none !important;
}
body.dmds-page nav.woocommerce-pagination .page-numbers {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 44px; min-height: 44px; padding: 0 14px;
  border-radius: var(--dm-radius-pill);
  background: var(--dm-card); color: var(--dm-ink);
  box-shadow: var(--dm-shadow-nav);
  font-size: 13px; font-weight: 600;
}
body.dmds-page nav.woocommerce-pagination .page-numbers.current {
  background: var(--dm-ink); color: #FFFFFF;
}
@media (hover: hover) and (pointer: fine) {
  body.dmds-page nav.woocommerce-pagination a.page-numbers:hover {
    background: var(--dm-inset);
  }
}

/* Search-card details the archive-posts widget adds that the design does not use.
   The date/comment-count meta line is meaningless on a PRODUCT result ("No Comments"
   under a desk mat), and the Read More link inherits GeneratePress's green, which
   appears nowhere in the design system's palette. */
body.dmds-page .elementor-posts .elementor-post__meta-data { display: none; }

body.dmds-page .elementor-posts .elementor-post__read-more {
  color: var(--dm-accent);
  font-family: var(--dm-font-display);
  font-size: 13px;
  font-weight: 600;
}
@media (hover: hover) and (pointer: fine) {
  body.dmds-page .elementor-posts .elementor-post__read-more:hover { color: var(--dm-accent-hover); }
}

/* ============================================================================
   MOBILE OVERFLOW HARDENING (AD-9)

   Every rule below fixes a box that measurably exceeded a 390px viewport. They are
   kept together, and each one names what it fixes, because this class of bug is
   invisible to the suite's `scrollX === 0` assertion: `overflow-x: clip` on
   html/body suppresses the SCROLL while leaving the layout box over-wide, so the
   document still lays out at 711px and the page still looks wrong. Screenshot width
   is the honest signal — see checkDocumentWidth in visual-check.mjs.
   ============================================================================ */

/* WooCommerce's zoom plugin injects `img.zoomImg` at the natural size of the
   full-resolution image (447px here) and positions it over the gallery. Zoom-on-hover
   is meaningless on a touch device and the overlay overflowed the product page, so it
   is constrained everywhere and suppressed outright where there is no fine pointer. */
body.dmds-page .woocommerce-product-gallery img.zoomImg {
  max-width: 100% !important;
}
@media not all and (hover: hover) and (pointer: fine) {
  body.dmds-page .woocommerce-product-gallery img.zoomImg {
    display: none !important;
  }
}

/* Media inside any hand-built (non-ported) page. The ported pages got this treatment
   at line ~404; the homepage, shop, product, contact and search templates never did.

   EXCLUDES `img[style*="height:100%"]` — the SAME reason as the `.dmds-ported` version
   of this rule: several fixed-height `overflow:hidden` photo wells (Collections' four
   cards among them) rely on their child img's inline `height:100%` for object-fit:cover
   to fill the box. This rule is on `body.dmds-page`, not scoped to `.dmds-ported`, so
   it ALSO matched those same images on ported pages and silently re-introduced the
   exact "empty band under the photo" bug the `.dmds-ported` exclusion was written to
   fix — caught because `body.dmds-page` applies everywhere and both rules have equal
   `!important`-free weight, with this one appearing later in the cascade. Two of
   Collections' four cards still showed 77-88px gaps until this rule was corrected too. */
@media (max-width: 1239px) {
  body.dmds-page img:not([style*="height:100%"]),
  body.dmds-page video {
    max-width: 100%;
    height: auto;
  }
}

/* ---- Product page, mobile (T5 / FR-4) ----
   Three defects that only appear once the two columns stack. */
@media (max-width: 1239px) {
  /* 1. The details column keeps its desktop 46px inter-column gutter as padding-left
        after the columns stack, so on a 390px viewport the title, price, pills and
        add-to-cart were confined to 264px with 86px of dead space on the left. The
        gutter exists to separate two side-by-side columns; once stacked it is wrong. */
  body.single-product .elementor-widget-wrap.elementor-element-populated {
    padding-left: 20px !important;
    padding-right: 20px !important;
  }

  /* 2. WooCommerce's variations table is inside `form.variations_form`, which the
        product template lays out as `display:flex; flex-direction:row`. The table
        therefore became a flex ITEM: it shrank to its content width and centred, which
        is what made the size/thickness/edge pills stagger down the page in a ragged
        diagonal instead of forming left-aligned wrapping rows. Stack the form. */
  body.single-product form.variations_form {
    display: block !important;
  }
  body.single-product table.variations,
  body.single-product table.variations tbody,
  body.single-product table.variations tr,
  body.single-product table.variations td,
  body.single-product table.variations th {
    display: block !important;
    width: 100% !important;
  }

  /* 3. `th.label` inherits the theme's `text-align:center` for table headers, so the
        SIZE / THICKNESS / EDGE eyebrows sat centred while the title, price and body
        copy beside them were left-aligned. */
  body.single-product table.variations th.label,
  body.single-product table.variations td.value {
    text-align: left !important;
  }
  body.single-product .dmds-pill-row {
    justify-content: flex-start !important;
  }
}

/* ============================================================================
   SITE-WIDE MOBILE TAP TARGETS & TOUCH ERGONOMICS

   Found by auditing all 17 pages at 390x844. The layout work above already gets
   every page to zero horizontal overflow; what remained were controls that are
   laid out correctly but are too small to hit reliably with a finger, plus a few
   touch-only behaviours that a mouse never exposes.

   44x44 CSS px is the accessibility floor for a touch target (WCAG 2.5.5 / the
   platform HIG both land there). Below that, hit rate drops sharply and users
   double-tap, which on a link means navigating twice.

   Breakpoint is 1239px, not 767px. The first cut of this block stopped at 767 and
   left the footer's one-character "X" social link at 9px wide on every tablet — the
   ported pages' own layout breakpoint is 1239px, so a control that needs finger-sized
   treatment needs it across that whole range, not just on phones.
   ============================================================================ */

@media (max-width: 1239px) {

  /* Footer social links ("Instagram", "YouTube", "X", "TikTok").
     These are 45px tall already, but "X" is a ONE-CHARACTER label — it measured
     9px wide, a target no thumb can hit without magnifying. The label cannot be
     padded blindly (the row is a flex line and the others are already wide
     enough), so give every one of them a minimum width and centre the text.

     `!important` is required, not preferred: the ported-page rule
     `.dmds-ported [style*="display:flex"] > * { min-width: 0 !important }` (added to
     stop long strings widening the page) matches these links, and an !important
     declaration can only be beaten by another !important one. Scoped to a single
     property on a single class, so it does not re-open the widening it guards.

     NOTE: the unconditional copy of this rule sits OUTSIDE the media query, below —
     a 9px-wide link is a bad target for a mouse too, not only for a thumb. */
  body.dmds-page .dmds-footer-link {
    min-width: 44px !important;
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  /* The stacked footer navigation columns ("All Desk Mats", "Collections", …).
     These are ordinary text links sitting on ~20px line boxes; on a dense list
     the adjacent link is easy to hit by mistake. Padding the line box rather than
     the text keeps the visual rhythm while separating the hit areas. */
  body.dmds-page .dmds-footer-link-ink {
    display: inline-block;
    padding-block: 11px;
    min-height: 44px;
  }

  /* WooCommerce's own form furniture on cart / checkout / my-account.
     The "Remember me" checkbox and the password-reveal eye both render at 22x22 —
     half the usable minimum. Scale the control itself rather than wrapping it, so
     WooCommerce's markup and its JS handlers are untouched. */
  body.dmds-page input[type="checkbox"],
  body.dmds-page input[type="radio"] {
    width: 22px;
    height: 22px;
    /* The visual box stays 22px; the HIT area grows via padding on the label's
       side, so the design does not suddenly sprout oversized checkboxes. */
    outline-offset: 4px;
  }

  body.dmds-page .woocommerce-form-login__rememberme,
  body.dmds-page .woocommerce-form__label-for-checkbox {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    min-height: 44px;
  }

  /* The eye icon that toggles password visibility. It is absolutely positioned
     inside the field, so it can be enlarged without disturbing the layout. */
  body.dmds-page .show-password-input {
    width: 44px;
    height: 44px;
    background-position: center;
    top: 50%;
    transform: translateY(-50%);
  }

  /* Legal-page contents lists (Terms §1-8, Privacy §1-6) are anchor links stacked
     at 20px line height with no separation — nine consecutive 20px targets is the
     densest tap surface on the site. */
  body.dmds-page [class*="-h1"],
  body.dmds-page [class*="-h2"],
  body.dmds-page [class*="-h3"],
  body.dmds-page [class*="-h4"],
  body.dmds-page [class*="-h5"],
  body.dmds-page [class*="-h6"],
  body.dmds-page [class*="-h7"],
  body.dmds-page [class*="-h8"] {
    min-height: 44px;
  }

  /* Anything the design draws as a pill/button must be thumb-sized, whatever
     element it happens to be built from. */
  body.dmds-page a.button,
  body.dmds-page button:not(.show-password-input):not([class*="qty"]),
  body.dmds-page input[type="submit"],
  body.dmds-page .dmds-pill {
    min-height: 44px;
  }

  /* iOS zooms the whole page when a focused input's font-size is under 16px, and
     never zooms back out — the single most jarring mobile-form failure there is.
     The site's inputs are 17px, but WooCommerce's quantity and coupon fields
     inherit smaller sizes on some templates, so pin the floor explicitly. */
  body.dmds-page input[type="text"],
  body.dmds-page input[type="email"],
  body.dmds-page input[type="tel"],
  body.dmds-page input[type="number"],
  body.dmds-page input[type="password"],
  body.dmds-page input[type="search"],
  body.dmds-page select,
  body.dmds-page textarea {
    font-size: max(16px, 1em);
  }
}

/* Touch devices at ANY width — a tablet is not narrow but has no cursor.
   Scoped by input type rather than viewport so a 1024px tablet is covered too. */
@media (pointer: coarse) {
  /* Kill the 300ms tap delay and stop double-tap-to-zoom swallowing the first tap
     on controls. `manipulation` keeps pinch-zoom, which must never be disabled. */
  body.dmds-page a,
  body.dmds-page button,
  body.dmds-page [role="button"],
  body.dmds-page input[type="checkbox"],
  body.dmds-page input[type="radio"] {
    touch-action: manipulation;
  }
}

/* The footer's one-character "X" social link measured 9px wide at EVERY width — the
   media-query copy above only rescues it below 1240px. A 9px target is awkward with a
   mouse and impossible with a thumb, and the four social links read as an uneven row
   when one of them is a tenth the width of its neighbours. Applying the same minimum
   unconditionally also keeps the row evenly spaced on desktop, which is what the
   design's own layout implies. */
body.dmds-page .dmds-footer-link {
  min-width: 44px !important;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
