/* ==========================================================================
   Planet Health UK — design system + product pages
   "Lab report, warmly typeset."
   Loaded after Storefront; scoped overrides only.
   ========================================================================== */

/* ---------- Fonts ---------- */
@font-face{
  font-family:'Manrope PH';
  src:url(../fonts/manrope-var.woff2) format('woff2');
  font-weight:400 800; font-style:normal; font-display:swap;
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
                U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,
                U+2212,U+2215,U+FEFF,U+FFFD;
}
@font-face{
  font-family:'Inter PH';
  src:url(../fonts/inter-var.woff2) format('woff2');
  font-weight:100 900; font-style:normal; font-display:swap;
  unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,
                U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,
                U+2212,U+2215,U+FEFF,U+FFFD;
}

/* ---------- Tokens ---------- */
:root{
  --white:#FFFFFF;
  --ink:#1A1A1A;
  --green:#3CBD9F;        /* decorative only — 2.2:1 on white */
  --green-deep:#2C8B75;   /* every text-bearing / interactive green */
  --green-deeper:#24705E;
  --wash:#F6F9F8;
  --wash-green:#F0F8F5;
  --hairline:#EDF1F0;
  --muted:#5B6663;

  --font-display:'Manrope PH',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
  --font-body:'Inter PH',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;

  --ease-out:cubic-bezier(0.22,1,0.36,1);
  --ease-inout:cubic-bezier(0.65,0,0.35,1);
  --ease-spring:cubic-bezier(0.34,1.56,0.64,1);
  --dur-fast:150ms; --dur-base:250ms; --dur-slow:400ms; --dur-reveal:550ms;

  --s1:4px;--s2:8px;--s3:12px;--s4:16px;--s5:24px;--s6:32px;
  --s7:48px;--s8:64px;--s9:96px;--s10:128px;

  --container:1280px;
  --prose:68ch;
  --radius:16px;
  --radius-sm:12px;
  --header-offset:96px;

  /* Header geometry. --hdr-shown is the height the header occupies once
     condensed; --subnav-top mirrors it and is zeroed by JS when the header
     retreats. --wp-bar is the WordPress admin bar's fixed height. */
  --hdr-h:72px;
  --hdr-shown:60px;
  --subnav-top:var(--hdr-shown);
  --wp-bar:0px;
}
html.ph-hdr-hidden{--subnav-top:0px;}
body.admin-bar{--wp-bar:32px;}
/* Below 782px WordPress switches the admin bar to position:absolute, so it
   scrolls away and must not be offset against. */
@media screen and (max-width:782px){
  body.admin-bar{--wp-bar:0px;}
}

/* ---------- Base ---------- */
.ph-scope,
body.single-product,
body.post-type-archive-product,
body.tax-product_cat{
  font-family:var(--font-body);
  color:var(--ink);
  background:var(--white);
  -webkit-font-smoothing:antialiased;
}
.ph-scope h1,.ph-scope h2,.ph-scope h3,.ph-scope h4,
.ph-scope .ph-display,.ph-scope button,.ph-scope .button{
  font-family:var(--font-display);
  letter-spacing:-0.01em;
}
.ph-scope *,.ph-scope *::before,.ph-scope *::after{box-sizing:border-box;}
.ph-scope :focus-visible{outline:2px solid var(--green-deep);outline-offset:2px;border-radius:4px;}

/* Storefront sets body{word-wrap:break-word}, which inherits into every
   descendant and turns any too-narrow column into mid-word wreckage —
   "Thermomet / ers" — instead of an overflow anyone would notice. We author
   every string inside .ph-scope, so an overflowing one is a bug we want loud
   at build time rather than camouflaged in production. Unbounded strings opt
   back in explicitly; see the contact email links below. */
.ph-scope{overflow-wrap:normal;}

.ph-container{max-width:var(--container);margin:0 auto;padding:0 var(--s5);}
.ph-prose{max-width:var(--prose);}
.ph-tnum{font-variant-numeric:tabular-nums;font-feature-settings:'tnum' 1;}

.ph-micro{font-size:12px;letter-spacing:0.08em;text-transform:uppercase;font-weight:600;color:var(--muted);}
.ph-small{font-size:14px;}
.ph-body{font-size:16px;line-height:1.6;}
.ph-body-lg{font-size:18px;line-height:1.7;}

.ph-scope h1{font-size:32px;line-height:1.2;font-weight:700;margin:0 0 var(--s3);}
.ph-scope h2{font-size:26px;line-height:1.25;font-weight:700;margin:0 0 var(--s4);}
.ph-scope h3{font-size:18px;line-height:1.35;font-weight:600;margin:0 0 var(--s3);}

/* ---------- Buttons ---------- */
.ph-btn{
  display:inline-flex;align-items:center;justify-content:center;gap:var(--s2);
  font-family:var(--font-display);font-size:16px;font-weight:600;line-height:1;
  padding:16px var(--s5);border:0;border-radius:10px;cursor:pointer;
  background:var(--green-deep);color:#fff;text-decoration:none;
  transition:background var(--dur-fast) var(--ease-out),
             transform var(--dur-fast) var(--ease-out),
             box-shadow var(--dur-fast) var(--ease-out);
}
.ph-btn:hover{background:var(--green-deeper);transform:translateY(-1px);color:#fff;}
.ph-btn:active{transform:scale(.98);transition-duration:100ms;}
.ph-btn[disabled],.ph-btn.is-disabled{background:#C9D3D0;cursor:not-allowed;transform:none;}
.ph-btn--block{width:100%;}
.ph-btn--ghost{background:transparent;color:var(--green-deep);border:1px solid var(--hairline);}
.ph-btn--ghost:hover{background:var(--wash-green);color:var(--green-deeper);}
.ph-btn--link{background:none;padding:0;color:var(--green-deep);font-size:14px;font-weight:600;text-decoration:none;}
.ph-btn--link:hover{background:none;transform:none;text-decoration:underline;}

/* ---------- Scroll reveal ---------- */
/* Scoped to html.js, never bare. The hidden state is only safe to assert once
   something has proved it can be undone: the flag is written by an inline
   script in wp_head (ph_reveal_flag) and withdrawn again if ph.js has not
   claimed the page shortly after load. Unscoped, a single failed script request
   blanks every revealed section on the site with nothing in the console to
   say so. See inc/ph-product.php. */
.js .ph-reveal{opacity:0;transform:translateY(24px);
  transition:opacity var(--dur-reveal) var(--ease-out),transform var(--dur-reveal) var(--ease-out);}
.ph-reveal.is-in{opacity:1;transform:none;}

/* ==========================================================================
   SINGLE PRODUCT
   ========================================================================== */
.ph-breadcrumb{padding:var(--s5) 0 var(--s4);font-size:13px;color:var(--muted);}
.ph-breadcrumb a{color:var(--muted);text-decoration:none;}
.ph-breadcrumb a:hover{color:var(--green-deep);text-decoration:underline;}
.ph-breadcrumb span[aria-current]{color:var(--ink);}

.ph-hero{display:grid;grid-template-columns:7fr 5fr;gap:var(--s8);
  align-items:start;padding-bottom:var(--s6);}

/* --- Gallery --- */
.ph-gallery{position:relative;}
.ph-gallery__stage{
  position:relative;aspect-ratio:1/1;background:#fff;
  border:1px solid var(--hairline);border-radius:var(--radius);overflow:hidden;
}
.ph-gallery__stage img{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:contain;padding:12%;
  opacity:0;transform:scale(1.03);
  transition:opacity var(--dur-base) var(--ease-out),transform var(--dur-base) var(--ease-out);
}
.ph-gallery__stage img.is-active{opacity:1;transform:none;}
.ph-gallery__zoom{
  position:absolute;right:var(--s3);bottom:var(--s3);z-index:2;
  width:40px;height:40px;border-radius:50%;border:1px solid var(--hairline);
  background:rgba(255,255,255,.9);color:var(--muted);cursor:zoom-in;
  display:grid;place-items:center;padding:0;
  transition:background var(--dur-fast) var(--ease-out),color var(--dur-fast) var(--ease-out),transform var(--dur-fast) var(--ease-out);
}
.ph-gallery__zoom:hover{background:var(--wash-green);color:var(--green-deep);transform:scale(1.08);}
.ph-gallery__zoom svg{display:block;}
.ph-gallery__thumbs{display:flex;gap:var(--s2);margin-top:var(--s3);flex-wrap:wrap;}
.ph-gallery__thumb{
  width:64px;height:64px;padding:0;background:#fff;cursor:pointer;
  border:1px solid var(--hairline);border-radius:8px;overflow:hidden;
  transition:border-color var(--dur-fast) var(--ease-out);
}
.ph-gallery__thumb img{width:100%;height:100%;object-fit:contain;padding:6px;display:block;}
.ph-gallery__thumb[aria-current="true"]{border:2px solid var(--green-deep);}

/* No-image placeholder */
.ph-noimg{
  aspect-ratio:1/1;background:var(--wash-green);border-radius:var(--radius);
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--s4);
}
.ph-noimg svg{width:96px;height:96px;color:var(--green);opacity:.4;}
.ph-noimg figcaption{font-size:14px;color:var(--muted);}
/* Card-sized placeholder. 96px was measured against a full-width product
   figure; inside a 280–360px card it crowds the frame, and the draw is not
   spent here at all — six of them animating across one grid is noise, not
   arrival. ph.js only observes figure.ph-noimg, so these stay still. */
.ph-card .ph-noimg{position:absolute;inset:0;border-radius:0;aspect-ratio:auto;}
.ph-card .ph-noimg svg{width:64px;height:64px;}

/* Glyph-draw. The base state above is the finished drawing — no dash
   properties at all — so a visitor with reduced motion, a visitor whose JS
   failed, and a printed page all get a complete glyph. The animation exists
   only under .ph-draw, which ph.js adds on intersection and only when
   (prefers-reduced-motion: no-preference) actually matches. Failure degrades
   to drawn, which is the right direction.

   Every element is pathLength="100", so one keyframe pair covers all of them
   at a uniform rate. The child selector is `svg > *` rather than [pathLength]
   deliberately: attribute-name matching is case-sensitive inside foreign
   content, and pathLength survives HTML parsing with its capital L intact, so
   the attribute selector is a coin-flip across engines while this is not.

   Delays overlap rather than queue — 80ms into a 400ms draw — so a four-path
   glyph completes at 640ms as one continuous gesture instead of four separate
   ones. animation, not transition: nothing here waits on transitionend. */
.ph-draw svg > *{
  stroke-dasharray:100;
  animation:ph-stroke var(--dur-slow) var(--ease-inout) both;
}
.ph-draw svg > *:nth-child(2){animation-delay:80ms;}
.ph-draw svg > *:nth-child(3){animation-delay:160ms;}
.ph-draw svg > *:nth-child(4){animation-delay:240ms;}
@keyframes ph-stroke{from{stroke-dashoffset:100;}to{stroke-dashoffset:0;}}

/* The glyph draws at .9 and settles to .4. At 96px in --green on --wash-green,
   40% opacity has too little presence for the arrival to be perceived at all —
   the stroke reaches the eye as a fade, not as a line being drawn. Raising the
   drawing opacity and settling it afterwards buys the gesture its visibility
   without changing the resting state, which stays exactly as designed.

   640ms is the worst-case end of the delay ladder (240ms delay + 400ms draw).
   Glyphs with fewer than four paths finish sooner and hold at .9 for the
   remainder, which is imperceptible and cheaper than four bespoke delays.
   Under reduced motion the global clamp collapses this to an instant landing
   at .4 — the correct end state, for free. */
.ph-draw svg{
  animation:ph-settle var(--dur-base) var(--ease-inout) 640ms both;
}
@keyframes ph-settle{from{opacity:.9;}to{opacity:.4;}}

/* --- Buy column --- */
.ph-buy{position:sticky;top:var(--header-offset);}
.ph-buy__eyebrow{margin-bottom:var(--s2);}
.ph-buy__intro{font-size:17px;line-height:1.6;color:var(--muted);margin:0 0 var(--s5);}
.ph-chips{display:flex;flex-wrap:wrap;gap:var(--s2);margin:0 0 var(--s5);padding:0;list-style:none;}
.ph-chip{
  display:inline-flex;gap:6px;align-items:baseline;
  background:var(--wash);border:1px solid var(--hairline);border-radius:999px;
  padding:7px 14px;font-size:13px;color:var(--ink);
}
.ph-chip b{font-weight:600;color:var(--muted);font-size:11px;letter-spacing:.06em;text-transform:uppercase;}
.ph-price{font-size:28px;font-weight:700;line-height:1;margin:0 0 var(--s2);font-family:var(--font-display);}
.ph-price small{font-size:13px;font-weight:500;color:var(--muted);margin-left:var(--s2);}
.ph-price--tba{font-size:18px;font-weight:600;color:var(--muted);}
.ph-stock{font-size:14px;color:var(--muted);margin:0 0 var(--s5);}

.ph-cart{display:flex;gap:var(--s3);align-items:stretch;margin-bottom:var(--s4);}
.ph-qty{display:flex;align-items:center;border:1px solid var(--hairline);border-radius:10px;overflow:hidden;}
.ph-qty button{width:40px;border:0;background:#fff;font-size:18px;cursor:pointer;color:var(--muted);
  transition:background var(--dur-fast) var(--ease-out);}
.ph-qty button:hover{background:var(--wash);}
.ph-qty input{width:44px;border:0;text-align:center;font:inherit;font-variant-numeric:tabular-nums;
  -moz-appearance:textfield;}
.ph-qty input::-webkit-outer-spin-button,.ph-qty input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0;}

/* Add-to-basket state machine */
.ph-atc{position:relative;flex:1;overflow:hidden;}
.ph-atc__label{transition:opacity var(--dur-fast) var(--ease-out);}
.ph-atc.is-loading .ph-atc__label{opacity:0;}
.ph-atc.is-loading::after{
  content:'';position:absolute;inset:0;margin:auto;width:6px;height:6px;border-radius:50%;
  background:#fff;box-shadow:-12px 0 0 #fff,12px 0 0 #fff;animation:ph-dots 900ms infinite var(--ease-inout);
}
@keyframes ph-dots{0%,80%,100%{opacity:.35}40%{opacity:1}}
.ph-atc.is-success{background:var(--green-deeper);}
.ph-atc.is-success .ph-atc__label::before{content:'';}

.ph-reassure{display:flex;flex-wrap:wrap;gap:var(--s4);padding-top:var(--s4);
  border-top:1px solid var(--hairline);font-size:13px;color:var(--muted);}
.ph-reassure span{display:inline-flex;align-items:center;gap:6px;}
.ph-pay{display:flex;gap:var(--s2);margin-top:var(--s4);opacity:.55;filter:grayscale(1);}
.ph-pay img{height:20px;}

/* --- Anchor sub-nav --- */
/* Storefront clips two ancestors of the sub-nav: `.site` gets overflow-x:hidden
   from style.css, and `.single-product div.product` gets overflow:hidden from
   woocommerce.css. Either one is enough to turn that ancestor into a scroll
   container, which silently disables position:sticky on everything inside it —
   the nav just scrolls away. `clip` gives identical horizontal containment
   without establishing a scroll container, so sticky survives. Both must be
   overridden; fixing only one leaves the other still breaking it. */
.ph-scope .site,
.ph-scope.single-product div.product{
  overflow-x:clip;
  overflow-y:visible;
}

/* Sits directly beneath the global header. --subnav-top carries the height of
   the header in its current state (see the header block below); when the
   header retreats off-screen the variable drops to 0 and the sub-nav rises to
   take its place on the identical duration and easing, so the two read as one
   moving unit rather than two bars racing each other. --wp-bar folds in the
   WordPress admin bar for logged-in users. */
.ph-subnav{
  position:sticky;top:calc(var(--wp-bar) + var(--subnav-top));z-index:80;height:48px;
  background:rgba(255,255,255,.92);
  backdrop-filter:saturate(180%) blur(12px);
  border-top:1px solid var(--hairline);border-bottom:1px solid var(--hairline);
  transition:top var(--dur-base) var(--ease-inout);
}
.ph-subnav__inner{display:flex;gap:var(--s6);height:100%;align-items:center;overflow-x:auto;
  scrollbar-width:none;}
.ph-subnav__inner::-webkit-scrollbar{display:none;}
.ph-subnav a{
  position:relative;flex:none;font-size:14px;font-weight:500;color:var(--muted);
  text-decoration:none;padding:14px 0;transition:color var(--dur-fast) var(--ease-out);
}
.ph-subnav a::after{
  content:'';position:absolute;left:0;right:0;bottom:0;height:2px;background:var(--green-deep);
  transform:scaleX(0);transform-origin:left;transition:transform var(--dur-base) var(--ease-out);
}
.ph-subnav a:hover{color:var(--ink);}
.ph-subnav a.is-active{color:var(--ink);font-weight:600;}
.ph-subnav a.is-active::after{transform:scaleX(1);}

/* --- Sections --- */
.ph-section{padding:var(--s9) 0;scroll-margin-top:calc(48px + var(--s5));}
.ph-section--wash{background:var(--wash-green);}
.ph-section__head{margin-bottom:var(--s6);}
.ph-section__head h2{margin:0;}

.ph-overview p{font-size:18px;line-height:1.7;max-width:var(--prose);margin:0 0 var(--s4);}

.ph-features{display:grid;grid-template-columns:1fr 1fr;gap:var(--s4) var(--s7);
  list-style:none;margin:0;padding:0;max-width:960px;}
.ph-features li{position:relative;padding-left:32px;font-size:16px;line-height:1.55;}
.ph-features li::before{
  content:'';position:absolute;left:0;top:2px;width:20px;height:20px;border-radius:50%;
  background:var(--green-deep) url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E") center/12px no-repeat;
}

.ph-specs{max-width:800px;}
.ph-specs__row{
  display:grid;grid-template-columns:40% 1fr;gap:var(--s5);align-items:center;
  min-height:50px;padding:var(--s3) 0;border-bottom:1px solid var(--hairline);
}
.ph-specs__row:first-child{border-top:1px solid var(--hairline);}
.ph-specs__k{font-size:14px;color:var(--muted);}
.ph-specs__v{font-size:15px;font-weight:500;font-variant-numeric:tabular-nums;}
.ph-specs__row.is-hidden{display:none;}
.ph-specs__more{margin-top:var(--s5);}

.ph-warranty{display:flex;gap:var(--s4);align-items:flex-start;max-width:800px;
  background:var(--wash);border:1px solid var(--hairline);border-radius:var(--radius);padding:var(--s5);}
.ph-warranty svg{flex:none;width:24px;height:24px;color:var(--green-deep);}
.ph-warranty p{margin:0;font-size:16px;line-height:1.6;}

/* Glossary tooltip */
.ph-term{border-bottom:1px dotted var(--muted);cursor:help;}
.ph-tip{
  position:absolute;z-index:60;max-width:280px;background:var(--ink);color:#fff;
  font-size:13px;line-height:1.5;padding:10px 12px;border-radius:8px;pointer-events:none;
  opacity:0;transform:translateY(4px);
  transition:opacity var(--dur-fast) var(--ease-out),transform var(--dur-fast) var(--ease-out);
}
.ph-tip.is-in{opacity:1;transform:none;}

/* Certification strip */
.ph-certs{display:flex;gap:var(--s7);align-items:center;flex-wrap:wrap;
  padding:var(--s7) 0;border-top:1px solid var(--hairline);}
.ph-certs img{height:40px;filter:grayscale(1);opacity:.6;
  transition:opacity var(--dur-base) var(--ease-out),filter var(--dur-base) var(--ease-out);}
.ph-certs img:hover{opacity:1;filter:grayscale(0);}

/* ==========================================================================
   PRODUCT CARDS / SHOP GRID
   ========================================================================== */
/* Two columns on a phone, not one: at 360px a single-column catalogue is a
   scroll marathon, and the card survives being half-width because its text is
   already clamped to two lines. Four only arrives at 1440, where the fourth
   column stops the grid drifting into a wide-gutter layout. */
.ph-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:var(--s4);
  max-width:var(--container);margin:0 auto;padding:0;list-style:none;}
@media (min-width:768px){.ph-grid{grid-template-columns:repeat(3,1fr);gap:var(--s5);}}
@media (min-width:1440px){.ph-grid{grid-template-columns:repeat(4,1fr);}}
.ph-card{position:relative;}
.ph-card__link{display:block;text-decoration:none;color:inherit;
  transition:transform var(--dur-slow) var(--ease-out),box-shadow var(--dur-slow) var(--ease-out);}
.ph-card__link:hover{transform:translateY(-4px);box-shadow:0 8px 24px rgba(26,26,26,.08);}
.ph-card__frame{display:block;position:relative;aspect-ratio:1/1;background:var(--white);overflow:hidden;
  border:1px solid var(--hairline);border-radius:var(--radius-sm);}
.ph-card__frame img{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;padding:0;
  transition:transform var(--dur-slow) var(--ease-out),opacity var(--dur-base) var(--ease-out);}
.ph-card__frame img.ph-card__alt{opacity:0;}
.ph-card__link:hover .ph-card__frame img{transform:scale(1.04);}
.ph-card__link:hover .ph-card__alt{opacity:1;}
.ph-card__eyebrow{display:block;margin:var(--s3) 0 var(--s1);}
.ph-card__title{font-family:var(--font-display);font-size:16px;line-height:1.35;font-weight:600;
  margin:0 0 var(--s1);display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
.ph-card__spec{display:block;font-size:13px;line-height:1.4;color:var(--muted);margin:0 0 var(--s2);}
.ph-card__price{display:block;font-size:15px;font-weight:700;font-variant-numeric:tabular-nums;margin:0;}
.ph-card__pill{position:absolute;top:var(--s3);left:var(--s3);z-index:2;
  background:#fff;border:1px solid #E3E8E6;color:var(--ink);
  font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;
  padding:5px 10px;border-radius:999px;}
/* The action layer overlays the image square exactly — left:0/right:0 with a
   1:1 aspect ratio makes its height equal the card width, which is the frame's
   height, so the buttons land on the frame's bottom edge without anyone having
   to know how tall the text below it grew. Padding lives inside; insetting the
   box instead would shorten it and float the buttons above the edge. */
.ph-card__actions{position:absolute;left:0;right:0;top:0;aspect-ratio:1/1;z-index:2;
  display:flex;flex-direction:column;justify-content:flex-end;align-items:stretch;
  gap:var(--s2);padding:var(--s3);pointer-events:none;}
.ph-card__actions > *{pointer-events:auto;}
.ph-card__quickadd{font-size:14px;padding:11px var(--s4);}
/* Demoted from a bordered pill to a ghost utility. Compare is a power move a
   minority make; as a bordered control it competed with the add button on
   every card in the grid at once. Muted text on --wash still clears AA, and
   the 12% image padding keeps it off the photograph. */
.ph-card__compare{background:none;border:0;padding:2px 0;cursor:pointer;
  font-family:var(--font-body);font-size:12px;font-weight:600;color:var(--muted);
  text-align:center;transition:color var(--dur-fast) var(--ease-out);}
.ph-card__compare:hover{color:var(--green-deep);}
/* At the four-product cap the same-category buttons go dead rather than
   silently doing nothing on click. */
.ph-card__compare[disabled]{opacity:.45;cursor:not-allowed;}
.ph-card__compare[disabled]:hover{color:var(--muted);}
.ph-card__ask{align-self:center;}
@media (hover:hover){
  .ph-card__actions > *{opacity:0;transform:translateY(6px);
    transition:opacity var(--dur-base) var(--ease-out),transform var(--dur-base) var(--ease-out),color var(--dur-fast) var(--ease-out);}
  .ph-card:hover .ph-card__actions > *,
  .ph-card:focus-within .ph-card__actions > *{opacity:1;transform:none;}
}
/* Compare is not a phone workflow — three-up columns do not exist on a 360px
   screen — so it is removed rather than hidden behind a gesture, and the add
   button stops pretending to be a hover affordance. */
@media (hover:none){
  .ph-card__compare{display:none;}
  .ph-card__actions{align-items:flex-end;}
  .ph-card__quickadd{width:auto;padding:9px 14px;font-size:13px;border-radius:999px;}
}

/* Related row */
.ph-related .ph-grid{gap:var(--s5);}

/* ==========================================================================
   COMPARE TRAY
   ========================================================================== */
.ph-tray{
  position:fixed;left:0;right:0;bottom:0;z-index:70;background:#fff;
  border-top:1px solid var(--hairline);box-shadow:0 -8px 24px rgba(26,26,26,.06);
  transform:translateY(110%);transition:transform var(--dur-base) var(--ease-inout);
}
.ph-tray.is-open{transform:none;}
.ph-tray__inner{display:flex;align-items:center;gap:var(--s4);padding:var(--s3) var(--s5);
  max-width:var(--container);margin:0 auto;}
.ph-tray__items{display:flex;gap:var(--s2);flex:1;overflow-x:auto;}
.ph-tray__item{display:flex;align-items:center;gap:var(--s2);background:var(--wash);
  border-radius:8px;padding:6px 10px;font-size:13px;white-space:nowrap;}
.ph-tray__item button{border:0;background:none;cursor:pointer;color:var(--muted);font-size:15px;line-height:1;}

/* ==========================================================================
   LIGHTBOX
   ========================================================================== */
.ph-lightbox{
  position:fixed;inset:0;z-index:100;background:rgba(255,255,255,.97);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);display:grid;place-items:center;
  opacity:0;visibility:hidden;transition:opacity var(--dur-base) var(--ease-out),visibility var(--dur-base);
}
.ph-lightbox.is-open{opacity:1;visibility:visible;}
.ph-lightbox img{max-width:min(90vw,900px);max-height:86vh;object-fit:contain;}
.ph-lightbox__close,.ph-lightbox__nav{
  position:absolute;width:48px;height:48px;border-radius:50%;border:1px solid var(--hairline);
  background:#fff;cursor:pointer;display:grid;place-items:center;font-size:20px;color:var(--ink);
}
.ph-lightbox__close{top:var(--s5);right:var(--s5);}
.ph-lightbox__nav--prev{left:var(--s5);top:50%;transform:translateY(-50%);}
.ph-lightbox__nav--next{right:var(--s5);top:50%;transform:translateY(-50%);}

/* ==========================================================================
   TOAST + CART BADGE
   ========================================================================== */
.ph-toast{
  position:fixed;left:var(--s5);bottom:var(--s5);z-index:90;
  background:var(--ink);color:#fff;font-size:14px;padding:14px 18px;border-radius:10px;
  opacity:0;transform:translateY(8px);pointer-events:none;
  transition:opacity var(--dur-base) var(--ease-out),transform var(--dur-base) var(--ease-out);
}
.ph-toast.is-in{opacity:1;transform:none;}
@keyframes ph-badge-pop{0%{transform:scale(1)}50%{transform:scale(1.35)}100%{transform:scale(1)}}
.ph-badge-pop{animation:ph-badge-pop 350ms var(--ease-spring);}

/* ==========================================================================
   MOBILE
   ========================================================================== */
.ph-mobile-bar{
  position:fixed;left:0;right:0;bottom:0;z-index:80;display:none;
  gap:var(--s3);align-items:center;
  padding:var(--s3) var(--s4) calc(var(--s3) + env(safe-area-inset-bottom));
  background:rgba(255,255,255,.96);backdrop-filter:blur(12px);
  border-top:1px solid var(--hairline);
  transform:translateY(110%);transition:transform var(--dur-base) var(--ease-inout);
}
.ph-mobile-bar.is-in{transform:none;}
.ph-mobile-bar__price{font-family:var(--font-display);font-size:17px;font-weight:700;
  font-variant-numeric:tabular-nums;white-space:nowrap;}
/* Specificity has to beat Storefront's `.woocommerce div.product form.cart`. */
.ph-scope .ph-mobile-bar form.cart.cart{margin:0;padding:0;flex:1;display:flex;}
.ph-scope .ph-mobile-bar .ph-btn{padding:13px var(--s5);font-size:15px;}

@media (max-width:1024px){
  .ph-hero{grid-template-columns:1fr;gap:var(--s6);padding-bottom:var(--s7);}
  .ph-buy{position:static;}
}
@media (max-width:768px){
  :root{--s9:56px;}
  .ph-container{padding:0 var(--s4);}
  .ph-scope h1{font-size:26px;}
  .ph-scope h2{font-size:22px;}
  .ph-overview p{font-size:17px;}
  .ph-features{grid-template-columns:1fr;}
  .ph-specs__row{grid-template-columns:1fr;gap:2px;padding:var(--s3) 0;}
  .ph-specs__k{font-size:12px;letter-spacing:.04em;text-transform:uppercase;}
  .ph-mobile-bar{display:flex;}
  body.single-product{padding-bottom:96px;}
  .ph-related .ph-grid{display:flex;overflow-x:auto;scroll-snap-type:x mandatory;
    -webkit-overflow-scrolling:touch;padding-bottom:var(--s3);}
  .ph-related .ph-card{flex:0 0 62%;scroll-snap-align:start;}
  .ph-lightbox__nav{display:none;}
}

/* ==========================================================================
   GLOBAL HEADER
   ========================================================================== */

/* Storefront's own header chrome is unhooked in PHP; these rules only have to
   undo the shell's padding, colour and stacking. */
.ph-scope .site-header{
  position:sticky;top:var(--wp-bar);z-index:90;
  margin:0;padding:0;border:0;background:#fff;
  transition:transform var(--dur-base) var(--ease-inout),
             box-shadow var(--dur-base) var(--ease-inout),
             border-color var(--dur-base) var(--ease-inout);
  border-bottom:1px solid transparent;
}
.ph-scope .site-header .col-full{
  max-width:none;width:100%;margin:0;padding:0;
}

/* At the very top of the page the header has no border and no shadow, so it
   dissolves into the page rather than sitting on it as a slab. */
.ph-scope .site-header.is-condensed{
  background:rgba(255,255,255,.92);
  backdrop-filter:saturate(180%) blur(12px);
  border-bottom-color:var(--hairline);
  box-shadow:0 8px 24px rgba(26,26,26,.04);
}
@supports not (backdrop-filter:blur(12px)){
  .ph-scope .site-header.is-condensed{background:#fff;}
}
.ph-scope .site-header.is-hidden{transform:translateY(-100%);}

.ph-hdr{
  position:relative;z-index:2;
  display:flex;align-items:center;gap:var(--s4);
  max-width:var(--container);margin:0 auto;padding:0 var(--s6);
  height:var(--hdr-h);
  transition:height var(--dur-base) var(--ease-inout);
}
.is-condensed .ph-hdr{height:var(--hdr-shown);}

.ph-hdr__logo{display:flex;align-items:center;flex:none;text-decoration:none;}
.ph-hdr__logo img{
  display:block;width:auto;height:40px;
  transition:height var(--dur-base) var(--ease-inout);
}
.is-condensed .ph-hdr__logo img{height:32px;}

/* Skip link — Storefront's own is unhooked with the rest of its header. */
.ph-skip{
  position:absolute;left:-9999px;top:0;z-index:120;
  background:var(--ink);color:#fff;padding:12px 18px;border-radius:0 0 10px 0;
  font-size:14px;text-decoration:none;
}
.ph-skip:focus{left:0;}

/* ---------- Nav ---------- */
.ph-nav{margin-inline:auto;}
.ph-nav__list{display:flex;align-items:center;gap:var(--s6);list-style:none;margin:0;padding:0;}
.ph-nav__item{position:relative;display:flex;align-items:center;gap:2px;}
.ph-nav a{
  position:relative;display:block;padding:8px 0;
  font-family:var(--font-display);font-size:15px;font-weight:500;line-height:1;
  color:var(--ink);text-decoration:none;
}
/* Extends the pointer target to the 44px minimum without adding 44px of
   painted box, which would blow the 32px gap apart. */
.ph-nav a::before{content:"";position:absolute;inset:-14px 0;}
.ph-nav a::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:2px;
  background:var(--green);transform:scaleX(0);transform-origin:left;
  transition:transform var(--dur-base) var(--ease-out);
}
.ph-nav a:hover::after,.ph-nav a:focus-visible::after{transform:scaleX(1);}
.ph-nav a[aria-current="page"]{font-weight:600;}
.ph-nav a[aria-current="page"]::after{transform:scaleX(1);background:var(--green-deep);}

.ph-nav__caret{
  display:flex;align-items:center;justify-content:center;
  width:24px;height:24px;padding:0;border:0;background:none;cursor:pointer;
  color:var(--ink);
  transition:transform var(--dur-base) var(--ease-inout);
}
.ph-nav__caret[aria-expanded="true"]{transform:rotate(180deg);}

/* ---------- Actions ---------- */
/* Stretched so .ph-search inside it spans the full header height and the
   results panel can hang off `top:100%` — i.e. off the header's own bottom
   edge — instead of off the 44px button, which would leave the panel starting
   6px inside the header and would need re-tuning every time it condenses. */
.ph-hdr__actions{display:flex;align-items:center;align-self:stretch;gap:var(--s1);flex:none;margin-left:auto;}
.ph-iconbtn{
  position:relative;display:flex;align-items:center;justify-content:center;
  width:44px;height:44px;padding:0;border:0;border-radius:10px;
  background:none;cursor:pointer;color:var(--ink);text-decoration:none;
  transition:background var(--dur-fast) var(--ease-out);
}
.ph-iconbtn:hover,.ph-iconbtn:focus-visible{background:var(--wash);}
.ph-hdr__badge{
  position:absolute;top:-4px;right:-6px;
  min-width:16px;height:16px;padding:0 4px;border-radius:999px;
  display:flex;align-items:center;justify-content:center;
  background:var(--green-deep);color:#fff;
  font-family:var(--font-display);font-size:11px;font-weight:600;line-height:1;
}
.ph-hdr__badge[hidden]{display:none;}

.ph-hdr__burger{display:none;}
.ph-burger{display:block;width:18px;height:12px;position:relative;}
.ph-burger i{
  position:absolute;left:0;width:18px;height:2px;background:var(--ink);border-radius:1px;
  transition:transform var(--dur-base) var(--ease-inout);
}
.ph-burger i:first-child{top:2.5px;}
.ph-burger i:last-child{bottom:2.5px;}
[aria-expanded="true"] .ph-burger i:first-child{transform:translateY(3.5px) rotate(45deg);}
[aria-expanded="true"] .ph-burger i:last-child{transform:translateY(-3.5px) rotate(-45deg);}

/* ---------- Header search ---------- */

/* The wrapper stays in flow at exactly the magnifier's 44px, and the field is
   absolutely positioned inside it growing leftward. Nothing in the header line
   re-solves when the field opens: the nav does not shift, it only dims and is
   then cleanly occluded by the field's opaque surface. */
.ph-search{position:relative;flex:none;width:44px;align-self:stretch;display:flex;align-items:center;}
.ph-search__sr{
  position:absolute;width:1px;height:1px;margin:0;padding:0;overflow:hidden;
  clip-path:inset(50%);white-space:nowrap;border:0;
}
/* Selectors deliberately over-specified with .ph-scope. Storefront's own
   style.css is enqueued a second time AFTER this file and styles the field
   through `input[type="search"]` (0,1,1) — which beats a bare class (0,1,0)
   and was silently repainting the field as a grey #f2f2f2 pill with a dark
   #43454b border, a 16px radius and an inset shadow. Measured, not guessed.
   Leading with the body class puts every declaration at 0,2,0 or better. */
.ph-scope .ph-search__input{
  position:absolute;right:0;top:50%;transform:translateY(-50%);
  width:44px;height:44px;opacity:0;pointer-events:none;
  border:1px solid var(--hairline);border-radius:var(--radius);
  background:var(--white);padding:0 44px 0 var(--s4);
  box-shadow:none;
  font-family:var(--font-body);font-size:15px;line-height:1;color:var(--ink);
  transition:width var(--dur-base) var(--ease-out),
             opacity var(--dur-fast) var(--ease-out),
             border-color var(--dur-fast) var(--ease-out);
}
.ph-search__input::-webkit-search-cancel-button{display:none;}
.ph-scope .ph-search__input::placeholder{color:var(--muted);}
.ph-scope .is-search .ph-search__input{width:280px;opacity:1;pointer-events:auto;}
.ph-scope .is-search .ph-search__input:focus{
  border-color:var(--green-deep);outline:none;box-shadow:none;
}

/* The magnifier never moves — it simply becomes the field's trailing icon. */
.ph-hdr__search{position:relative;z-index:1;}
.ph-search__ico--shut{display:none;}
.is-search .ph-search__ico--find{display:none;}
.is-search .ph-search__ico--shut{display:block;}
.is-search .ph-hdr__search:hover,.is-search .ph-hdr__search:focus-visible{background:none;color:var(--green-deep);}

.ph-nav{transition:opacity var(--dur-fast) var(--ease-inout);}
.is-search .ph-nav{opacity:.4;pointer-events:none;}

.ph-search__cancel{display:none;}

/* 380, not the field's 280: a 40px thumb plus a name plus a price does not sit
   in 280 without the name truncating on almost every product. It did not sit in
   360 either — measured, the row was 32 padding + 40 thumb + 12 gap + 160 name
   + 12 gap + 102 price = exactly full, with the name needing 172. The extra 20
   is that shortfall, and lands the panel on the basket drawer's 380 besides.
   Deliberately not full-bleed — the mega-panel is for browsing, this is for
   aiming. */
.ph-search__panel{
  position:absolute;right:0;top:100%;margin-top:var(--s2);
  width:380px;max-width:calc(100vw - 48px);
  background:#fff;border:1px solid var(--hairline);border-radius:var(--radius);
  box-shadow:0 16px 32px rgba(26,26,26,.08);
  overflow:hidden;
  opacity:0;transform:translateY(-4px);
  transition:opacity var(--dur-fast) var(--ease-out),
             transform var(--dur-fast) var(--ease-out);
}
.ph-search__panel.is-open{opacity:1;transform:none;}
.ph-search__row{
  display:flex;align-items:center;gap:var(--s3);
  min-height:56px;padding:var(--s2) var(--s4);
  color:var(--ink);text-decoration:none;
  transition:background var(--dur-fast) var(--ease-out);
}
.ph-search__row:hover,.ph-search__row:focus-visible{background:var(--wash);outline:none;}
.ph-search__thumb{
  flex:none;display:flex;align-items:center;justify-content:center;
  width:40px;height:40px;overflow:hidden;
  border:1px solid var(--hairline);border-radius:var(--radius-sm);background:var(--wash);
}
.ph-search__thumb img{display:block;width:100%;height:100%;object-fit:contain;}
/* No photography yet: the category glyph stands in. Authored in a 48-unit box
   at stroke-width 2, so it needs the same weight correction the mega-panel
   glyphs get or it renders as a hairline at 22px. */
.ph-search__thumb--glyph{color:var(--muted);}
.ph-search__thumb--glyph svg{width:20px;height:20px;}
.ph-search__thumb--glyph svg *{stroke-width:4;}
.ph-search__name{
  flex:1;min-width:0;
  font-family:var(--font-body);font-size:14px;font-weight:500;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
}
.ph-search__price{
  flex:none;font-family:var(--font-body);font-size:13px;font-weight:600;
  font-variant-numeric:tabular-nums;color:var(--ink);
}
/* "Price on request" is not a price and must not be typeset as one — at 13/600
   in ink it sits at exactly the weight the eye scans for a figure, and the row
   reads as priced until you actually read the words. */
.ph-search__price--ask{font-weight:500;color:var(--muted);}
.ph-search__all{
  display:block;padding:var(--s3) var(--s4);
  border-top:1px solid var(--hairline);
  font-family:var(--font-body);font-size:14px;font-weight:500;
  color:var(--green-deep);text-decoration:none;
}
.ph-search__all:hover,.ph-search__all:focus-visible{background:var(--wash-green);color:var(--green-deeper);outline:none;}
.ph-search__msg{margin:0;padding:var(--s4);font-family:var(--font-body);font-size:14px;color:var(--muted);}

/* ---------- Mini-basket drawer ---------- */
.ph-overlay{
  position:fixed;inset:0;z-index:100;
  background:rgba(26,26,26,.32);opacity:0;
  transition:opacity var(--dur-base) var(--ease-out);
}
.ph-overlay.is-open{opacity:1;}
.ph-overlay[hidden]{display:none;}
body.ph-locked{overflow:hidden;}

/* Named ph-drawer, not ph-cart: the single-product buy row already ships as
   <form class="cart ph-cart"> (WooCommerce insists on `cart`, we added ours),
   and a later .ph-cart{position:fixed} here would rip that row out of flow. */
/* top is offset by the admin bar for the same reason the header is: #wpadminbar
   is position:fixed at z-index 99999, so a top:0 drawer has its heading sliced
   off for every logged-in editor. --wp-bar is 0 for visitors. */
.ph-drawer{
  position:fixed;top:var(--wp-bar);right:0;bottom:0;z-index:101;
  display:flex;flex-direction:column;
  width:min(380px,100vw);
  background:#fff;box-shadow:-24px 0 48px rgba(26,26,26,.10);
  transform:translateX(100%);
  transition:transform 320ms var(--ease-inout);
}
.ph-drawer.is-open{transform:none;}
.ph-drawer[hidden]{display:none;}
.ph-drawer__head{
  flex:none;display:flex;align-items:center;justify-content:space-between;gap:var(--s4);
  padding:var(--s5) var(--s4) var(--s4) var(--s5);
  border-bottom:1px solid var(--hairline);
}
.ph-drawer__title{margin:0;font-family:var(--font-display);font-size:18px;font-weight:600;line-height:1;}
.ph-drawer__body{flex:1;overflow-y:auto;-webkit-overflow-scrolling:touch;}
/* Only when empty. A basket that has just been emptied should not leave its
   message stranded at the top of 700px of white — that reads as content that
   failed to load rather than as a deliberate state. :has() scopes the change
   to the empty case so a real list still starts at the top where it belongs. */
.ph-drawer__body:has(.ph-drawer__empty){display:flex;flex-direction:column;justify-content:center;}
.ph-drawer__list{list-style:none;margin:0;padding:0;}

/* The 0fr collapse only takes the content height to zero; the padding and the
   rule below survive it, so both are animated on the same curve or the row
   leaves a 33px scar. */
.ph-drawer__row{
  display:grid;grid-template-rows:1fr;
  border-bottom:1px solid var(--hairline);
  transition:grid-template-rows 250ms var(--ease-inout),
             border-bottom-color 250ms var(--ease-inout);
}
.ph-drawer__row.is-removing{grid-template-rows:0fr;border-bottom-color:transparent;}
.ph-drawer__rowinner{
  overflow:hidden;display:flex;align-items:center;gap:var(--s3);
  padding:var(--s4) var(--s5);
  transition:opacity var(--dur-fast) var(--ease-inout),
             transform var(--dur-fast) var(--ease-inout),
             padding-block 250ms var(--ease-inout);
}
.ph-drawer__row.is-fading .ph-drawer__rowinner{opacity:0;transform:translateX(8px);}
.ph-drawer__row.is-removing .ph-drawer__rowinner{padding-block:0;}
.ph-drawer__thumb{
  flex:none;display:flex;align-items:center;justify-content:center;
  width:56px;height:56px;overflow:hidden;
  border:1px solid var(--hairline);border-radius:var(--radius-sm);background:var(--wash);
}
.ph-drawer__thumb img{display:block;width:100%;height:100%;object-fit:contain;}
.ph-drawer__meta{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;}
.ph-drawer__name{
  font-family:var(--font-body);font-size:14px;font-weight:500;line-height:1.35;
  color:var(--ink);text-decoration:none;
}
.ph-drawer__name:hover{color:var(--green-deep);}
.ph-drawer__line{font-family:var(--font-body);font-size:13px;color:var(--muted);font-variant-numeric:tabular-nums;}
.ph-drawer__remove{
  flex:none;display:flex;align-items:center;justify-content:center;
  width:32px;height:32px;border-radius:999px;color:var(--muted);
  transition:background var(--dur-fast) var(--ease-out),color var(--dur-fast) var(--ease-out);
}
.ph-drawer__remove:hover,.ph-drawer__remove:focus-visible{background:var(--wash);color:var(--ink);outline:none;}

.ph-drawer__foot{flex:none;padding:var(--s5);border-top:1px solid var(--hairline);background:var(--wash);}
.ph-drawer__foot[hidden]{display:none;}
.ph-drawer__subtotal{display:flex;align-items:baseline;justify-content:space-between;font-family:var(--font-body);font-size:15px;}
.ph-drawer__subtotal strong{
  font-family:var(--font-display);font-size:20px;font-weight:600;
  font-variant-numeric:tabular-nums;
  transition:opacity 100ms linear;
}
.ph-drawer__note{margin:var(--s2) 0 var(--s4);font-size:13px;line-height:1.5;color:var(--muted);}
.ph-drawer__viewbasket{
  display:block;margin-top:var(--s3);text-align:center;
  font-family:var(--font-body);font-size:14px;font-weight:500;
  color:var(--green-deep);text-decoration:none;
}
.ph-drawer__viewbasket:hover{color:var(--green-deeper);text-decoration:underline;}

.ph-drawer__empty{
  display:flex;flex-direction:column;align-items:center;gap:var(--s3);
  padding:var(--s8) var(--s5);text-align:center;color:var(--muted);
  /* Optical, not geometric: lifted off the true centre so the block sits at
     roughly 45% of the drawer's height, where the eye expects a centred
     message to be. */
  margin-bottom:10vh;
  transition:opacity var(--dur-base) var(--ease-out),transform var(--dur-base) var(--ease-out);
}
.ph-drawer__emptyline{margin:0;font-family:var(--font-body);font-size:15px;font-weight:500;color:var(--ink);}
.ph-drawer__browse{font-family:var(--font-body);font-size:14px;font-weight:500;color:var(--green-deep);text-decoration:none;}
.ph-drawer__browse:hover{color:var(--green-deeper);text-decoration:underline;}

/* The dip lands under the badge pop, so the two read as one arrival rather
   than as two independent alarms. */
.ph-hdr__basketico{display:flex;}
@keyframes ph-basket-dip{0%,100%{transform:translateY(0)}50%{transform:translateY(2px)}}
.ph-basket-dip{animation:ph-basket-dip 200ms var(--ease-inout);}

/* ---------- Shop mega-panel ---------- */

/* Positioned against the header's own bottom edge rather than appended to the
   body, so it needs no repositioning when the header condenses or retreats. */
.ph-mega{
  position:absolute;left:0;right:0;top:100%;z-index:89;
  background:#fff;
  border-top:1px solid var(--hairline);
  box-shadow:0 24px 48px rgba(26,26,26,.08);
  opacity:0;transform:translateY(-8px);
  /* The base duration is the CLOSING one; opening overrides it. Closing wants
     to be quicker than opening — a panel that lingers on the way out feels
     like lag rather than grace. */
  transition:opacity var(--dur-fast) var(--ease-out),
             transform var(--dur-fast) var(--ease-out);
}
.ph-mega[hidden]{display:none;}
.ph-mega.is-open{
  opacity:1;transform:none;
  transition-duration:var(--dur-base);
}

.ph-mega__inner{max-width:var(--container);margin:0 auto;padding:var(--s6);}
.ph-mega__grid{
  display:grid;grid-template-columns:repeat(4,1fr);gap:var(--s5);
  list-style:none;margin:0;padding:0;
}
.ph-mega__item{
  display:flex;align-items:center;gap:var(--s3);
  padding:12px;border-radius:8px;text-decoration:none;
  transition:background var(--dur-fast) var(--ease-out);
}
.ph-mega__item:hover,.ph-mega__item:focus-visible{background:var(--wash);}
.ph-mega__glyph{
  display:flex;flex:none;color:var(--ink);opacity:.65;
  transition:opacity var(--dur-fast) var(--ease-out),color var(--dur-fast) var(--ease-out);
}
/* The glyphs are authored in a 48-unit box at stroke-width 2, which is right
   at the ~120px placeholder size but resolves to a hairline once the box is
   halved. Doubling the width inside this instance only restores a 2px rendered
   stroke; the placeholder keeps its own weight because the selector is scoped
   to .ph-mega__glyph. A scoped override rather than vector-effect:
   non-scaling-stroke, which would pin the stroke at every size and reach the
   placeholder too if the selector ever widened. */
.ph-mega__glyph svg{width:24px;height:24px;}
.ph-mega__glyph svg *{stroke-width:4;}
.ph-mega__item:hover .ph-mega__glyph,
.ph-mega__item:focus-visible .ph-mega__glyph{color:var(--green-deep);opacity:1;}
.ph-mega__text{display:flex;flex-direction:column;gap:2px;min-width:0;}
.ph-mega__name{
  font-family:var(--font-body);font-size:15px;font-weight:500;line-height:1.3;
  color:var(--ink);
}
.ph-mega__count{font-family:var(--font-body);font-size:13px;line-height:1.3;color:var(--muted);}

/* The thirteenth cell spans the row instead of sitting alone in a column of
   its own — a single orphaned tile at the foot of the grid reads as a mistake.
   It keeps a hairline above it so it parses as a footer, not a category. */
/* The span has to live on the grid item — the list element — not on the anchor
   inside it, which is not a grid child at all. */
.ph-mega__grid > li:last-child{grid-column:1 / -1;}
.ph-mega__all{
  gap:var(--s2);
  margin-top:var(--s2);padding-top:var(--s4);
  border-top:1px solid var(--hairline);border-radius:0;
  font-family:var(--font-display);font-size:15px;font-weight:600;
  color:var(--green-deep);
}
.ph-mega__all:hover,.ph-mega__all:focus-visible{background:none;color:var(--green-deeper);}
.ph-mega__all svg{transition:transform var(--dur-base) var(--ease-out);}
.ph-mega__all:hover svg{transform:translateX(4px);}

/* Anchor jumps must clear the condensed header (60) + sub-nav (48) + breathing
   room (8). */
html{scroll-padding-top:116px;}

@media (max-width:900px){
  html{scroll-padding-top:112px;}
  :root{--hdr-h:56px;--hdr-shown:56px;}
  .ph-hdr{padding:0 var(--s5);gap:var(--s2);}
  .ph-hdr__logo img,.is-condensed .ph-hdr__logo img{height:32px;}
  .is-condensed .ph-hdr{height:var(--hdr-shown);}
  .ph-nav{display:none;}
  .ph-hdr__burger{display:flex;}
  /* The drawer carries the categories below 900px; the panel would have
     nothing to hang off once the nav is gone. */
  .ph-mega{display:none !important;}

  /* A 280px field cannot open inside a 56px bar next to two other controls,
     so below 900 the search takes the whole screen. z-index 95 sits above the
     header (90) and below the basket drawer (100) — the two never coexist,
     but the ordering should still be honest. */
  .is-search .ph-search{
    position:fixed;inset:0;z-index:95;width:auto;
    display:flex;flex-wrap:wrap;align-items:center;align-content:flex-start;
    gap:var(--s3);padding:var(--s4) var(--s5);
    background:#fff;
  }
  .is-search .ph-search__input{
    position:static;transform:none;flex:1;min-width:0;
    width:auto;height:48px;padding:0 var(--s4);
  }
  .is-search .ph-hdr__search{display:none;}
  .is-search .ph-search__cancel{
    display:block;flex:none;padding:0 var(--s2);
    border:0;background:none;cursor:pointer;
    font-family:var(--font-body);font-size:15px;font-weight:500;color:var(--green-deep);
  }
  .is-search .ph-search__panel{
    position:static;flex-basis:100%;width:auto;max-width:none;
    margin-top:var(--s2);border:0;border-radius:0;box-shadow:none;
  }
  .is-search .ph-search__row{padding-inline:0;}
  .is-search .ph-search__all{padding-inline:0;}
  .is-search .ph-search__all:hover{background:none;}
  .is-search .ph-search__msg{padding-inline:0;}
}

/* ==========================================================================
   MOBILE NAVIGATION DRAWER  (below 900px)
   ==========================================================================
   The drawer slides out from under the header rather than over it, and the
   burger's own X-morph is the close control — a second X inside the panel
   would give one gesture two affordances. That is why the header keeps the
   higher z-index (90 vs 85) and why the overlay starts below the header's
   bottom edge instead of at inset:0: search and basket stay reachable and
   undimmed the whole time the menu is open.

   Everything here is gated to the mobile query. Above 900 the desktop nav and
   the mega-panel own this job, and the markup — which ships on every page —
   simply never resolves to a box. */
@media (max-width:900px){
  .ph-mnav[hidden],.ph-mnav__overlay[hidden]{display:none;}

  .ph-mnav__overlay{
    position:fixed;left:0;right:0;bottom:0;
    top:calc(var(--wp-bar) + 56px);
    z-index:84;
    background:rgba(26,26,26,.32);
    opacity:0;
  }
  .ph-mnav__overlay.is-open{opacity:1;}

  /* dvh, not vh: on iOS the URL bar's retraction changes the viewport, and vh
     would leave the contact block stranded behind the browser chrome. */
  .ph-mnav{
    position:fixed;top:calc(var(--wp-bar) + 56px);right:0;
    z-index:85;
    width:min(88vw,400px);
    height:calc(100dvh - var(--wp-bar) - 56px);
    background:var(--white);
    overflow-y:auto;overscroll-behavior:contain;
    padding-bottom:env(safe-area-inset-bottom);
    box-shadow:-16px 0 32px rgba(26,26,26,.06);
    transform:translateX(100%);
  }
  .ph-mnav.is-open{transform:none;}

  /* Motion is additive. The settled state is carried by .is-open alone, so a
     reduced-motion visitor gets a drawer that is simply *there* — and the
     inline transition-delay each row ships with stays inert because there is
     no transition for it to delay. */
  @media (prefers-reduced-motion:no-preference){
    .ph-mnav{transition:transform 320ms var(--ease-inout);}
    .ph-mnav__overlay{transition:opacity var(--dur-base) var(--ease-out);}
    .ph-mnav__row{
      opacity:0;transform:translateX(12px);
      transition:opacity var(--dur-base) var(--ease-out),
                 transform var(--dur-base) var(--ease-out);
    }
    .ph-mnav.is-open .ph-mnav__row{opacity:1;transform:none;}
  }

  .ph-mnav__primary{list-style:none;margin:0;padding:0;}
  .ph-mnav__primary li + li{border-top:1px solid var(--hairline);}
  .ph-mnav__primary a{
    display:flex;align-items:center;min-height:56px;padding:0 var(--s5);
    font-family:var(--font-display);font-size:20px;font-weight:600;
    color:var(--ink);text-decoration:none;
  }
  .ph-mnav__primary a[aria-current="page"]{color:var(--green-deep);}

  .ph-mnav__shop{border-top:1px solid var(--hairline);}
  .ph-mnav__label{
    margin:0;padding:var(--s5) var(--s5) var(--s2);
    font-family:var(--font-display);font-size:12px;font-weight:600;
    text-transform:uppercase;letter-spacing:.08em;color:var(--muted);
  }
  .ph-mnav__all{
    display:flex;align-items:center;gap:var(--s2);
    min-height:48px;padding:0 var(--s5);
    font-family:var(--font-body);font-size:15px;font-weight:500;
    color:var(--green-deep);text-decoration:none;
  }

  .ph-mnav__cats{list-style:none;margin:0;padding:0;}
  .ph-mnav__cat{
    display:flex;align-items:center;gap:var(--s3);
    min-height:48px;padding:0 var(--s5);text-decoration:none;
  }
  /* Same hairline correction as the mega-panel: the glyphs are drawn in a
     48-unit box at stroke-width 2, which renders as half a pixel once the box
     is 20. Full opacity here — these are list rows, not a hover surface. */
  .ph-mnav__glyph{flex:none;display:flex;width:20px;height:20px;color:var(--ink);}
  .ph-mnav__glyph svg{width:20px;height:20px;}
  .ph-mnav__glyph svg *{stroke-width:4;}
  .ph-mnav__name{
    flex:1 1 auto;min-width:0;
    font-family:var(--font-body);font-size:15px;font-weight:500;color:var(--ink);
  }
  .ph-mnav__count{
    flex:none;font-family:var(--font-body);font-size:13px;color:var(--muted);
  }

  .ph-mnav__contact{
    margin-top:var(--s5);padding:var(--s6) var(--s5);background:var(--wash);
  }
  .ph-mnav__phone{
    display:block;
    font-family:var(--font-body);font-size:15px;font-weight:500;
    color:var(--ink);text-decoration:none;
  }
  .ph-mnav__hours{
    margin:var(--s1) 0 var(--s4);
    font-family:var(--font-body);font-size:13px;color:var(--muted);
  }
  .ph-mnav__social{list-style:none;display:flex;gap:var(--s4);margin:0;padding:0;}
  .ph-mnav__social a{display:flex;width:24px;height:24px;color:var(--ink);}
}

/* Position-fixed body, not overflow:hidden — iOS Safari ignores the latter on
   <html> and scrolls the page behind the drawer anyway. The cost is that the
   document jumps to the top the moment it becomes fixed, so the offset is
   parked in --lock-y and the scroll position restored on close. */
html.ph-lock{overflow:hidden;}
html.ph-lock body{position:fixed;inset:0;top:calc(-1 * var(--lock-y,0px));width:100%;}

/* ==========================================================================
   REDUCED MOTION
   ========================================================================== */
/* A header that appears and disappears is itself motion, so it stays put. */
@media (prefers-reduced-motion:reduce){
  .ph-scope .site-header.is-hidden{transform:none;}
  html.ph-hdr-hidden{--subnav-top:var(--hdr-shown);}
}
@media (prefers-reduced-motion:reduce){
  .ph-scope *,.ph-scope *::before,.ph-scope *::after,
  .ph-reveal,.ph-subnav,.ph-mobile-bar,.ph-tray,.ph-lightbox,.ph-toast{
    animation-duration:.01ms !important;animation-iteration-count:1 !important;
    transition-duration:.01ms !important;scroll-behavior:auto !important;
  }
  .ph-reveal{opacity:1 !important;transform:none !important;}
}

/* ==========================================================================
   STOREFRONT SUPPRESSION
   ========================================================================== */
.single-product .woocommerce-tabs,
.single-product .product .summary.entry-summary > .woocommerce-product-rating,
.single-product .storefront-sorting,
.single-product .woocommerce-product-gallery{display:none !important;}
.single-product .site-main{padding-top:0;}
.single-product div.product{margin-bottom:0;}


/* ==========================================================================
   SHOP INDEX  (/shop/)
   ========================================================================== */
.ph-shop{padding:var(--s7) 0 var(--s9);}
.ph-shop__head{margin-bottom:var(--s6);}
.ph-scope .ph-shop__head h1{margin:0 0 var(--s2);}
.ph-shop__lede{font-size:16px;color:var(--muted);max-width:var(--prose);margin:0;}

/* Bar. The pill row scrolls sideways rather than wrapping to three lines:
   twelve categories wrapped push the first product below the fold on a phone,
   and the grid is what the visitor came for. */
.ph-shop__bar{display:flex;align-items:center;gap:var(--s4);
  padding-bottom:var(--s4);border-bottom:1px solid var(--hairline);margin-bottom:var(--s4);}
.ph-filters{display:flex;gap:var(--s2);flex:1;min-width:0;overflow-x:auto;
  scrollbar-width:none;-webkit-overflow-scrolling:touch;padding:2px 0;}
.ph-filters::-webkit-scrollbar{display:none;}
.ph-scope .ph-filter{flex:0 0 auto;background:#fff;border:1px solid var(--hairline);
  border-radius:999px;padding:8px 14px;cursor:pointer;white-space:nowrap;
  font-family:var(--font-body);font-size:13px;font-weight:600;color:var(--muted);
  transition:background var(--dur-fast) var(--ease-out),color var(--dur-fast) var(--ease-out),border-color var(--dur-fast) var(--ease-out);}
.ph-scope .ph-filter:hover{color:var(--ink);border-color:#DCE4E2;}
.ph-scope .ph-filter.is-on{background:var(--green-deep);border-color:var(--green-deep);color:#fff;}

.ph-sort{flex:0 0 auto;position:relative;}
.ph-scope .ph-sort__select{appearance:none;-webkit-appearance:none;
  background:#fff url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath fill='none' stroke='%235B6663' stroke-width='1.6' d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E") no-repeat right 12px center/12px 8px;
  border:1px solid var(--hairline);border-radius:999px;padding:8px 34px 8px 14px;
  font-family:var(--font-body);font-size:13px;font-weight:600;color:var(--ink);cursor:pointer;}

.ph-shop__count{font-size:13px;color:var(--muted);margin:0 0 var(--s5);}
.ph-shop__none{font-size:15px;color:var(--muted);text-align:center;padding:var(--s8) 0;margin:0;}

/* Price on request is a sentence, not a figure — it must not be typeset at the
   weight the eye scans a grid for numbers. */
.ph-card__price--ask{font-weight:500;color:var(--muted);}

/* Arrival. A load-time ladder, not an observer: on a twelve-row grid a
   scroll-triggered reveal fires under the visitor's thumb while they are
   already reading, which reads as lag. The first eight cards are the ones
   present at first paint; everything below simply exists. Base state is the
   landed state, so no CSS animation support means a visible grid. */
@keyframes ph-card-in{from{opacity:0;transform:translateY(8px);}to{opacity:1;transform:none;}}
.ph-shop__grid > .ph-card:nth-child(-n+8){animation:ph-card-in var(--dur-base) var(--ease-inout) both;}
.ph-shop__grid > .ph-card:nth-child(2){animation-delay:40ms;}
.ph-shop__grid > .ph-card:nth-child(3){animation-delay:80ms;}
.ph-shop__grid > .ph-card:nth-child(4){animation-delay:120ms;}
.ph-shop__grid > .ph-card:nth-child(5){animation-delay:160ms;}
.ph-shop__grid > .ph-card:nth-child(6){animation-delay:200ms;}
.ph-shop__grid > .ph-card:nth-child(7){animation-delay:240ms;}
.ph-shop__grid > .ph-card:nth-child(8){animation-delay:280ms;}

/* Empty state. Optically centred rather than mathematically — the block sits
   at 45% of the well, which is where the eye expects the middle to be. */
.ph-empty{display:flex;flex-direction:column;align-items:center;text-align:center;
  padding:var(--s9) var(--s5) var(--s10);}
.ph-empty__glyph{display:block;line-height:0;}
.ph-empty__glyph svg{width:40px;height:40px;color:var(--green);opacity:.4;}
.ph-empty__title{font-family:var(--font-display);font-size:18px;font-weight:600;
  color:var(--ink);margin:var(--s5) 0 var(--s2);}
.ph-empty__body{font-size:15px;color:var(--muted);max-width:38ch;margin:0 0 var(--s4);}

@media (max-width:640px){
  .ph-shop{padding:var(--s6) 0 var(--s8);}
  .ph-shop__bar{flex-direction:column;align-items:stretch;gap:var(--s3);}
  .ph-sort{align-self:flex-start;}
}

/* ==========================================================================
   9. CONTENT PAGES — about / contact / journal
   Section rhythm uses --sec (96 → 64 at <768). NOT --s9: --s9 collapses to
   56px inside the 768 max-width query and would break fable's rhythm.
   ========================================================================== */
.ph-page{--sec:96px;padding:var(--s7) 0 var(--s9);}
.ph-page__head{margin-bottom:var(--s7);}
.ph-scope .ph-page__head h1{margin:0 0 var(--s3);font-family:var(--font-display);
  font-size:clamp(32px,5vw,48px);line-height:1.08;letter-spacing:-.02em;font-weight:700;}
.ph-page__lede{max-width:var(--prose);margin:0;font-size:17px;line-height:1.6;color:var(--muted);}

.ph-scope .ph-h2{margin:0 0 var(--s4);font-family:var(--font-display);font-size:clamp(24px,3vw,32px);
  line-height:1.15;letter-spacing:-.015em;font-weight:700;color:var(--ink);}
.ph-scope .ph-h3{margin:0 0 var(--s2);font-family:var(--font-display);font-size:18px;
  line-height:1.3;font-weight:600;color:var(--ink);}

.ph-panel{background:var(--wash);border:1px solid var(--hairline);border-radius:var(--radius);
  padding:var(--s6);}
.ph-panel__body{margin:0;font-size:16px;line-height:1.65;color:var(--muted);}
.ph-panel .ph-micro{display:block;margin-bottom:var(--s3);}

/* --- contact ------------------------------------------------------------ */
.ph-contact{display:grid;grid-template-columns:7fr 5fr;gap:var(--s7);align-items:start;}
.ph-contact__form-col{min-width:0;}
.ph-contact__info{min-width:0;background:var(--wash);border:1px solid var(--hairline);
  border-radius:var(--radius);padding:var(--s6);}
/* The deliberate opt-in back into breaking. An email address is one unbroken
   token, and support@planethealth.uk at 320px is wider than the column it
   sits in — the only string on the site today that legitimately has to break
   mid-word. Anything else that overflows is a bug, and .ph-scope's
   overflow-wrap:normal is what makes it visible. */
.ph-contact__info a[href^="mailto:"]{overflow-wrap:anywhere;}

.ph-form__row{margin-bottom:var(--s5);}
.ph-scope .ph-label{display:block;margin:0 0 var(--s2);font-size:14px;font-weight:600;color:var(--ink);}
.ph-label__opt{margin-left:var(--s2);font-weight:400;color:var(--muted);}
.ph-scope .ph-input,
.ph-scope .ph-select,
.ph-scope .ph-textarea{display:block;width:100%;box-sizing:border-box;margin:0;
  padding:12px var(--s4);font-family:var(--font-body);font-size:16px;line-height:1.4;
  color:var(--ink);background:var(--white);border:1px solid #D8E0DE;border-radius:var(--radius-sm);
  box-shadow:none;appearance:none;-webkit-appearance:none;
  transition:border-color var(--dur-fast) var(--ease-out),box-shadow var(--dur-fast) var(--ease-out);}
.ph-scope .ph-textarea{min-height:160px;resize:vertical;}
.ph-scope .ph-select{padding-right:var(--s7);
  background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8' fill='none' stroke='%235B6663' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");
  background-repeat:no-repeat;background-position:right var(--s4) center;background-size:12px 8px;}
.ph-scope .ph-input:hover,
.ph-scope .ph-select:hover,
.ph-scope .ph-textarea:hover{border-color:#BFCCC9;}
.ph-scope .ph-input:focus,
.ph-scope .ph-select:focus,
.ph-scope .ph-textarea:focus{outline:none;border-color:var(--green-deep);
  box-shadow:0 0 0 3px rgba(44,139,117,.16);}
.ph-scope .ph-input::placeholder,
.ph-scope .ph-textarea::placeholder{color:#9AA6A3;}

.ph-form__err{display:none;margin-top:var(--s2);font-size:13.5px;line-height:1.45;color:#B4322B;}
.ph-form__row.is-invalid .ph-form__err{display:block;}
.ph-scope .ph-form__row.is-invalid .ph-input,
.ph-scope .ph-form__row.is-invalid .ph-select,
.ph-scope .ph-form__row.is-invalid .ph-textarea{border-color:#B4322B;}
.ph-scope .ph-form__row.is-invalid .ph-input:focus,
.ph-scope .ph-form__row.is-invalid .ph-select:focus,
.ph-scope .ph-form__row.is-invalid .ph-textarea:focus{box-shadow:0 0 0 3px rgba(180,50,43,.14);}
.ph-form__err--top{display:none;margin:0 0 var(--s5);padding:12px var(--s4);font-size:14px;
  line-height:1.5;color:#8E2721;background:#FCF1F0;border:1px solid #F0D2D0;
  border-radius:var(--radius-sm);}
.ph-form__err--top.is-visible{display:block;}
.ph-form__note{margin:var(--s4) 0 0;font-size:13.5px;line-height:1.55;color:var(--muted);}
.ph-hp{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden;}

.ph-form__done{display:none;padding:var(--s7) var(--s6);text-align:center;
  background:var(--wash-green);border:1px solid var(--hairline);border-radius:var(--radius);}
.ph-form__done.is-visible{display:block;}
.ph-form__tick{display:block;line-height:0;margin:0 auto var(--s4);}
.ph-form__tick svg{width:48px;height:48px;color:var(--green-deep);}
.ph-scope .ph-form__done-title{margin:0 0 var(--s2);font-family:var(--font-display);font-size:20px;
  font-weight:600;color:var(--ink);}
.ph-form__done-body{margin:0;font-size:15px;line-height:1.6;color:var(--muted);}

.ph-deflist{margin:0;}
.ph-deflist dt{margin:0 0 var(--s1);font-size:12px;font-weight:600;letter-spacing:.08em;
  text-transform:uppercase;color:var(--green-deep);}
.ph-deflist dd{margin:0 0 var(--s5);font-size:15px;line-height:1.6;color:var(--ink);}
.ph-deflist dd:last-of-type{margin-bottom:0;}
.ph-deflist a{color:var(--ink);text-decoration:none;border-bottom:1px solid var(--hairline);
  transition:color var(--dur-fast) var(--ease-out),border-color var(--dur-fast) var(--ease-out);}
.ph-deflist a:hover{color:var(--green-deep);border-bottom-color:var(--green-deep);}
.ph-deflist__note{display:block;font-size:13px;color:var(--muted);}

.ph-social{display:flex;gap:var(--s4);align-items:center;list-style:none;margin:var(--s6) 0 0;padding:0;}
.ph-social li{margin:0;}
.ph-social a{display:flex;align-items:center;justify-content:center;width:40px;height:40px;
  color:var(--muted);background:var(--white);border:1px solid var(--hairline);border-radius:50%;
  transition:color var(--dur-fast) var(--ease-out),border-color var(--dur-fast) var(--ease-out),
    transform var(--dur-fast) var(--ease-out);}
.ph-social a:hover{color:var(--green-deep);border-color:var(--green-deep);transform:translateY(-2px);}
.ph-social svg{width:20px;height:20px;}

/* --- about -------------------------------------------------------------- */
.ph-about__hero{margin-bottom:var(--s7);}
.ph-about__strap{display:block;margin-bottom:var(--s3);}
.ph-scope .ph-about__hero h1{margin:0;max-width:20ch;font-family:var(--font-display);
  font-size:clamp(34px,6vw,56px);line-height:1.05;letter-spacing:-.025em;font-weight:700;}
.ph-about__intro{margin-bottom:var(--sec);}
.ph-about__intro p{margin:0 0 var(--s4);font-size:17px;line-height:1.7;color:var(--muted);}
.ph-about__intro p:last-child{margin-bottom:0;}

.ph-about__vm{display:grid;grid-template-columns:1fr 1fr;gap:var(--s5);margin-bottom:var(--sec);}
.ph-about__commit{margin-bottom:var(--sec);}

.ph-ladder{list-style:none;margin:0;padding:0;max-width:var(--prose);
  border-top:1px solid var(--hairline);}
.ph-ladder__item{padding:var(--s5) 0;border-bottom:1px solid var(--hairline);}
.ph-ladder__n{display:block;margin-bottom:var(--s2);font-size:13px;font-weight:600;
  letter-spacing:.06em;color:var(--green-deep);font-variant-numeric:tabular-nums;}
.ph-ladder__label{display:block;margin-bottom:var(--s2);font-family:var(--font-display);
  font-size:17px;font-weight:600;color:var(--ink);}
.ph-ladder__body{margin:0;font-size:15.5px;line-height:1.65;color:var(--muted);}

.ph-about__cert{margin-bottom:var(--sec);}
.ph-cert{display:grid;grid-template-columns:1fr 1fr;gap:var(--s5);}
.ph-cert__card{display:flex;flex-direction:column;align-items:flex-start;}
.ph-cert__glyph{display:block;line-height:0;margin-bottom:var(--s4);}
.ph-cert__glyph svg{width:48px;height:48px;color:var(--green);opacity:.75;}
.ph-cert__name{display:block;margin-bottom:var(--s2);font-family:var(--font-display);
  font-size:17px;font-weight:600;color:var(--ink);}
.ph-cert__body{margin:0;font-size:15px;line-height:1.6;color:var(--muted);}
.ph-cert__note{max-width:var(--prose);margin:var(--s5) 0 0;font-size:14.5px;line-height:1.6;
  color:var(--muted);}
.ph-cert__note .ph-btn--link{margin-left:var(--s2);}

.ph-band{display:flex;flex-direction:column;align-items:center;text-align:center;
  padding:var(--s8) var(--s6);background:var(--wash-green);border-radius:var(--radius);}
.ph-scope .ph-band__title{margin:0 0 var(--s5);max-width:24ch;font-family:var(--font-display);
  font-size:clamp(22px,3vw,30px);line-height:1.2;letter-spacing:-.015em;font-weight:700;}
.ph-band__acts{display:flex;flex-wrap:wrap;gap:var(--s3);justify-content:center;}

/* --- journal ------------------------------------------------------------ */
.ph-journal__grid{margin-top:var(--s6);}
.ph-post__date{display:block;margin-top:var(--s2);font-size:13px;color:var(--muted);}

/* Pager. Printed by ph_journal_pager() only when there is more than one page.
   Every cell is 44x44 minimum — these are the smallest tap targets on the
   site and the only ones a mis-tap costs a page load. The .dots span is the
   one cell with no border: it is punctuation, not a control, and framing it
   like the others invites a tap that does nothing. */
.ph-pager{display:flex;justify-content:center;margin-top:var(--s7);}
.ph-pager ul{display:flex;flex-wrap:wrap;align-items:center;justify-content:center;
  gap:var(--s2);margin:0;padding:0;list-style:none;}
.ph-pager li{display:flex;}
.ph-scope .ph-pager a,
.ph-scope .ph-pager span{display:flex;align-items:center;justify-content:center;
  min-width:44px;min-height:44px;padding:0 var(--s3);
  border:1px solid var(--hairline);border-radius:var(--radius-sm);
  background:var(--white);font-family:var(--font-body);font-size:14px;
  font-weight:600;line-height:1;color:var(--ink);text-decoration:none;
  transition:background var(--dur-fast) var(--ease-out),
             border-color var(--dur-fast) var(--ease-out),
             color var(--dur-fast) var(--ease-out);}
.ph-scope .ph-pager a:hover{background:var(--wash-green);
  border-color:var(--green-deep);color:var(--green-deeper);}
.ph-scope .ph-pager span.current{background:var(--green-deep);
  border-color:var(--green-deep);color:var(--white);}
.ph-scope .ph-pager span.dots{min-width:24px;padding:0;border-color:transparent;
  background:none;color:var(--muted);}

/* Below 480 the controls of a multi-page pager cannot hold one line — seven
   44px cells plus six 8px gaps is 356px against 288px of content width, and
   shrinking the cells trades a real touch target for a cosmetic one. Left
   alone they wrap ragged, with Newer buried mid-numbers and Older orphaned:
   grouping by available width rather than by meaning. fable CONFIRMED (B):
   float prev and next onto full-width rows of their own so the numbers form
   one contiguous band between them. paginate_links emits prev → numbers →
   next in that DOM order, so the visual reflow matches tab order and no
   inversion is introduced. It also gives the two controls people actually use
   on date-ordered content the largest targets on the smallest screens.

   480 is the right threshold ONLY because ph_journal_pager() passes
   'mid_size' => 0, which caps the pager at seven controls
   (Newer 1 … n … N Older) no matter how many pages exist. Measured: a
   seven-control pager holds one line all the way down to 481, while the
   nine-control shape mid_size 1 produces already wraps at 620 — which would
   have left a ragged 481–619 window that no fixed breakpoint can close,
   because the wrap point moves with the page count. Capping the count fixes
   the geometry instead of chasing it. If mid_size is ever raised, this
   breakpoint is wrong again.

   The prev/next selectors are qualified with li because .ph-pager li above
   would otherwise win on specificity (0,1,1 against 0,1,0) and the full-width
   rows would silently never apply — which is exactly what happened first
   time round. */
@media (max-width:480px){
  /* The ul is a shrink-to-fit flex item, so between 403 and 361 of content
     width it sits narrower than the nav and the two bars come out inset by a
     few pixels on one screen size and full-bleed on the next. Pinning it to
     100% makes the sandwich the same width as everything else in the column
     at every mobile size. */
  .ph-pager ul{width:100%;}
  .ph-pager li.ph-pager__prev{order:-1;flex:1 0 100%;}
  .ph-pager li.ph-pager__next{order:99;flex:1 0 100%;}
  .ph-scope .ph-pager__prev a,
  .ph-scope .ph-pager__next a{width:100%;}
}

@media (max-width:1024px){
  .ph-contact{grid-template-columns:1fr;gap:var(--s6);}
  /* fable: mobile visitors want the phone number, not a form */
  .ph-contact__info{order:-1;}
}
@media (max-width:768px){
  .ph-page{--sec:64px;padding:var(--s6) 0 var(--s8);}
  .ph-page__head{margin-bottom:var(--s6);}
  .ph-about__vm{grid-template-columns:1fr;}
  .ph-cert{grid-template-columns:1fr;}
  .ph-panel{padding:var(--s5);}
  .ph-contact__info{padding:var(--s5);}
  .ph-band{padding:var(--s7) var(--s5);}
  .ph-band__acts{flex-direction:column;align-self:stretch;}
  .ph-band__acts .ph-btn{width:100%;}
}

/* ==========================================================================
   COMPARE PAGE
   ========================================================================== */
/* Small button. The compare column is 180px wide; the 16px/16px-padding
   default .ph-btn wraps "Add to basket" onto two lines at that width. */
.ph-btn--sm{padding:9px 14px;font-size:13px;border-radius:8px;}

.ph-page--compare .ph-page__head{margin-bottom:var(--s6);}

/* The scroll container. Sticky positions resolve against the nearest
   scrollport, so the horizontal scroll must live here and not on the page. */
.ph-compare__scroll{
  overflow-x:auto;overflow-y:visible;
  -webkit-overflow-scrolling:touch;
  scrollbar-width:thin;
  border:1px solid var(--hairline);border-radius:var(--radius);background:#fff;
}
/* border-collapse:collapse detaches borders from cells, and a collapsed
   table's cells cannot be sticky in Chrome. Separate + zero spacing keeps the
   grid look while leaving the cells positionable. */
.ph-compare__table{
  border-collapse:separate;border-spacing:0;width:100%;
  font-family:var(--font-body);font-size:14px;
}
.ph-compare__table th,
.ph-compare__table td{
  text-align:left;vertical-align:top;padding:var(--s4);
  border-bottom:1px solid var(--hairline);
}
.ph-compare__table tbody tr:last-child th,
.ph-compare__table tbody tr:last-child td{border-bottom:0;}

/* Product columns sit above the page but below the frozen label column. The
   explicit background is what stops the label text showing through them. */
.ph-compare__col,
.ph-compare__val{position:relative;z-index:1;background:#fff;}
.ph-compare__col{min-width:160px;width:180px;border-left:1px solid var(--hairline);}
.ph-compare__val{border-left:1px solid var(--hairline);color:var(--ink);}

/* Frozen label column. z-index above the product cells as well as an opaque
   background — without the stacking order the columns ghost underneath at
   sub-pixel scroll positions. */
.ph-compare__lab{
  position:sticky;left:0;z-index:2;background:#fff;
  min-width:150px;width:170px;
  font-family:var(--font-body);font-weight:600;color:var(--muted);
  box-shadow:1px 0 0 var(--hairline);
}
.ph-compare__corner{vertical-align:bottom;padding-bottom:var(--s5);}
.ph-compare__corner .ph-micro{display:block;}

.ph-compare__col{padding-top:var(--s5);}
.ph-compare__remove{
  position:absolute;top:6px;right:6px;z-index:1;
  width:26px;height:26px;border:0;border-radius:50%;
  background:var(--wash);color:var(--muted);
  font-size:16px;line-height:1;cursor:pointer;opacity:0;
  transition:opacity var(--dur-fast) var(--ease-out),background var(--dur-fast) var(--ease-out),color var(--dur-fast) var(--ease-out);
}
.ph-compare__col:hover .ph-compare__remove,
.ph-compare__remove:focus-visible{opacity:1;}
.ph-compare__remove:hover{background:var(--green-deep);color:#fff;}
@media (hover:none){.ph-compare__remove{opacity:1;}}

.ph-compare__prod{display:block;text-decoration:none;color:inherit;}
.ph-compare__frame{
  display:block;position:relative;background:var(--wash);border-radius:var(--radius-sm);
  aspect-ratio:1/1;overflow:hidden;margin-bottom:var(--s3);
}
.ph-compare__frame img{
  width:100%;height:100%;object-fit:contain;display:block;padding:10%;
  transition:transform var(--dur-base) var(--ease-out);
}
.ph-compare__prod:hover .ph-compare__frame img{transform:scale(1.04);}
.ph-compare__frame .ph-noimg{position:absolute;inset:0;border-radius:0;aspect-ratio:auto;}
.ph-compare__frame .ph-noimg svg{width:48px;height:48px;}
.ph-compare__name{
  display:block;font-family:var(--font-display);font-size:15px;font-weight:600;
  line-height:1.35;margin-bottom:var(--s2);
}
.ph-compare__prod:hover .ph-compare__name{color:var(--green-deep);}
.ph-compare__price{display:block;font-weight:600;margin-bottom:var(--s3);}
.ph-compare__price--ask{font-weight:500;font-size:13px;color:var(--muted);}

/* Add-another column. Dashed so it reads as a slot rather than a product. */
.ph-compare__col--add{background:var(--wash);}
.ph-compare__add{
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--s2);
  min-height:180px;padding:var(--s4);
  border:1px dashed #CFDBD7;border-radius:var(--radius-sm);
  text-decoration:none;color:var(--muted);font-size:13px;font-weight:600;text-align:center;
  transition:border-color var(--dur-fast) var(--ease-out),color var(--dur-fast) var(--ease-out),background var(--dur-fast) var(--ease-out);
}
.ph-compare__add:hover{border-color:var(--green-deep);color:var(--green-deep);background:#fff;}
.ph-compare__plus{font-size:24px;line-height:1;}
.ph-compare__val--add{background:var(--wash);}

/* Storefront re-enqueues style.css AFTER ph.css, and its
   `table:not(.has-background) th` (0,1,2) and `... tbody td` (0,1,3) rules beat
   a bare class — they were repainting the frozen column #F8F8F8, the values
   #FDFDFD and every even row #FBFBFB, three near-whites striping against each
   other. Restate each background led by .ph-scope and named down to the
   element, then keep the add-column and the hover highlight one cascade step
   above it so the slot still reads as a slot and the row still lights up. */
.ph-scope .ph-compare__table thead tr th.ph-compare__lab,
.ph-scope .ph-compare__table thead tr th.ph-compare__col,
.ph-scope .ph-compare__table tbody tr th.ph-compare__lab,
.ph-scope .ph-compare__table tbody tr td.ph-compare__val{background:#fff;}
.ph-scope .ph-compare__table thead tr th.ph-compare__col--add,
.ph-scope .ph-compare__table tbody tr td.ph-compare__val--add{background:var(--wash);}

/* A row highlight is the whole point of a comparison table: it keeps the eye
   on one spec while it travels sideways. */
.ph-scope .ph-compare__table tbody tr:hover th.ph-compare__lab,
.ph-scope .ph-compare__table tbody tr:hover td.ph-compare__val{background:var(--wash);}
.ph-compare__val--none{color:#9AA5A2;}

/* "Also specified" — the union of what the compared products publish beyond
   the category's fixed skeleton. It is a heading row and not a caption or a
   colspan because every row in this table has to carry the same cell count:
   the label column is position:sticky, and a short row lets the product
   columns slide under it at sub-pixel scroll positions. So the heading takes
   the label cell, the product cells ride along empty, and the separation is
   carried by weight and a rule rather than by structure. */
.ph-compare__grouprow th.ph-compare__group{
  padding-top:var(--s5);font-family:var(--font-display);font-size:13px;
  font-weight:700;letter-spacing:.02em;color:var(--muted);
}
.ph-compare__grouprow td.ph-compare__val--group{padding-top:var(--s5);}
.ph-scope .ph-compare__table tbody.ph-compare__also tr.ph-compare__grouprow th.ph-compare__lab,
.ph-scope .ph-compare__table tbody.ph-compare__also tr.ph-compare__grouprow td.ph-compare__val{background:#fff;}
/* No hover highlight on the heading row: it is not a spec, and lighting it up
   invites the click it does not answer. */
.ph-scope .ph-compare__table tbody tr.ph-compare__grouprow:hover th.ph-compare__lab,
.ph-scope .ph-compare__table tbody tr.ph-compare__grouprow:hover td.ph-compare__val{background:#fff;}

/* Printed only when a gap exists — see ph_compare_markup(). Sits outside the
   scroll container so it stays put while the table moves sideways. */
.ph-compare__note{
  margin:var(--s3) 0 0;font-size:13px;line-height:1.5;color:var(--muted);
}

.ph-compare__acts{
  display:flex;align-items:center;justify-content:space-between;gap:var(--s4);
  margin-top:var(--s5);flex-wrap:wrap;
}
.ph-compare__clear{color:var(--muted);}
.ph-compare__clear:hover{color:var(--green-deep);}

/* Replace prompt. A native confirm() would be correct semantically and wrong
   in every other way — it steals focus and freezes the page. This asks in
   place, inside the thing being changed. */
.ph-tray__ask{
  position:absolute;inset:0;z-index:1;display:none;
  align-items:center;justify-content:center;gap:var(--s4);
  padding:var(--s3) var(--s5);background:#fff;text-align:center;
}
.ph-tray.is-asking .ph-tray__ask{display:flex;}
.ph-tray.is-asking .ph-tray__inner{visibility:hidden;}
.ph-tray__ask p{margin:0;font-size:14px;color:var(--ink);}
.ph-tray__ask .ph-btn{white-space:nowrap;}

@media (max-width:768px){
  .ph-compare__table th,
  .ph-compare__table td{padding:var(--s3);}
  .ph-compare__lab{min-width:118px;width:118px;font-size:13px;}
  .ph-compare__col{min-width:148px;width:148px;}
  .ph-compare__acts{flex-direction:column;align-items:flex-start;gap:var(--s3);}
  .ph-tray__ask{flex-direction:column;gap:var(--s2);justify-content:center;}
}

/* ==========================================================================
   HOME
   --------------------------------------------------------------------------
   The front page was the last surface outside the design system. Its old
   wrappers (ph-hero-in / ph-hero-txt / ph-hero-art / ph-btn-ghost) had no
   rules anywhere and its outer .ph-hero was colliding with the product page's
   grid; everything below is namespaced .ph-home__ so that cannot recur.
   ========================================================================== */
.ph-home{--sec:96px;padding:0 0 var(--s9);}

/* --- Hero --- */
.ph-home__hero{padding:var(--s8) 0 var(--sec);}
.ph-home__hero-in{display:grid;grid-template-columns:6fr 6fr;gap:var(--s8);align-items:center;}
.ph-home__strap{margin:0 0 var(--s3);}
.ph-scope .ph-home__hero-txt h1{margin:0;font-size:clamp(34px,4.2vw,52px);line-height:1.08;letter-spacing:-0.02em;}
.ph-home__lede{margin:var(--s4) 0 0;max-width:46ch;font-size:17px;line-height:1.6;color:var(--muted);}
.ph-home__cta{display:flex;flex-wrap:wrap;gap:var(--s3);margin:var(--s6) 0 0;}

/* --- Hero slider --- */
/* Ambient, not navigational: crossfade rather than translate, dots rather
   than arrows. The artwork alone rotates — the headline never moves. */
.ph-hslider{position:relative;--fade:600ms;}
.ph-hslider__stage{
  position:relative;aspect-ratio:4/3;border-radius:var(--radius);overflow:hidden;
  background:var(--wash-green);
}
.ph-hslider__slide{
  position:absolute;inset:0;opacity:0;
  transition:opacity var(--fade) var(--ease-inout);
  pointer-events:none;
}
.ph-hslider__slide.is-active{opacity:1;pointer-events:auto;}
.ph-scope .ph-hslider__slide img{
  display:block;width:100%;height:100%;object-fit:cover;object-position:center;
}
.ph-hslider__cap{
  position:absolute;left:var(--s4);bottom:var(--s4);z-index:2;
  display:inline-flex;align-items:center;gap:6px;
  padding:8px var(--s4);border-radius:999px;
  background:rgba(255,255,255,.92);border:1px solid var(--hairline);
  font-size:13px;font-weight:600;color:var(--ink);text-decoration:none;
  transition:background var(--dur-fast) var(--ease-out),color var(--dur-fast) var(--ease-out),transform var(--dur-fast) var(--ease-out);
}
.ph-hslider__cap svg{opacity:.55;transition:transform var(--dur-fast) var(--ease-out);}
.ph-hslider__cap:hover{background:#fff;color:var(--green-deeper);transform:translateY(-1px);}
.ph-hslider__cap:hover svg{transform:translateX(3px);opacity:1;}
.ph-hslider__dots{
  position:absolute;right:var(--s4);bottom:var(--s4);z-index:2;
  display:flex;align-items:center;gap:6px;
}
.ph-scope .ph-hslider__dot{
  width:8px;height:8px;padding:0;border:0;border-radius:999px;cursor:pointer;
  background:rgba(26,26,26,.25);
  transition:width var(--dur-base) var(--ease-inout),background var(--dur-base) var(--ease-out);
}
.ph-scope .ph-hslider__dot:hover{background:rgba(26,26,26,.45);}
.ph-scope .ph-hslider__dot.is-active{width:20px;background:var(--green-deep);}

/* --- Trust strip --- */
.ph-home__trust{border-block:1px solid var(--hairline);background:var(--wash);}
.ph-scope .ph-home__trust-list{
  display:grid;grid-template-columns:repeat(4,1fr);gap:var(--s4);
  list-style:none;margin:0;padding:var(--s5) 0;
}
.ph-scope .ph-home__trust-list li{
  display:flex;align-items:center;gap:10px;margin:0;
  font-size:14px;font-weight:600;color:var(--ink);
}
.ph-home__trust-glyph{flex:0 0 auto;display:grid;place-items:center;width:22px;height:22px;color:var(--green-deep);}
.ph-home__trust-glyph svg{width:22px;height:22px;display:block;}

/* --- Section rhythm --- */
.ph-home__sec{padding:var(--sec) 0;}
.ph-home__sec--wash{background:var(--wash-green);}
.ph-home__sec-head{
  display:flex;align-items:baseline;justify-content:space-between;gap:var(--s4);
  margin-bottom:var(--s6);
}
.ph-scope .ph-home__sec-head h2{margin:0;}

/* --- Category grid --- */
/* minmax(0,1fr), never 1fr: a bare 1fr is minmax(auto,1fr) and floors at the
   track's min-content, so a card whose glyph plus longest word exceeds its
   share pushes the track past its share and the row overflows its own
   container silently — no scrollbar, just clipped cards. Standing rule for
   every grid in this file that holds words. */
.ph-scope .ph-home__cats{
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--s4);
  list-style:none;margin:0;padding:0;
}
.ph-scope .ph-home__cats li{margin:0;}
.ph-home__cat{
  display:grid;grid-template-columns:auto 1fr;column-gap:var(--s3);
  align-items:center;align-content:center;height:100%;
  padding:var(--s4);border:1px solid var(--hairline);border-radius:var(--radius-sm);
  background:#fff;text-decoration:none;color:var(--ink);
  transition:border-color var(--dur-fast) var(--ease-out),transform var(--dur-fast) var(--ease-out),box-shadow var(--dur-fast) var(--ease-out);
}
.ph-home__cat:hover{border-color:var(--green);transform:translateY(-2px);box-shadow:0 8px 24px rgba(26,26,26,.06);color:var(--ink);}
.ph-home__cat-glyph{grid-row:1 / span 2;display:grid;place-items:center;width:40px;height:40px;border-radius:10px;background:var(--wash-green);color:var(--green-deep);}
.ph-home__cat-glyph svg{width:24px;height:24px;display:block;}
.ph-home__cat-name{grid-column:2;display:block;min-width:0;font-weight:600;font-size:15px;line-height:1.3;}
.ph-home__cat-count{grid-column:2;display:block;min-width:0;font-size:12px;color:var(--muted);margin-top:2px;}

/* --- Featured grid --- */
.ph-scope .ph-home__grid{grid-template-columns:repeat(4,1fr);}

/* --- Journal --- */
.ph-scope .ph-home__posts{
  display:grid;grid-template-columns:repeat(3,1fr);gap:var(--s5);
  list-style:none;margin:0;padding:0;
}
.ph-scope .ph-home__posts li{margin:0;}
.ph-home__post{display:block;text-decoration:none;color:inherit;
  transition:transform var(--dur-fast) var(--ease-out);}
.ph-home__post:hover{transform:translateY(-3px);color:inherit;}
.ph-home__post-frame{display:block;position:relative;aspect-ratio:16/10;overflow:hidden;
  border-radius:var(--radius-sm);background:var(--wash);margin-bottom:var(--s3);}
.ph-scope .ph-home__post-frame img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;}
.ph-home__post-title{display:block;margin-top:var(--s1);font-family:var(--font-display);
  font-size:17px;line-height:1.35;font-weight:600;}
.ph-home__post:hover .ph-home__post-title{color:var(--green-deeper);}

@media (max-width:1024px){
  .ph-home__hero-in{grid-template-columns:1fr;gap:var(--s6);}
  .ph-scope .ph-home__cats{grid-template-columns:repeat(3,minmax(0,1fr));}
  .ph-scope .ph-home__grid{grid-template-columns:repeat(2,minmax(0,1fr));}
}
@media (max-width:768px){
  .ph-home{--sec:64px;}
  .ph-home__hero{padding:var(--s6) 0 var(--sec);}
  /* The composites are authored with a centred 16:9 safe zone precisely so
     this crop is lossless in the only sense that matters — no product leaves
     the frame. */
  .ph-hslider__stage{aspect-ratio:16/9;}
  .ph-hslider__cap{left:var(--s3);bottom:var(--s3);}
  .ph-hslider__dots{right:var(--s3);bottom:var(--s3);}
  .ph-scope .ph-home__trust-list{grid-template-columns:1fr 1fr;padding:var(--s4) 0;}
  .ph-scope .ph-home__cats{grid-template-columns:repeat(2,minmax(0,1fr));}
  .ph-home__sec-head{flex-direction:column;align-items:flex-start;gap:var(--s2);}
  .ph-scope .ph-home__posts{grid-template-columns:1fr;}
}
@media (max-width:480px){
  .ph-scope .ph-home__grid{grid-template-columns:minmax(0,1fr);}
  .ph-home__cta .ph-btn{flex:1 1 100%;text-align:center;}

  /* Below 480 the two-column cards stop working: half of 360px minus the
     glyph leaves less room than "Blood pressure monitors" needs, and there is
     no honest way to fit it — shrinking the type or breaking the word both
     damage the name, and the names are canonical (the same twelve strings run
     in the drawer, the mega-panel, the shop pills and the breadcrumbs, so
     they cannot be shortened for one surface only).

     So the cards become list rows, the pattern the mobile nav drawer already
     uses for this exact content at this exact width: full name on one line,
     count trailing, whole row a tap target. Taller than the grid, but a
     category index is scanned and left, not dwelt in. */
  .ph-scope .ph-home__cats{
    grid-template-columns:minmax(0,1fr);gap:0;
    border:1px solid var(--hairline);border-radius:var(--radius-sm);
    background:#fff;overflow:hidden;
  }
  .ph-scope .ph-home__cats li + li{border-top:1px solid var(--hairline);}
  .ph-home__cat{
    display:flex;align-items:center;gap:var(--s3);
    min-height:56px;padding:var(--s3) var(--s4);
    border:0;border-radius:0;
  }
  .ph-home__cat:hover{transform:none;box-shadow:none;background:var(--wash-green);}
  /* The panel needs overflow:hidden so the rows' square corners are cut by its
     radius, but that same clip eats the focus ring: the global rule draws a 2px
     outline 2px outside the row box, and the row sits 1px from the panel edge,
     so keyboard focus rendered as two green horizontal lines with no sides.
     Negative offset draws the ring inside the row instead — same weight, same
     colour, nothing to clip. Corners stay square here because the row's own
     radius is 0 in row mode; only the panel is rounded. */
  .ph-home__cat:focus-visible{outline-offset:-2px;border-radius:0;}
  /* No tinted tile in row mode — twelve tiles stacked in a column read as a
     second, competing column of blocks. The glyph keeps the green so the row
     still belongs to the section. */
  .ph-home__cat-glyph{flex:none;width:24px;height:24px;border-radius:0;background:none;}
  .ph-home__cat-glyph svg{width:24px;height:24px;}
  .ph-home__cat-name{flex:1 1 auto;min-width:0;font-size:15px;font-weight:500;}
  .ph-home__cat-count{flex:none;margin-top:0;font-size:14px;}
}

@media (prefers-reduced-motion:reduce){
  /* Auto-advance is off in JS; the dots still work, and the swap is instant.
     A control that does nothing is worse than a control that is blunt. */
  .ph-hslider__slide{transition:none !important;}
}
