/* ============================================================
   Ink & Intuition - Ways
   Canonical source: Final Handoff/Ink & Intuition Ways v3.dc.html.
   The rich glass only sits over this section's own watercolour field;
   it deliberately does not become a global card treatment.
   ============================================================ */

.ways {
  --ways-pink: #ec5f9c;
  --ways-pink-deep: #963264;
  --ways-violet: #7b6fd0;
  --ways-violet-deep: #46368c;
  --ways-teal: #2f9e8f;
  --ways-teal-deep: #145a50;

  /* The card deck's own rail. The heading rides the shared .container rail
     instead (see .ways__header), which is why this is a variable now rather
     than padding on the section: the deck stays wide, the text does not. */
  --ways-gutter: clamp(30px, 5.5vw, 90px);

  /* The veiled sheet's own colour, measured off a real browser with
     tools/ways-ambient.mjs: rgb(246, 239, 221) at the seam band at 1440,
     2552 and 390, which is also what the --paper-veil block in tokens.css
     predicts (246.2, 239.2, 221.5).

     .ways__wash paints this edge to edge and it is load-bearing twice over.
     (1) SEAM LIP RULE: this section's top and bottom edges composite to the
     identical colour as the bare lip between chapters, so the lip is never
     left as a bright stripe between two tones. (2) .ways isolates its own
     stacking context and .ways__grain blends soft-light INSIDE it, so
     anywhere this group has painted nothing the grain falls through to flat
     mid-grey - gotcha 7, and the second of its two shipped occurrences.
     --ways-field-0 is the same colour at zero alpha; `transparent` would be
     rgba(0,0,0,0) and darken every fade out of it. */
  --ways-field: rgb(246, 239, 221);
  --ways-field-0: rgba(246, 239, 221, 0);

  /* ============================================================
     THE GLASS PRESETS (2026-08-08). This block, plus the three
     [data-ways-glass-preset] blocks below it, is the whole switch.

     The technique: an SVG feTurbulence -> feGaussianBlur ->
     feDisplacementMap filter (03-ways.html) referenced from the cards'
     backdrop-filter, so the browser's own compositor does real per-pixel
     refraction. It replaced the vendored WebGL LiquidGlass library, which
     captured the DOM behind each card with html-to-image, uploaded it to a
     texture and re-rendered it on a rAF loop. Nothing here runs on a clock.

     A preset is five values. FOUR of them are ordinary CSS and live here as
     custom properties. The FIFTH is the filter reference, and it has to be a
     whole separate <filter> element per preset because feDisplacementMap's
     `scale` and feTurbulence's `baseFrequency` are SVG ATTRIBUTES and cannot
     be driven by a custom property - so --ways-glass-filter switches which
     url(#id) is in force rather than switching a number inside one filter.

     2a is the default (2026-08-20). Daniel saw 1a/1b/1e side by side and
     picked 1e outright ("i like 1e the best for sure") in the same message
     that asked for each card to stop reading as cream; 2a is 1e's optics
     with the one tint alpha step that lets the colour survive the warm
     backdrop, and the block below explains why that step is not optional.
     Declared right here on .ways rather than only in an attribute block so
     the section still has a complete, correct surface with JavaScript
     disabled or before ways.js runs. 1a was the original default from
     2026-08-08 to 2026-08-20; it, 1b and 1e are all unchanged below and
     still reachable on ?glass=, so reverting is one attribute and the
     comparison never stopped existing.

     NOTE, and this is the trap that cost this pass a whole cycle: changing
     THESE values alone does not change the default. The markup authors
     data-ways-glass-preset on the section (03-ways.html) and ways.js writes
     it again at load from DEFAULT_GLASS_PRESET, and an attribute block
     below beats this bare rule. All three must name the same preset.

     HOW TO SWITCH, by hand, on the live site, with no rebuild:
       ?glass=1a  ?glass=1b  ?glass=1e  ?glass=2a  (ways.js sets the attr)
       ?glasslab=1                        adds a small on-page switcher
     Everything else is unchanged between presets: hue, layout, copy,
     shadows, bevel, tilt, glare, rim glow. */
  --ways-glass-filter: url(#ways-glass-distort-1e);
  --ways-glass-blur: 26px;
  --ways-glass-saturate: 1.16;
  --ways-glass-grain-opacity: 0.05;
  /* The three stops of every card's own tint gradient. Only the ALPHA moves
     between presets; each card keeps its own hue (pink / violet / mint
     white), set in .ways-card-item--* below - except on 2a, which also
     lifts the hue, in its own scoped block. */
  --ways-glass-tint-1: 0.38;
  --ways-glass-tint-2: 0.26;
  --ways-glass-tint-3: 0.32;

  position: relative;
  isolation: isolate;
  /* clip, not hidden (2026-07-30, R3, tmp/briefs/R3-ways-resize.md): the
     field-banding fix in ways.js can make .ways__field (a direct child)
     taller in CSS px than .ways's own real box, by design - see resize() in
     ways.js. overflow:hidden still clips that visually, but it ALSO makes
     .ways a genuine (if invisible) scroll container the instant a child's
     scrollHeight exceeds its clientHeight - and something as ordinary as
     scrollIntoView() on ANY descendant (a card, a focused control) can then
     scroll .ways's own hidden scrollTop to help bring that descendant into
     view, silently shifting the coordinate space every position:absolute
     child in here is measured against. Measured directly: scrollIntoView()
     on a card set .ways.scrollTop to exactly the field's own slack amount
     (66px) and shifted the field 66px out of alignment with .ways's real
     top edge - not a CSS resolution quirk, a real scroll side effect.
     overflow:clip clips identically but is never a scroll container, so it
     cannot happen. No known behaviour depends on .ways being scrollable. */
  overflow: clip;
  /* 128 / 136 at desktop, against the shared major-section band. Was
     144 / 151.2 at 1440, which read as a chapter with more air than the
     hero. Inline padding moved to --ways-gutter on .ways__inner so the
     header can escape it and sit on the shared rail. */
  padding: clamp(112px, 8.9vw, 128px) 0 clamp(118px, 9.4vw, 136px);
  /* No background colour of its own: .ways__wash below is the opaque base. */
  background: transparent;
}

/* 1a ORIGINAL DEFAULT, 2026-08-08 to 2026-08-20. No longer declared on the
   bare .ways rule above (1e replaced it there, GLASS-1E) - kept here in full
   so ?glass=1a is still a real, complete preset rather than a state nothing
   produces any more, all three presets still read as one table, and
   reverting the default is copying these five lines back up. */
.ways[data-ways-glass-preset='1a'] {
  --ways-glass-filter: url(#ways-glass-distort-1a);
  --ways-glass-blur: 20px;
  --ways-glass-saturate: 1.5;
  --ways-glass-grain-opacity: 0.16;
  --ways-glass-tint-1: 0.52;
  --ways-glass-tint-2: 0.38;
  --ways-glass-tint-3: 0.46;
}

/* 1b SHARPER. Less blur, so you read the field through the card rather than
   over it. Displacement drops with it (70 -> 50): the same displacement over
   a much sharper backdrop reads as a smeared photo instead of as glass. */
.ways[data-ways-glass-preset='1b'] {
  --ways-glass-filter: url(#ways-glass-distort-1b);
  --ways-glass-blur: 8px;
  --ways-glass-saturate: 1.4;
  --ways-glass-grain-opacity: 0.16;
  --ways-glass-tint-1: 0.36;
  --ways-glass-tint-2: 0.24;
  --ways-glass-tint-3: 0.32;
}

/* 1e CLEARER, AND THE DEFAULT since 2026-08-20 (GLASS-1E) - the bare .ways
   rule at the top of this file now carries these same five values, so no
   query string and ?glass=1e render identically. Declared again here anyway
   so the preset table stays complete and self-documenting. The thinnest
   tint of the three, so the refraction and the deeper blur have to carry
   the whole surface - hence the higher saturate and the much larger
   displacement. Grain drops to .05 because there is far less white veil
   left for it to sparkle against. */
.ways[data-ways-glass-preset='1e'] {
  --ways-glass-filter: url(#ways-glass-distort-1e);
  --ways-glass-blur: 26px;
  --ways-glass-saturate: 1.75;
  --ways-glass-grain-opacity: 0.05;
  --ways-glass-tint-1: 0.26;
  --ways-glass-tint-2: 0.16;
  --ways-glass-tint-3: 0.22;
}

/* 2a 1e's OPTICS, A TINT THICK ENOUGH TO CARRY COLOUR. The default since
   2026-08-20 (second pass). This preset exists because of a measured
   conflict between two things Daniel asked for in the same breath: he
   picked 1e ("i like 1e the best for sure") AND asked that each card stop
   reading as "saturated cream colour". At 1e's tint alphas those two cannot
   both be true, and the arithmetic says why rather than the eye: the card
   face is alpha*tint + (1-alpha)*backdrop, so 1e's .16 body stop lets 84% of
   the backdrop through, and the backdrop here is the warm cream field
   (--ways-field rgb(246,239,221)) plus .ways__wash. Shot at 1440 with the
   per-card hue lifted to 35% and even 60% accent, the middle and right
   cards still resolved to peach - see
   output/ways-1e-hue-variants/v20..v60-1440.png. Nothing "gentle" wins
   against 84% warm backdrop; only alpha does.
   So 2a keeps every optical property Daniel actually judged 1e by - the
   same #ways-glass-distort-1e displacement, blur 26px, saturate 1.75, card
   grain .05 - and moves ONLY the three tint alphas, one notch up to 1b's
   (.38/.26/.32, still well under 1a's .52/.38/.46). Blur, refraction and
   the near-absent grain are what made 1e read as glass rather than paper;
   none of them move here.
   1a, 1b and 1e are untouched and still reachable on ?glass=, so the
   comparison he judged still exists and the revert is one attribute. */
.ways[data-ways-glass-preset='2a'] {
  --ways-glass-filter: url(#ways-glass-distort-1e);
  --ways-glass-blur: 26px;
  /* SATURATE 1.75 -> 1.16 (2026-08-20, second pass). Daniel: "reduce
     saturation on those elements, the glassiness of the about section is
     the right idea". About runs saturate 1.16 on its own glass, deliberately
     brought down from 1.55 (about.css ~667) for the same reason. Daniel, seeing the
     first cut: "just want to reduce a little to match closer to about me
     glass card. but slightly different because we have a gradient and edge
     glow. i like those keep those" - so this sits ON About's number rather
     than near it, and the gradient and the rim are deliberately untouched.
     This does double duty. saturate() here does not touch the card's own
     tint - it multiplies the SATURATION OF THE FIELD BEHIND the glass, and
     that field is animated: ways.js's shader walks a violet/pink/coral/amber
     brand ramp on the clock (brandRamp(m*0.92 + uTime*0.0055), plus a wash
     term on uTime*0.043). Measured on the therapy card at 1440, sampling the
     face every 2s for a minute: green swings 14 units peak to trough on 1a
     and further on the thinner presets, which is the "cards start off
     rendering correctly then something goes over the top and changes the
     look" report. Hiding .ways__field killed the drift outright (G -0.6 over
     12s against -14.3 with it on) while hiding .ways__grain and .ways__wash
     changed nothing, so the field is the cause and the grain is innocent.
     Turning saturate down shrinks how much of that excursion reaches the
     card face; it does not stop the field animating. */
  --ways-glass-saturate: 1.16;
  --ways-glass-grain-opacity: 0.05;
  --ways-glass-tint-1: 0.38;
  --ways-glass-tint-2: 0.26;
  --ways-glass-tint-3: 0.32;
}

/* 2a's own per-card stops: the same three-stop recipe as the base cards
   below (corner / body / corner, each that card's accent mixed toward
   white), but at 28% accent on the body stop instead of 20%. (Was 35% for one
   deploy; Daniel asked for a little less, so it came down to 28% with the
   ratios held.) Scoped to 2a
   rather than edited into .ways-card-item--* so 1a, 1b and 1e keep the
   exact faces they were compared at. Ratios: corner 8.4%, body 28%,
   corner 14% - the same 0.30 / 1.0 / 0.50 relationship the base stops
   use, scaled up together so the gradient still reads as light catching
   glass rather than a flat fill. Body copy measured 8.6:1 or better on all
   three at 1440 and 390 (AA floor 4.5:1). */
.ways[data-ways-glass-preset='2a'] .ways-card-item--therapy {
  --ways-card-bg: linear-gradient(158deg, rgba(253, 242, 247, var(--ways-glass-tint-1)), rgba(250, 210, 227, var(--ways-glass-tint-2)) 58%, rgba(252, 233, 241, var(--ways-glass-tint-3)));
}

.ways[data-ways-glass-preset='2a'] .ways-card-item--families {
  --ways-card-bg: linear-gradient(158deg, rgba(244, 243, 251, var(--ways-glass-tint-1)), rgba(218, 215, 242, var(--ways-glass-tint-2)) 58%, rgba(237, 235, 248, var(--ways-glass-tint-3)));
}

/* Mint over warm cream is the one most at risk of reading sage or olive
   (see the note on .ways-card-item--clinicians below). Checked at this
   ratio specifically: it holds as mint. */
.ways[data-ways-glass-preset='2a'] .ways-card-item--clinicians {
  --ways-card-bg: linear-gradient(158deg, rgba(238, 247, 246, var(--ways-glass-tint-1)), rgba(197, 228, 224, var(--ways-glass-tint-2)) 58%, rgba(226, 241, 239, var(--ways-glass-tint-3)));
}

/* 2a ON THE PHONE. The coarse-pointer card does NOT use --ways-card-bg at
   all: it paints --ways-card-mobile-bg instead (see the block at
   html[data-pointer="coarse"] .ways-card near the end of this file), which
   runs .78/.64/.72 alpha for legibility over a surface with no field
   behind it. So every desktop tint decision above reaches the phone as
   exactly nothing - shot and confirmed at 390 before this block existed,
   where the cards were still near-white while 1440 read pink / violet /
   mint.
   Mixed at 12% accent on the body stop, not 2a's 28%, because the alpha is
   2.5x thicker here: 0.64 * 15% and 0.26 * 35% put the same amount of
   colour on the glass (deviation from white within 1 unit per channel on
   all three cards), so the phone and the desktop read as the same design
   rather than two. The corner radial accent each card already carried is
   untouched. Scoped to 2a so 1a, 1b and 1e keep their phone faces too. */
/* THINNER AND LESS BLURRED ON THE PHONE (2026-08-20, third pass), because
   the state Daniel likes turned out to be the one that was BROKEN.

   Driving his own Find N over USB established that the card's
   backdrop-filter was inert until something forced a re-raster; a JS nudge
   fixed that and measured it fixed (grain 0.933 with the nudge against 1.571
   without, on his handset). He then looked at the fixed build and said:
   "when it first comes in it's like i can sort of see behind it the textures
   and then it pops way more opaque on it's own sometimes or definitely when
   i press it", and, of the corrected state, "drops the render and it goes
   back to flat card".

   Both halves of that are right, and together they say the fix was aimed the
   wrong way. The see-through look he likes IS the un-filtered state - the
   paper behind showing through unblurred. The "flat card" is what a 26px
   blur does over a mobile background already running .78/.64/.72 alpha: it
   smears the only thing behind the glass into an even wash, so the card
   stops reading as glass and starts reading as paint. The bug worth fixing
   is the INCONSISTENCY (it popped on its own, sometimes), not the
   transparency.

   So: keep the nudge, which makes every card render the same way every time,
   and retune what that consistent state looks like. Alphas come down to
   .56/.42/.50 so there is genuinely something behind the glass to see, and
   the phone's blur drops from 26px to 14px so what is behind stays legible
   as texture rather than being averaged away. Desktop is untouched - it has
   a live WebGL field behind the cards and never had this problem. */
.ways[data-ways-glass-preset='2a'] .ways-card-item--therapy {
  --ways-card-mobile-bg:
    radial-gradient(120% 82% at 2% 0%, rgba(231, 87, 150, 0.18), rgba(231, 87, 150, 0) 56%),
    linear-gradient(158deg, rgba(254, 249, 251, 0.56), rgba(253, 236, 243, 0.42) 58%, rgba(254, 245, 249, 0.50));
}

.ways[data-ways-glass-preset='2a'] .ways-card-item--families {
  --ways-card-mobile-bg:
    radial-gradient(120% 82% at 2% 0%, rgba(126, 106, 216, 0.18), rgba(126, 106, 216, 0) 56%),
    linear-gradient(158deg, rgba(250, 250, 253, 0.56), rgba(239, 238, 249, 0.42) 58%, rgba(247, 246, 252, 0.50));
}

.ways[data-ways-glass-preset='2a'] .ways-card-item--clinicians {
  --ways-card-mobile-bg:
    radial-gradient(120% 82% at 2% 0%, rgba(48, 151, 136, 0.18), rgba(48, 151, 136, 0) 56%),
    linear-gradient(158deg, rgba(248, 252, 251, 0.56), rgba(230, 243, 242, 0.42) 58%, rgba(243, 249, 248, 0.50));
}

/* The phone's blur, and ONLY the phone's. 26px is right on desktop, where a
   live WebGL field sits behind the glass and a deep blur reads as depth. On
   a phone the only thing behind the card is paper, and 26px turns paper into
   a flat tone - the "flat card" above. 14px still softens it into glass
   without erasing it. ?waysflat=1 restores the old thick/deep pair so the
   two can be compared on the device without a rebuild. */
html[data-pointer="coarse"] .ways[data-ways-glass-preset='2a'] {
  --ways-glass-blur: 14px;
}

html[data-ways-flat="1"][data-pointer="coarse"] .ways[data-ways-glass-preset='2a'] {
  --ways-glass-blur: 26px;
}

html[data-ways-flat="1"] .ways[data-ways-glass-preset='2a'] .ways-card-item--therapy {
  --ways-card-mobile-bg:
    radial-gradient(120% 82% at 2% 0%, rgba(231, 87, 150, 0.16), rgba(231, 87, 150, 0) 56%),
    linear-gradient(158deg, rgba(254, 249, 251, 0.78), rgba(253, 236, 243, 0.64) 58%, rgba(254, 245, 249, 0.72));
}

html[data-ways-flat="1"] .ways[data-ways-glass-preset='2a'] .ways-card-item--families {
  --ways-card-mobile-bg:
    radial-gradient(120% 82% at 2% 0%, rgba(126, 106, 216, 0.16), rgba(126, 106, 216, 0) 56%),
    linear-gradient(158deg, rgba(250, 250, 253, 0.78), rgba(239, 238, 249, 0.64) 58%, rgba(247, 246, 252, 0.72));
}

html[data-ways-flat="1"] .ways[data-ways-glass-preset='2a'] .ways-card-item--clinicians {
  --ways-card-mobile-bg:
    radial-gradient(120% 82% at 2% 0%, rgba(48, 151, 136, 0.16), rgba(48, 151, 136, 0) 56%),
    linear-gradient(158deg, rgba(248, 252, 251, 0.78), rgba(230, 243, 242, 0.64) 58%, rgba(243, 249, 248, 0.72));
}

/* Referenced by id, never rendered. Kept out of layout and out of the
   accessibility tree; ways.js does not touch it. */
.ways__filters {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
  pointer-events: none;
}

.ways__wash,
.ways__blob,
.ways__grain {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* .ways__wash and .ways__blob are declared here but live inside .ways__grid
   in the markup, not as section-level children (it was the LiquidGlass root
   until 2026-08-08; it is now just where the cards' backdrop is painted). Removing position/z-index from .ways__inner and .ways__grid
   (below) leaves .ways as the nearest positioned ancestor, so inset:0 still
   resolves against the full section box exactly as it would at this level. */
/* The section's whole opaque base, in four jobs, top layer first.

   The v3 watercolour photograph used to be all of it: full bleed, opacity 1,
   under the heading as much as under the deck. Measured at 1440 it cost the
   heading band 40 luma against the shared sheet (rgb(216,192,194) L 197.5 vs
   rgb(246,239,221) L 239.1), which is most of why this chapter read as a
   different, cooler website than the hero. It is now veiled back to the
   shared field at both edges and concentrated into the card band, where it
   is the richest thing the glass has to refract and therefore earns its
   place. */
.ways__wash {
  z-index: -4;
  background-color: var(--ways-field);
  background-image:
    /* 1. A breath of warmth around the opened-up heading, ABOVE the veil, so
          the calm top still belongs to the same luminous world as the hero.
          -1 strength: this runs wide, and tokens.css records what a -3 costs
          when it is not a radial core. */
    radial-gradient(74% 30% at 28% 19%, var(--atmos-pink-1), var(--atmos-pink-0) 72%),
    radial-gradient(62% 26% at 84% 13%, var(--atmos-amber-1), var(--atmos-amber-0) 74%),
    /* 2. The veil that concentrates everything below it into the card band.
          Fully opaque --ways-field at 0% and 100%, so this section's own top
          and bottom edges composite to the bare lip's colour exactly and the
          seam is never a stripe between two tones. The ramps are 27% and 22%
          of the section height, about 280px and 230px at 1440 against a
          16-26px lip. */
    linear-gradient(to bottom,
      var(--ways-field) 0%,
      var(--ways-field) 14%,
      var(--ways-field-0) 42%,
      var(--ways-field-0) 78%,
      var(--ways-field) 100%),
    /* 3. The pigment landscape the deck sits in, in the section's three
          colour states: pink under Therapy, violet under Families, teal with
          a restrained amber edge under Clinicians. */
    radial-gradient(56% 40% at 17% 62%, var(--atmos-pink-2), var(--atmos-pink-0) 70%),
    radial-gradient(54% 38% at 50% 58%, var(--atmos-violet-2), var(--atmos-violet-0) 68%),
    radial-gradient(52% 38% at 82% 62%, var(--atmos-teal-2), var(--atmos-teal-0) 68%),
    radial-gradient(34% 28% at 98% 52%, var(--atmos-amber-2), var(--atmos-amber-0) 72%),
    /* 4. Cream lift over the photograph. It keeps the watercolour the cards
          refract while taking roughly 40% out of its chroma, which is the
          "reduce the ribbon's saturation" half of this pass. */
    linear-gradient(to bottom,
      var(--atmos-cream-0) 30%,
      var(--atmos-cream-3) 50%,
      var(--atmos-cream-3) 74%,
      var(--atmos-cream-0) 94%),
    /* 5. The v3 watercolour itself. */
    url('../../assets/ways/ways-wash.jpg');
  background-position: center;
  background-size: cover;
  background-repeat: no-repeat;
  opacity: 1;
}

/* CSS-only ribbon. Until the 2026-07-27 ambient transplant this hosted a
   glass-blob2 WebGL ribbon (ways.js); that mount is gone now, replaced by the
   wet glass field (.ways__field, see ways.js and the rule below), which
   paints across the whole section and would have hidden this ribbon anyway
   wherever it is running. What is left is exactly the CSS-only surface this
   always was underneath that WebGL layer: it now IS the fallback, shown
   whenever .ways__field is not (WebGL2 unavailable, a context loss that
   never restored, a constrained device, or prefers-reduced-motion, where it
   is held at its neutral frame rather than animated - see the reduced-motion
   block below), so the section is never colourless.

   It used to be two unrelated shapes at 34% and bottom 8%, drifting on
   separate cycles, which is what made the section read as scattered blobs
   rather than one liquid. It is now one band with a highlight riding its
   upper edge, both concentrated in the card band and both breathing on the
   same slow viscosity cycle. */
.ways__blob {
  z-index: -2;
  overflow: hidden;
  opacity: 1;
}

.ways__blob::before,
.ways__blob::after {
  content: '';
  position: absolute;
  inset: auto;
  pointer-events: none;
}

.ways__blob::before {
  width: min(118vw, 1780px);
  height: clamp(160px, 23vw, 340px);
  top: 38%;
  left: -8vw;
  border-radius: 46% 54% 40% 60% / 58% 38% 62% 42%;
  background:
    radial-gradient(50% 108% at 15% 50%, var(--atmos-pink-3), var(--atmos-pink-0) 74%),
    radial-gradient(50% 112% at 49% 48%, var(--atmos-violet-3), var(--atmos-violet-0) 72%),
    radial-gradient(44% 106% at 83% 54%, var(--atmos-teal-3), var(--atmos-teal-0) 72%),
    radial-gradient(26% 100% at 99% 44%, var(--atmos-amber-2), var(--atmos-amber-0) 74%);
  filter: blur(30px);
  transform: rotate(-2.5deg);
  animation: ways-viscosity var(--dur-ambient-slow) var(--ease-ambient) infinite;
}

.ways__blob::after {
  width: min(74vw, 1040px);
  height: clamp(80px, 11vw, 170px);
  top: 40%;
  left: 6vw;
  border-radius: 58% 42% 54% 46% / 62% 46% 54% 38%;
  background: radial-gradient(58% 74% at 46% 40%, var(--atmos-cream-3), var(--atmos-cream-0) 74%);
  filter: blur(26px);
  transform: rotate(-2.5deg);
  animation: ways-viscosity var(--dur-ambient) var(--ease-ambient) infinite reverse;
}

/* The wet glass ambient field (2026-07-27 transplant, ways.js). A direct
   child of .ways__grid from first paint. THIS IS WHAT THE GLASS REFRACTS -
   the live watercolour behind the cards - and it is the reason the effect is
   worth having; do not remove it when tidying the section's layers.
   Same inset:0 box as .ways__wash (every ancestor between here and .ways,
   the nearest positioned one, is a plain unpositioned wrapper), and it sits
   above both .ways__wash and .ways__blob so it fully replaces them as the
   backdrop the cards refract whenever it is running - see .is-down below for
   the path where it is not. */
.ways__field {
  position: absolute;
  inset: 0;
  z-index: -1;
  display: block;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* WebGL2 unavailable, shader compile failed, a lost context that never
   restored, or the constrained-device gate in ways.js never created a
   context at all: hide the canvas outright rather than show a dead or blank
   surface. .ways__wash and .ways__blob's own CSS ribbon (below) are always
   in the DOM regardless, so this is never a blank section, just a calmer,
   static one. */
.ways__field.is-down { display: none; }

/* Reuses the single sitewide grain asset rather than adding a competing
   fixed or global overlay. It keeps the shared tile size and soft-light
   finish, but at a lower local opacity: the darker watercolour field gives
   soft-light substantially more contrast than the pale paper beneath the
   other chapters, so the shared .30 value reads like a separate heavy paper
   stock here. .10 is deliberately a much quieter finish: enough to keep the
   section's paper in the header and card gaps, without laying a visible veil
   over the richer watercolour field. Coarse-pointer devices still remove this
   layer entirely in base.css. The URL deliberately
   stays local because a relative url() inside a custom property resolves
   against the stylesheet that USES it (trap 2). */
.ways__grain {
  z-index: 30;
  background: url('../../assets/img/grain.png') center / var(--grain-size) repeat;
  mix-blend-mode: var(--grain-blend);
  opacity: 0.10;
}

/* Deliberately not positioned: .ways already isolates its own stacking
   context, so this and .ways__grid can stay plain wrappers and let the
   wash/blob (nested inside .ways__grid, see above) keep resolving inset:0
   against the section itself instead of against a narrower ancestor box.

   The inline padding lives here rather than on .ways so the header can sit
   outside it on the shared rail. max-width is 1320 PLUS the gutter on each
   side, which reproduces the deck's previous box exactly: 1266.6px at
   x=79.2 at 1440, 1320px at x=608.5 at 2552, both measured. */
.ways__inner {
  max-width: calc(1320px + 2 * var(--ways-gutter));
  margin-inline: auto;
  padding-inline: var(--ways-gutter);
}

/* The eyebrow and heading ride the shared .container rail (the class is on
   the element, so there is one definition of that rail, in base.css) and the
   header is a direct child of .ways rather than of .ways__inner so the
   deck's wider gutter cannot pull it left. Measured before this change, the
   Ways text started 67.3px left of every other section's text at 1440 and
   94.0px left at 2552. Do not add a max-width here: .container supplies it,
   and the measure cap belongs on the heading. */
.ways__header { margin-block: 0; }

.ways__eyebrow {
  margin: 0;
  color: var(--pink-link);
  font-family: var(--font-mono);
  font-size: var(--text-eyebrow);
  font-weight: 400;
  letter-spacing: 0.28em;
  line-height: 1.45;
  text-transform: uppercase;
}

/* 60px at desktop, against the hero H1's 60px cap. It computed 71.2px across
   the entire desktop range before this pass, which is the single reason the
   chapter read as a second hero. margin-top is the shared 18-24px eyebrow
   gap Approach and About use; it was 38px. */
.ways__heading {
  max-width: 850px;
  margin: 20px 0 0;
  color: var(--ink-900);
  font-size: clamp(2.9rem, 5.15vw, 3.75rem);
  line-height: 1.04;
  letter-spacing: -0.025em;
  text-wrap: pretty;
}

/* Was a hand-rolled, darker gradient (#6253b8/#a83566/#a93e29) that matched
   neither of the site's two published text-gradient tokens - Daniel: "the
   fonts gradient are way too dark and not our gradient". --grad-text is the
   one the hero H1 accent and About's H2 accent both use (via the shared
   .grad-text class in base.css), and it is the right one here rather than
   the deeper --grad-text-deep: both are large italic headings in the same
   H1/H2 size class (this heading computes 60px/400 down to 43px/400, never
   smaller than --text-h2's own floor), and tokens.css records --grad-text as
   the one tuned to clear the 3:1 AA large-text minimum for exactly that
   size class, --grad-text-deep for smaller sub-24px emphasis text instead. */
.ways__heading-accent {
  padding-bottom: 0.08em;
  font-style: italic;
  background: var(--grad-text);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.ways__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.03fr) minmax(0, 0.97fr) minmax(0, 1.03fr);
  align-items: stretch;
  gap: clamp(24px, 2.4vw, 36px);
  margin-top: clamp(66px, 6vw, 86px);
}

.ways-card-item {
  --ways-rotate-x: 0deg;
  --ways-rotate-y: 0deg;
  --ways-lift: 0px;
  --ways-scale: 1;
  --ways-reveal-y: 0px;
  position: relative;
  /* PAPER OFF THE CARDS, GRAIN STAYS ON THE SECTION (2026-08-20, GLASS-1E).
     .ways__grain is a section-wide pass at z-index:30 that used to wash the
     cards too: this element and .ways__grain are both direct participants
     in .ways's own isolated stacking context (.ways sets isolation:isolate;
     .ways__inner and .ways__grid are deliberately plain, unpositioned
     wrappers - see the comment on .ways__inner), and a positive z-index
     sibling always paints after a z-index:auto one regardless of DOM order.
     31 is simply "one past 30": nothing else in this file carries a z-index
     between them, so this is the whole fix. .ways__grain itself is
     untouched and still paints above the section background, the header
     and the gaps between cards - proved with a screenshot of the FULL
     section, not just the cards, per the brief (see
     output/ways-1e-tinted-2026-08-20/full-section-after-*.png). Each card
     keeps its own separate, finer grain (.ways-card__grain, z-index 2
     INSIDE .ways-card's own nested stacking context) so the surface still
     reads as material, not a clean pane, without double-dipping into the
     coarser sitewide texture Daniel asked to lose. */
  z-index: 31;
  align-self: stretch;
  min-width: 0;
  border-radius: 28px;
  /* ways.js animates --ways-reveal-y (a custom property, not this transform
     directly) for the scroll entrance, so the value keeps composing with
     the tilt's own transform below instead of one overwriting the other. */
  transform: translateY(var(--ways-reveal-y));
}

/* CARD TINTS ARE NOW PRESET-DRIVEN (2026-08-08). Each card keeps its own
   three hues; only the alpha of the three stops moves, from
   --ways-glass-tint-1/2/3 at the top of this file.

   SUPERSEDED, and worth recording because the numbers changed visibly: this
   file shipped .44/.30/.38 on all three cards, thinner than the v3
   prototype's .52/.38/.46, on the reasoning that the blur behind them had
   gone 23px -> 34px and a wider blur averages the field flat, so the same
   white veil starts reading as milk. That reasoning was sound for a 34px
   blur with no refraction. Preset 1a is the prototype's .52/.38/.46 at 20px
   with real displacement, so on the default preset these cards are now
   MEASURABLY MORE OPAQUE than the build this replaced (+.08 per stop) - a
   deliberate, specified change, not a drift. 1b and 1e go the other way and
   are thinner than either.

   HUE, NOT JUST ALPHA (2026-08-20, GLASS-1E). Daniel, after 1e shipped as
   the default: "is there a way for each card to look less cream by adding
   1E ... a gentle colour overlay so each cards reads less saturated cream
   colour". The three stops below were near-white with only a 2-11 unit
   nudge in one channel (therapy's middle stop, the one covering most of the
   card face, was rgba(255,244,250) - 11 units off white total) - so thin
   that it read as plain cream at every alpha this file ships, 1e's
   thinnest included, because there was barely any hue there for the alpha
   to carry. The fix is not more opacity (that is the "coloured plastic"
   failure this brief explicitly rules out) - it is putting more of each
   card's own accent into the stops that were already there. Each stop below
   is that card's --ways-card-accent mixed toward white at a fixed ratio (corner
   6%, body 20%, corner 10% - the 58%-position middle stop covers most of
   the visible face, so it carries the most colour; the two ends stay close
   to white so the gradient still reads as light catching glass, not a flat
   fill). 20% at the body stop, still 80% white, is the "gentle" ceiling:
   measured body-copy contrast on the actual rendered face stayed
   10.15-13.35:1 (AA floor 4.5:1) at every card and width this touches - see
   the coordinator's contrast table. Alpha (var(--ways-glass-tint-1/2/3),
   set per preset above) is unchanged by this - hue and alpha stay two
   separate levers, exactly as the block comment above already describes. */
.ways-card-item--therapy {
  --ways-card-accent: var(--ways-pink);
  --ways-card-deep: var(--ways-pink-deep);
  --ways-card-bg: linear-gradient(158deg, rgba(254, 245, 249, var(--ways-glass-tint-1)), rgba(251, 223, 235, var(--ways-glass-tint-2)) 58%, rgba(253, 239, 245, var(--ways-glass-tint-3)));
  --ways-card-mobile-bg:
    radial-gradient(120% 82% at 2% 0%, rgba(231, 87, 150, 0.16), rgba(231, 87, 150, 0) 56%),
    linear-gradient(158deg, rgba(255, 252, 249, 0.78), rgba(255, 241, 248, 0.64) 58%, rgba(255, 249, 246, 0.72));
  --ways-card-shadow: rgba(150, 50, 100, 0.3);
  --ways-link: #a83566;
}

.ways-card-item--families {
  --ways-card-accent: var(--ways-violet);
  --ways-card-deep: var(--ways-violet-deep);
  --ways-card-bg: linear-gradient(158deg, rgba(247, 246, 252, var(--ways-glass-tint-1)), rgba(229, 226, 246, var(--ways-glass-tint-2)) 58%, rgba(242, 241, 250, var(--ways-glass-tint-3)));
  --ways-card-mobile-bg:
    radial-gradient(120% 82% at 2% 0%, rgba(126, 106, 216, 0.16), rgba(126, 106, 216, 0) 56%),
    linear-gradient(158deg, rgba(253, 252, 255, 0.78), rgba(244, 242, 255, 0.64) 58%, rgba(251, 249, 255, 0.72));
  --ways-card-shadow: rgba(70, 55, 140, 0.3);
  --ways-link: #5b4cae;
}

.ways-card-item--clinicians {
  --ways-card-accent: var(--ways-teal);
  --ways-card-deep: var(--ways-teal-deep);
  /* This is the card the brief flagged as most likely to go muddy: mint is
     the one cool hue of the three sitting over a warm cream field
     (--ways-field, rgb(246,239,221)), and the card behind it in
     .ways__wash is itself "teal with a restrained amber edge" - a warm
     note already fighting the cool tint before this gradient adds any more.
     Checked directly against that risk (screenshots at
     output/ways-1e-tinted-2026-08-20/card-clinicians-after.png and the
     full-section frame): it reads as mint, not sage/olive, at the same 20%
     body-stop mix ratio as the other two cards, so no extra per-card alpha
     override was needed - see the coordinator's report for what was
     checked before settling on that. */
  --ways-card-bg: linear-gradient(158deg, rgba(243, 249, 248, var(--ways-glass-tint-1)), rgba(213, 236, 233, var(--ways-glass-tint-2)) 58%, rgba(234, 245, 244, var(--ways-glass-tint-3)));
  --ways-card-mobile-bg:
    radial-gradient(120% 82% at 2% 0%, rgba(48, 151, 136, 0.16), rgba(48, 151, 136, 0) 56%),
    linear-gradient(158deg, rgba(251, 255, 253, 0.78), rgba(239, 252, 248, 0.64) 58%, rgba(248, 254, 251, 0.72));
  --ways-card-shadow: rgba(20, 90, 80, 0.3);
  --ways-link: #197264;
}

.ways-card {
  --ways-rotate-x: 0deg;
  --ways-rotate-y: 0deg;
  --ways-lift: 0px;
  --ways-glare-x: 50%;
  --ways-glare-y: 50%;
  position: relative;
  isolation: isolate;
  display: block;
  height: 100%;
  overflow: hidden;
  border-radius: 28px;
  background: var(--ways-card-bg);
  color: inherit;
  text-decoration: none;
  /* THE GLASS. Two declarations, and the ORDER OF THESE TWO LINES IS
     LOAD-BEARING - do not tidy them into one, and do not swap them.

     In Chromium, -webkit-backdrop-filter is an ALIAS for backdrop-filter, not
     a separate property, so writing the prefixed line second would overwrite
     the SVG-filtered value with the plain one and silently delete the whole
     refraction in the browser this was tuned in. Prefixed first, standard
     second, always.

     WEBKIT/SAFARI, an accepted fallback and not a bug: WebKit silently
     ignores a url() filter reference inside backdrop-filter. The prefixed
     line above is written to be exactly the fallback we want it to land on -
     the same blur and saturate as the preset, just without the displacement -
     so Safari and every iOS browser get a clean frosted card with the right
     depth and the right colour, missing only the per-pixel refraction. That
     is why this line carries real values rather than being a copy of the
     standard one.

     CLAUDE.md gotcha 8 is about THIS EXACT SELECTOR: .ways-card shipped a
     blur here and computed `none` in the browser for months, because the
     LiquidGlass JS path overrode it with `backdrop-filter: none` further down
     this file. That override is gone (2026-08-08) and this is the real path
     again. Measured with tools/ways-css-glass.mjs, which reads
     getComputedStyle and never the declaration. */
  -webkit-backdrop-filter: blur(var(--ways-glass-blur)) saturate(var(--ways-glass-saturate));
  backdrop-filter: var(--ways-glass-filter) blur(var(--ways-glass-blur)) saturate(var(--ways-glass-saturate));
  box-shadow:
    0 0 0 1.25px rgba(255, 255, 255, 0.82),
    0 0 0 2.5px color-mix(in srgb, var(--ways-card-accent) 7%, transparent),
    /* Longer and softer than the prototype's 24/54/-22, so the deeper glass
       reads as sitting further off the paper rather than printed on it. */
    0 28px 62px -24px var(--ways-card-shadow),
    0 6px 18px -10px rgba(42, 34, 48, 0.14),
    inset 0 1.5px 1px rgba(255, 255, 255, 0.92),
    inset 0 -1px 1px rgba(255, 255, 255, 0.35),
    inset 3px 0 4px -1px rgba(255, 255, 255, 0.55),
    inset -3px 0 4px -1px rgba(255, 255, 255, 0.45);
  transform: perspective(1100px) rotateX(var(--ways-rotate-x)) rotateY(var(--ways-rotate-y)) translateY(var(--ways-lift)) scale(var(--ways-scale, 1));
  transform-style: preserve-3d;
  transition: transform 0.55s cubic-bezier(0.2, 0.8, 0.25, 1), color var(--dur-quick) var(--ease-brand);
  will-change: transform;
}

.ways-card:hover { color: inherit; }
.ways-card:active { --ways-scale: 0.99; }

.ways-card.is-tilting { transition: transform 0.09s linear, color var(--dur-quick) var(--ease-brand); }

.ways-card__bevel,
.ways-card__glare,
.ways-card__ambient,
.ways-card__grain {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* PER-CARD SPARKLE GRAIN (2026-08-08, handoff section 4). The section already
   has .ways__grain, a full-bleed pass ABOVE the cards at z-index 30; this is a
   different job - a fine soft-light texture INSIDE the glass, so the surface
   reads as a material rather than as a clean pane. Opacity is the preset's
   (.16 on 1a/1b, .05 on 1e).

   Reuses assets/img/grain.png, the single sitewide tile (base.css records that
   it is the only one), at 150px rather than the sitewide --grain-size 180px:
   this tile is being read at card scale, not page scale, and 150px is the
   handoff's own figure. The url() is written out rather than tokenised because
   a relative url() inside a custom property resolves against the stylesheet
   that USES it (ways.css records this as trap 2, and .ways__grain right above
   makes the same choice for the same reason).

   z-index 2 sits it above .ways-card__ambient's colour blobs (1) and below the
   bevel (4), glare (5), content (6) and rim (7).

   mix-blend-mode against a transparent backdrop falls through to the source
   colour (gotcha 7) and this is safe: .ways-card sets isolation:isolate AND
   paints its own --ways-card-bg gradient, so this blends against real paint,
   not against nothing. .ways-card__glare has used soft-light in this same
   stacking context since the section shipped, which is the standing proof. */
.ways-card__grain {
  z-index: 2;
  border-radius: inherit;
  background: url('../../assets/img/grain.png') center / 150px repeat;
  mix-blend-mode: soft-light;
  opacity: var(--ways-glass-grain-opacity);
}

/* Only the CSS-glass path uses this stack; the glass-ready override further
   down replaces it wholesale. The two changes here are both about the deeper
   blur: the broad inner white glow is pulled back (it was the single biggest
   milk contributor once the backdrop went flat) and the accent shading under
   the edges is pushed up to replace that lost definition with colour rather
   than with white. */
.ways-card__bevel {
  z-index: 4;
  border-radius: inherit;
  box-shadow:
    inset 0 0 0 1.5px rgba(255, 255, 255, 0.6),
    inset 0 0 0 3px rgba(255, 255, 255, 0.1),
    inset 0 3px 5px -1px rgba(255, 255, 255, 0.9),
    inset 0 -4px 9px -4px color-mix(in srgb, var(--ways-card-accent) 27%, transparent),
    inset 8px 0 15px -10px color-mix(in srgb, var(--ways-card-accent) 25%, transparent),
    inset -8px 0 15px -10px rgba(255, 255, 255, 0.5),
    inset 0 0 28px rgba(255, 255, 255, 0.17);
}

/* Shared by both paths, so it is tuned once for the deeper blur: a flatter,
   lighter surface takes a soft-light highlight much more readily than the
   thinner glass did, and 0.9 white now blows the tilt highlight out. */
.ways-card__glare {
  z-index: 5;
  border-radius: inherit;
  opacity: 0;
  background: radial-gradient(460px circle at var(--ways-glare-x) var(--ways-glare-y), rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0) 62%);
  mix-blend-mode: soft-light;
  transition: opacity 0.4s var(--ease-brand);
}

.ways-card.is-tilting .ways-card__glare { opacity: 1; }

.ways-card__ambient {
  z-index: 1;
  overflow: hidden;
  border-radius: inherit;
}

.ways-card__ambient > span {
  position: absolute;
  display: block;
  filter: blur(34px);
  opacity: 0.5;
  animation: ways-float-a 24s ease-in-out infinite;
}

.ways-card__ambient > span:first-child {
  top: -46px;
  left: -38px;
  width: 288px;
  height: 256px;
  border-radius: 58% 42% 55% 45% / 52% 46% 54% 48%;
  background: radial-gradient(64% 58% at 42% 38%, var(--ways-card-accent), rgba(255, 255, 255, 0) 72%);
}

.ways-card__ambient > span:last-child {
  right: -30px;
  bottom: -42px;
  width: 210px;
  height: 196px;
  border-radius: 52% 48% 46% 54% / 54% 50% 50% 46%;
  background: radial-gradient(60% 60% at 52% 54%, #f8a06f, rgba(255, 255, 255, 0) 74%);
  filter: blur(28px);
  opacity: 0.42;
  animation-name: ways-float-b;
  animation-duration: 27s;
}

.ways-card-item--families .ways-card__ambient > span:first-child {
  top: -44px;
  right: -36px;
  left: auto;
  background: radial-gradient(64% 58% at 44% 40%, #9385e6, rgba(255, 255, 255, 0) 72%);
  animation-duration: 25s;
}

.ways-card-item--families .ways-card__ambient > span:last-child {
  right: auto;
  bottom: -40px;
  left: -30px;
  background: radial-gradient(60% 60% at 50% 54%, #ec8fb8, rgba(255, 255, 255, 0) 74%);
  animation-duration: 28s;
}

.ways-card-item--clinicians .ways-card__ambient > span:first-child {
  top: auto;
  bottom: -48px;
  left: calc(50% - 150px);
  width: 300px;
  height: 250px;
  border-radius: 54% 46% 56% 44% / 48% 50% 50% 52%;
  background: radial-gradient(62% 58% at 50% 56%, #43b0a0, rgba(255, 255, 255, 0) 72%);
  animation-duration: 26s;
}

.ways-card-item--clinicians .ways-card__ambient > span:last-child {
  top: -40px;
  right: -28px;
  bottom: auto;
  left: auto;
  width: 196px;
  height: 188px;
  background: radial-gradient(60% 60% at 52% 50%, #9ec97a, rgba(255, 255, 255, 0) 74%);
  animation-duration: 29s;
}

/* ============================================================
   RIM GLOW ON HOVER, 2026-08-06. Daniel: "add a small matched colour rim
   glow to the card when you hover each one ... all the way around but
   perhaps a more prominent rotation little section can happen as well.
   Only on desktop."

   Two generated layers on .ways-card ITSELF, not new markup: ::before is
   the quiet, steady ring in the card's own --ways-card-accent (pink /
   violet / teal, already set per card by .ways-card-item--*); ::after is a
   short bright arc on the same ring that sweeps continuously while hovered
   - the "more prominent rotation" half of the ask. Both use the standard
   padding + mask-composite:exclude ring trick, so they paint a THIN 1.5px
   border glow rather than a filled shape and can never sit over the copy.
   Confirmed no existing rule uses .ways-card::before/::after (grepped the
   whole file first) and z-index:7 is deliberately one past
   .ways-card__content's own 6 just above, so the ring is unambiguously the
   topmost layer without depending on generated-content DOM-order tie-break
   rules to get there.

   RECONCILED 2026-08-08 with the CSS/SVG glass handoff, which specified a
   rim-glow ring of its own (its section 6: a conic-gradient span with
   mask-composite:exclude, --rim-color per card, shown from tiltMove/
   tiltLeave). This implementation is the same technique and already carries
   the same three accent colours - #ec5f9c / #7b6fd0 / #2f9e8f arrive here as
   --ways-card-accent from .ways-card-item--* above - so it was kept as-is
   rather than duplicated. Two deliberate differences from the handoff, both
   in this build's favour: it is two generated layers instead of new markup
   (nothing to add to six card variants), and it is driven by :hover in CSS
   instead of by the tilt JS, so it survives that script failing.

   The stacking note that used to sit here described how this rode above the
   injected LiquidGlass canvas. There is no injected canvas any more; the ring
   paints directly on the card's own box, which is the case that always
   needed nothing extra.

   Gated on (hover: hover) and (pointer: fine) - the exact media query the
   tilt pointermove handler below already uses via finePointer - rather than
   a plain :hover, so a touch device that fakes hover support cannot pick
   this up. The :hover selectors below work standalone, with no dependency
   on the tilt JS or its .is-tilting class: data-ways-card sits on .ways-card
   itself (03-ways.html), so this is real progressive enhancement - the glow
   still works if that script ever fails to run.

   VISIBLE AT REST, 2026-08-20 (GLASS-1E). Daniel asked again for this in the
   same message that picked 1e: "keep maybe a nicer glowy rim outline on
   each? just subtle without killing render". Only ::before (the quiet,
   steady ring) is touched: it now paints at all times instead of fading in
   only on hover, with a small static drop-shadow added so it reads as a
   soft glow rather than a hairline. Hovering still brightens it (opacity 1,
   same as before). ::after - the bright sweeping arc and its
   ways-rim-rotate animation - is UNTOUCHED: still opacity:0 and
   animation-play-state:paused until :hover, exactly as shipped
   2026-08-06. That is the one that must never run unattended: animating a
   conic gradient through a mask-composite ring on three cards forever is a
   per-frame repaint, the same mistake the CTA particles were rescued from
   on 2026-08-08 (CLAUDE.md gotcha and this file's own comment above the
   animation, both still correct and still the reason it stays paused). A
   static ::before, never animated, costs nothing per frame - measured with
   tools/ways-perf.mjs alongside every other change in this pass; see the
   coordinator's before/after table. */
@property --ways-rim-angle {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0deg;
}

@media (hover: hover) and (pointer: fine) {
  .ways-card::before,
  .ways-card::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 7;
    border-radius: inherit;
    padding: 1.5px;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.4s var(--ease-brand);
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
  }

  .ways-card::before {
    /* Static "quiet ring", now visible at rest instead of hover-only (see
       the block comment above) - opacity 0.6 so the resting glow reads as
       subtle rather than repeating the full-strength hover moment
       permanently; hover below still brightens it to 1. The drop-shadow is
       new: a soft colour halo rather than a crisp hairline, which is what
       makes it read as "glowy" rather than just an outline. Both are
       static - nothing here is animated or driven by JS, so this costs one
       paint, not one per frame. */
    opacity: 0.6;
    background: color-mix(in srgb, var(--ways-card-accent) 50%, transparent);
    filter: drop-shadow(0 0 5px color-mix(in srgb, var(--ways-card-accent) 32%, transparent));
  }

  /* drop-shadow, not box-shadow: it follows the arc's own alpha shape as it
     sweeps, rather than haloing the whole card evenly the way a box-shadow
     on the full ring would. */
  .ways-card::after {
    background: conic-gradient(from var(--ways-rim-angle, 0deg),
      transparent 0deg,
      color-mix(in srgb, var(--ways-card-accent) 92%, white 8%) 18deg,
      transparent 46deg,
      transparent 360deg);
    filter: drop-shadow(0 0 5px color-mix(in srgb, var(--ways-card-accent) 65%, transparent));
  }

  .ways-card:hover::before,
  .ways-card:hover::after {
    opacity: 1;
  }

  /* Motion (the rotation itself) is separated from colour (the ring fading
     in) so reduced motion keeps the static accent and only loses the spin -
     "reduced motion must leave content visible and usable", not absent. */
  @media (prefers-reduced-motion: no-preference) {
    .ways-card::after {
      animation: ways-rim-rotate 3.6s linear infinite paused;
    }
    .ways-card:hover::after {
      animation-play-state: running;
    }
  }

  @keyframes ways-rim-rotate {
    to { --ways-rim-angle: 360deg; }
  }
}

.ways-card__content {
  position: relative;
  z-index: 6;
  display: flex;
  height: 100%;
  min-height: 522px;
  flex-direction: column;
  align-items: flex-start;
  padding: 44px 42px 46px;
  transform: translateZ(26px);
}

.ways-card__icon {
  position: relative;
  display: grid;
  width: 54px;
  height: 54px;
  flex: none;
  place-items: center;
  overflow: hidden;
  border-radius: 16px;
  color: rgba(255, 255, 255, 0.97);
  isolation: isolate;
  /* A small static glass chip, not a second animated effect: the accent is
     deliberately mixed with transparent so the moving field remains a quiet
     contributor, while the two inner highlights make the 54px tile read as
     the card's smaller, brighter lens. The blur is bounded to this 54px box
     and has no rAF or hover loop. WebKit must stay first: see CLAUDE.md's
     declaration-order rule for Ways glass. */
  -webkit-backdrop-filter: blur(4px) saturate(1.12);
  backdrop-filter: blur(4px) saturate(1.12);
  background:
    radial-gradient(92% 78% at 14% 3%, rgba(255, 255, 255, 0.38), rgba(255, 255, 255, 0) 74%),
    linear-gradient(142deg, color-mix(in srgb, var(--ways-card-accent) 76%, transparent), color-mix(in srgb, var(--ways-card-deep) 60%, transparent));
  box-shadow:
    0 10px 20px -8px color-mix(in srgb, var(--ways-card-deep) 58%, transparent),
    inset 0 1px 1px rgba(255, 255, 255, 0.74),
    inset 0 -1px 2px color-mix(in srgb, var(--ways-card-deep) 26%, transparent),
    inset 0 0 0 1px rgba(255, 255, 255, 0.17);
}

.ways-card__icon::before,
.ways-card__icon::after {
  content: '';
  position: absolute;
  z-index: 0;
  pointer-events: none;
}

.ways-card__icon::before {
  inset: 1px;
  border: 1px solid rgba(255, 255, 255, 0.34);
  border-radius: inherit;
  box-shadow: inset 2px 2px 4px -3px rgba(255, 255, 255, 0.96);
}

.ways-card__icon::after {
  right: -12px;
  bottom: -17px;
  width: 42px;
  height: 38px;
  border-radius: 52% 48% 46% 54% / 48% 54% 46% 52%;
  background: radial-gradient(closest-side, rgba(255, 255, 255, 0.28), rgba(255, 255, 255, 0));
  transform: rotate(-22deg);
}

.ways-card__icon-svg {
  position: relative;
  z-index: 1;
  display: block;
  width: 54px;
  height: 54px;
  overflow: visible;
}

.ways-card__title {
  display: block;
  margin-top: 18px;
  color: var(--ink-900);
  font-family: var(--font-serif);
  font-size: 33px;
  font-weight: 400;
  line-height: 1.05;
  letter-spacing: -0.01em;
}

.ways-card__underline {
  display: block;
  width: 52px;
  height: 2px;
  margin-top: 11px;
  border-radius: 2px;
  background: var(--ways-card-accent);
}

.ways-card__description {
  display: block;
  margin-top: 17px;
  color: color-mix(in srgb, var(--ink-900) 88%, #342836);
  font-family: var(--font-sans);
  font-size: 15.5px;
  font-weight: 500;
  line-height: 1.57;
  text-wrap: pretty;
}

.ways-card__list {
  display: flex;
  flex-direction: column;
  gap: 11px;
  margin-top: 20px;
  color: color-mix(in srgb, var(--ink-900) 84%, #342836);
  font-family: var(--font-sans);
  font-size: 15.5px;
  font-weight: 500;
  line-height: 1.3;
}

.ways-card__list > span {
  display: flex;
  align-items: center;
  gap: 11px;
}

.ways-card__list > span::before {
  content: '';
  width: 7px;
  height: 7px;
  flex: none;
  border-radius: 50%;
  background: var(--ways-card-accent);
}

.ways-card__cta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: auto;
  padding-top: 30px;
  color: var(--ways-link);
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 600;
  line-height: 1.3;
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--ways-link) 40%, transparent);
  text-decoration-thickness: 1.5px;
  text-underline-offset: 5px;
}

.ways-card__cta > span {
  font-size: 16px;
  transition: transform var(--dur-quick) var(--ease-brand);
}

.ways-card:hover .ways-card__cta > span,
.ways-card:focus-visible .ways-card__cta > span { transform: translateX(5px); }

@media (hover: hover) and (pointer: fine) {
  .ways-card:hover { --ways-lift: -5px; --ways-scale: 1.012; }
}

/* ============================================================
   Mobile card disclosure (2026-07-30).

   All three cards were `href="#book"` with the whole card as the anchor, so
   any tap sent a visitor straight to the booking form having seen nothing
   (Daniel, item 9). Decision: expand in place. .ways-card--link above is
   that exact original anchor-wrapped card, untouched, and stays the ONLY
   thing shown on desktop/fine-pointer. .ways-card--disclosure is a second,
   sibling copy of the SAME card (03-ways.html; zero new copy), a plain
   element rather than a link, holding a real <button aria-expanded
   aria-controls> instead - never a link nested inside a button, and never
   the reverse. Exactly one of the two is display:none at any width, so they
   never coexist in the accessibility tree; see ways.js initCardDisclosures()
   for the toggle logic and 03-ways.html for why the article's own
   aria-labelledby lists both headings' ids.

   Both variants share the .ways-card base class on purpose: background,
   backdrop-filter, box-shadow, border-radius and the glass-ready overrides
   below all apply to whichever one is visible with no duplicated rules.

   ONE LIVE BLUR, NOT TWO (2026-08-20). Daniel: "ways card has double blur
   yes i noticed that too ... I would kill one since it's stacking and no
   need to doesn't make sense i see both load separately which is dumb."
   Measured with tools/ways-double-blur.mjs before this fix: at every width
   and every preset, whichever of .ways-card--link/.ways-card--disclosure
   was display:none STILL computed a live, non-"none" backdrop-filter (the
   shared .ways-card rule above gives both the same declaration) - never
   simultaneously PAINTING (display:none already prevents that), but always
   declared, which is the redundancy Daniel is reading as "both load
   separately". display:none already makes it free at runtime; this closes
   the structural half of the complaint so getComputedStyle agrees with the
   eye: exactly one live surface, full stop.
   -webkit- first, standard second, same rule as the base .ways-card
   declaration above and for the same reason (Chromium aliases the prefixed
   property onto the standard one, so prefixed-second would silently win and
   undo this). This is the DEFAULT/fine-pointer/no-swap case; the two
   coarse-pointer cases where the swap is actually live need their own
   overrides because of what already touches .ways-card at those widths -
   see the comment beside each one (~line 1207 and ~line 1296). */
.ways-card--disclosure {
  display: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

.ways-card__content--disclosure {
  /* The desktop content's min-height/flex-grow (.ways-card__content) assumed
     a fixed-height card with the CTA pinned to the bottom via margin-top:auto
     (job 2's card is allowed to grow instead - the whole point of expanding
     in place - so it must not inherit that stretch). */
  min-height: 0;
  flex: none;
}

.ways-card__toggle {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  margin-top: 18px;
  padding: 13px 2px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--ways-link);
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 600;
  line-height: 1.3;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.ways-card__toggle-label {
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--ways-link) 40%, transparent);
  text-decoration-thickness: 1.5px;
  text-underline-offset: 5px;
}

.ways-card__toggle-chevron {
  width: 9px;
  height: 9px;
  flex: none;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
  transition: transform 0.3s var(--ease-brand);
}

.ways-card__toggle[aria-expanded="true"] .ways-card__toggle-chevron {
  transform: rotate(-135deg);
}

.ways-card__toggle:focus-visible {
  outline: 2px solid var(--ways-link);
  outline-offset: 2px;
}

/* Grid-rows accordion: animates a track from 0fr to 1fr instead of height/
   max-height, so it does not need a measured pixel value. The inner wrapper
   is what actually clips (a grid item needs min-height:0 to shrink below its
   own content's natural height, or the 0fr track cannot go to zero). hidden
   is the ground truth for the accessibility tree and the tab order - see
   ways.js: it comes off only once the closing transition has genuinely
   finished, so the "Book a free 15-min consult" link is never focusable
   while visually collapsed to zero height. */
.ways-card__panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows 0.4s var(--ease-brand);
}

.ways-card__panel.is-open { grid-template-rows: 1fr; }

.ways-card__panel[hidden] { display: none; }

.ways-card__panel-inner {
  overflow: hidden;
  min-height: 0;
}

.ways-card__panel .ways-card__list { margin-top: 2px; }

.ways-card__cta--book {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 22px;
  padding: 12px 2px;
  color: var(--ways-link);
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 600;
  line-height: 1.3;
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--ways-link) 40%, transparent);
  text-decoration-thickness: 1.5px;
  text-underline-offset: 5px;
}

.ways-card__cta--book > span {
  font-size: 16px;
  transition: transform var(--dur-quick) var(--ease-brand);
}

.ways-card__cta--book:hover > span,
.ways-card__cta--book:focus-visible > span { transform: translateX(5px); }

/* ============================================================
   THE GLASS LAB SWITCHER (2026-08-08). Opt-in, ?glasslab=1 only. ways.js
   creates this element ONLY when that flag is present, so it can never reach
   a real visitor: the production default state has no such element in the DOM
   at all, not merely a hidden one. It is also aria-hidden and its controls
   carry tabindex="-1", so even with the flag on it stays out of the
   accessibility tree and out of the tab order.
   ============================================================ */
.ways-glasslab {
  position: fixed;
  z-index: 9999;
  right: 16px;
  bottom: 16px;
  display: flex;
  gap: 6px;
  align-items: center;
  padding: 8px 10px;
  border-radius: 12px;
  background: rgba(24, 18, 28, 0.86);
  box-shadow: 0 10px 30px -12px rgba(0, 0, 0, 0.6);
  color: #fff;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}

.ways-glasslab__label { opacity: 0.55; }

.ways-glasslab__btn {
  padding: 6px 10px;
  border: 1px solid rgba(255, 255, 255, 0.24);
  border-radius: 8px;
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.ways-glasslab__btn[aria-pressed='true'] {
  border-color: transparent;
  background: #fff;
  color: #1a1420;
}

/* The section's one ambient behaviour: viscosity. The band stretches along
   its length as it thins and compresses as it thickens, which is how a liquid
   under surface tension conserves volume, rather than drifting like a cloud.
   Travel stays inside --atmos-drift (18px) and scale inside --atmos-scale
   (1.045). 0%/100% is the NEUTRAL midpoint of the swing, not an endpoint, so
   base.css collapsing animation-duration to 0.01ms under reduced motion parks
   it somewhere legible - and the element's own transform declaration carries
   the same rotation, so `animation: none` lands in the same place. */
@keyframes ways-viscosity {
  0%, 100% { transform: rotate(-2.5deg) scale(1, 1) translateY(0); }
  35% { transform: rotate(-2.5deg) scale(1.045, 0.962) translateY(-9px); }
  70% { transform: rotate(-2.5deg) scale(0.972, 1.03) translateY(7px); }
}

@keyframes ways-float-a {
  0%, 100% { transform: translate(0, 0); }
  50% { transform: translate(10px, -12px); }
}

@keyframes ways-float-b {
  0%, 100% { transform: translate(0, 0); }
  50% { transform: translate(-9px, 11px); }
}

/* No backdrop-filter support at all (so no blur, no saturate and no SVG
   refraction): the card has to carry its own opaque-enough surface, since the
   preset tints go as thin as .16 and would leave copy sitting on bare
   watercolour. Independent of the preset on purpose. */
@supports not (backdrop-filter: blur(1px)) {
  .ways-card { background: rgba(255, 252, 249, 0.9); }
}

@media (max-width: 1120px) {
  .ways { padding-block: 104px 116px; }

  .ways__heading { font-size: clamp(2.7rem, 5.4vw, 3.4rem); }

  .ways__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 26px;
    margin-top: 64px;
  }

  .ways-card-item--clinicians { grid-column: span 2; }

  .ways-card__content { min-height: 482px; }

  .ways-card-item--clinicians .ways-card__content { min-height: 412px; }
}

@media (max-width: 860px) {
  /* One reliable gutter below the deck's two-column layout: the cards stack,
     so the deck rail and the text rail are the same rail and both come from
     the shared --gutter (20px at 390, 24px at 480). */
  .ways {
    --ways-gutter: var(--gutter);
    padding-block: 88px 96px;
  }

  .ways__grid {
    grid-template-columns: 1fr;
    gap: 24px;
    margin-top: 52px;
  }

  .ways-card-item--clinicians { grid-column: auto; }

  .ways-card__content,
  .ways-card-item--clinicians .ways-card__content { min-height: 0; }
}

@media (max-width: 480px) {
  /* --ways-gutter already resolves to the shared --gutter here (20px at 390),
     so the section no longer sets its own inline padding. */
  .ways__heading { font-size: clamp(2.35rem, 11.1vw, 2.75rem); }

  .ways-card__content { padding: 34px 29px 36px; }

  .ways-card__title { font-size: 28px; }

  /* Job 3: noticeably narrower cards so the background field reads down
     both sides ("especially the width, we need to see the hard work we put
     in the background animations" - Daniel, item 10). 84vw is the midpoint
     of the requested 82-86% of VIEWPORT width - not a percentage of
     .ways-card-item's own containing block, which is already inset by
     --ways-gutter and would land well under 82% of the viewport if used
     here (measured: 84% of the 350px track at 390 is 294px, only 75% of the
     390px viewport - the wrong base entirely). vw is exact against the
     viewport regardless of the track's own inset. margin-inline:auto centers
     the now-narrower box inside its still-full-width grid track, because a
     grid item with an explicit width no longer stretches to fill it.
     Legibility (priority 1) is unaffected: content padding is untouched, so
     the inner text column narrows from 292px to about 270px, still short of
     anything that reads as ragged for this card's short lines. */
  .ways-card-item {
    width: 84vw;
    margin-inline: auto;
  }
}

/* Mobile card disclosure swap (2026-07-30, job 2). Scoped to BOTH a coarse
   pointer and this narrow a viewport (the brief's own instruction), not
   width alone, so a touchscreen Windows laptop resized to a narrow window
   is not silently switched to a control it does not need, and a mouse-driven
   browser is never handed a button with no visible hover affordance instead
   of its usual whole-card link. See the long comment above .ways-card--disclosure
   for why both variants exist in the DOM at every width. */
@media (max-width: 480px) {
  html[data-pointer="coarse"] .ways-card--link { display: none; }
  html[data-pointer="coarse"] .ways-card--disclosure { display: block; }

  /* Mobile disclosure cards keep a single blur system: the card's own
     backdrop-filter. The ambient spans were a second pair of 34px/28px
     filtered layers on top of it, and their live rounded edge was what
     visibly separated from the card edge during expand/collapse. */
  html[data-pointer="coarse"] .ways-card__ambient { display: none; }

  /* NO SVG DISPLACEMENT ON THE PHONE, and this is a deliberate trade, not an
     oversight (2026-08-08). Two reasons, in order:

     1. It would be almost invisible. This layout swaps in
        --ways-card-mobile-bg, which runs .78/.64/.72 alpha for legibility on
        a small screen - roughly three times the tint of preset 1e - so there
        is very little backdrop left showing through to refract.
     2. It is not free. A displacement map on backdrop-filter is a per-pixel
        compositor pass over the whole card, and this section already spends
        1.47 Mpx of continuously animated WebGL2 on .ways__field. CLAUDE.md
        gotcha 9 and 12 are both about exactly this: fill rate, and a
        per-layer ceiling with no page budget.

     The blur and saturate still track the preset, so the mobile card still
     changes with the switch - just without the refraction. Reverse it by
     deleting this one declaration if Daniel wants the phone identical. */
  html[data-pointer="coarse"] .ways-card {
    backdrop-filter: blur(var(--ways-glass-blur)) saturate(var(--ways-glass-saturate));
    background: var(--ways-card-mobile-bg);
    transform: none;
    /* Pointer tilt never runs on this layout, so the layer promotion
       desktop's .ways-card carries unconditionally (line ~397) is switched
       back off by default here - re-enabled ONLY while a panel is open or
       transitioning, below, for an unrelated reason (paint-timing, not
       tilt). */
    will-change: auto;
  }

  /* ONE LIVE BLUR, NOT TWO, continued from .ways-card--disclosure above
     (2026-08-20). At THIS breakpoint .ways-card--link is the hidden one
     (line ~1231), but the blanket `html[data-pointer="coarse"] .ways-card`
     rule immediately above hands blur to BOTH variants alike - it has to,
     since .ways-card--disclosure needs exactly that value while it is the
     one showing. So .ways-card--link needs its own override, back to none,
     placed AFTER that blanket rule: same (html attr + class) specificity,
     so source order is what decides, and later wins. */
  html[data-pointer="coarse"] .ways-card--link {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }

  /* COLLAPSE/EXPAND WHITE-FLASH FIX (2026-08-06). Reported: for one frame
     mid-collapse the whole card goes blank white and a hard-edged patch of
     the page's paper colour (--ways-field) bleeds in from the bottom-right
     corner, overlapping the card below - confirmed by frame-by-frame video
     on a real device, self-corrects the very next frame.

     `will-change: auto` above turned OFF layer promotion for a reason
     unrelated to this bug (pointer tilt never runs on this layout, so its
     transform promotion looked pointless) without accounting for what else
     that promotion was doing: this card still runs a 34px backdrop-filter,
     and .ways-card__panel (ways.css, the accordion) transitions
     grid-template-rows - not a compositable property, so it forces a real
     browser layout recalculation on every intermediate frame of the 0.4s
     transition, changing this card's own border-box height (and therefore
     its backdrop-filter's sample/clip rect) every one of those frames. With
     no compositing layer of its own, the card's paint is inlined into
     whatever ancestor layer last invalidated, which can commit the new
     border-radius-clipped box geometry a frame before the backdrop-filter
     content behind it has been re-rastered to match - exactly the failure
     shape this project's CLAUDE.md already documents twice ("a border-radius
     leaves unpainted regions inside an otherwise opaque box... reads as a
     flat grey veil" / gotcha 7): a filtered box painting against what its
     stacking context has actually composited, not what it logically should
     have, for the one frame layout and paint disagree.

     Fix: give the card its own stable compositing layer again for exactly
     the duration of the transition, same mechanism desktop's .ways-card
     already carries unconditionally (will-change: transform, line ~397) -
     promoting it decouples its raster surface from the ancestor's
     layout-thrash so a mid-transition frame can no longer catch it between
     two different geometries.

     Scoped to :has(.ways-card__panel:not([hidden])), NOT :has(...is-open):
     ways.js's setExpanded() (initCardDisclosures()) removes the is-open
     class SYNCHRONOUSLY the instant a close begins - that is what starts
     the 1fr->0fr transition in the first place - so gating on is-open would
     strip this promotion at the exact moment the collapse starts, which is
     backwards. [hidden] is the one attribute that stays false for the
     panel's entire visible lifetime - open dwell, the expand transition
     AND the collapse transition - only flipping true once the close has
     genuinely settled (panel.hidden = true, at transitionend or the 550ms
     backstop). Not a permanent blanket promotion: idle collapsed cards (the
     overwhelming majority of the time on this page - 3 cards, default
     collapsed) still carry will-change: auto and pay nothing, honouring the
     same memory-consciousness that motivated turning it off in the first
     place. */
  html[data-pointer="coarse"] .ways-card:has(.ways-card__panel:not([hidden])) {
    will-change: transform;
  }

}


/* ONE LIVE BLUR, NOT TWO - the WIDE coarse-pointer case (2026-08-20,
   continued from .ways-card--disclosure above and from ways-card--link
   just above this comment). A plain `.ways-card--disclosure { backdrop-
   filter: none }` at the top of this file is enough for every FINE-pointer
   width, because nothing coarse-pointer-only can then apply to override it.
   It is NOT enough on its own for a coarse-pointer visitor wider than
   480px - exactly the foldable-UNFOLDED class this file already has a
   dedicated rule for (see "THE NO SVG DISPLACEMENT ON THE PHONE RULE
   ACTUALLY REACHING THE PHONE" below, commit 87f3ac7): that rule's
   `@media (pointer: coarse) and (hover: none) { .ways-card { backdrop-
   filter: ... } }` targets .ways-card generically - same (0,1,0)
   specificity as the bare `.ways-card--disclosure` rule up top - and it
   sits LATER in this file, so on any coarse-pointer device it would win
   the specificity tie and hand .ways-card--disclosure a live blur value
   again even though it is still display:none there (.ways-card--link is
   the one actually shown at this width). Verified on the exact device
   class that bit this project before: tools/ways-double-blur.mjs at 1440
   with a coarse pointer showed the disclosure copy's computed
   backdrop-filter come back to "blur(...) saturate(...)" without this
   block. html[data-pointer="coarse"] .ways-card--disclosure is (0,2,1),
   which outranks that (0,1,0) rule outright, so this does not depend on
   staying later in the file the way the .ways-card--link fix above does.
   Scoped to width > 480px only: inside that band .ways-card--disclosure
   is the shown surface and must keep its live blur from the block above. */
@media (min-width: 481px) {
  html[data-pointer="coarse"] .ways-card--disclosure {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  /* .ways__field draws exactly one still frame under reduced motion (see
     initWetGlassField() in ways.js) rather than being hidden here, so it
     stays the backdrop where it is running. Where it is not (WebGL2
     unavailable, low power, a lost context), .ways__blob's ribbon is the
     surface, and it is held STILL, not hidden: both pseudo-elements carry
     the same rotation in their own transform declaration as the keyframes'
     0% frame, so stopping the animation leaves them exactly where the
     neutral frame put them. */
  .ways__blob::before,
  .ways__blob::after { animation: none; }

  .ways-card,
  .ways-card-item,
  .ways-card__glare,
  .ways-card__cta > span { transition: none; }

  .ways-card__ambient > span { animation: none; }
}

@media (prefers-reduced-transparency: reduce), (forced-colors: active) {
  .ways__blob,
  .ways__field { display: none; }
  /* html[data-pointer="coarse"] is carried here ONLY to out-specify the
     mobile-disclosure block above, which now sets its own backdrop-filter
     (0,2,1 against this rule's bare 0,1,0). Without it, a phone with reduced
     transparency turned on would keep the blur - the exact thing this media
     query exists to remove. Verified with tools/ways-css-glass.mjs rather
     than reasoned about, because specificity against a later rule is the
     quiet way a preference gets ignored. */
  .ways-card,
  html[data-pointer="coarse"] .ways-card {
    background: rgba(255, 252, 249, 0.96);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }

  /* The per-card grain is a soft-light texture layer, which is a
     transparency effect in the same sense the glass is; drop it here too so
     the card is a genuinely flat, opaque surface rather than a nearly-flat
     one. The section-level .ways__grain (z-index 30) is a sitewide paper
     treatment and is deliberately left alone, exactly as every other section
     leaves it. */
  .ways-card__grain { display: none; }
}

/* THE "NO SVG DISPLACEMENT ON THE PHONE" RULE ACTUALLY REACHING THE PHONE.

   2026-08-19. The block above states the intent plainly - "NO SVG DISPLACEMENT
   ON THE PHONE, and this is a deliberate trade, not an oversight" - and gives
   two measured reasons: the mobile tint leaves almost no backdrop to refract,
   and a displacement map on backdrop-filter is a per-pixel compositor pass over
   the whole card in a section that already spends 1.47 Mpx of animated WebGL2.

   It is gated on `@media (max-width: 480px)`. Daniel's foldable UNFOLDED is
   691px wide with a coarse pointer, so it falls straight through and runs the
   full SVG chain. Measured live on that device over USB, reading the COMPUTED
   value rather than the declaration (gotcha 8):

     a.ways-card--link   623x460 CSS   backdrop-filter:
       url("#ways-glass-distort-1a") blur(20px) saturate(1.5)
       = 3.02 Mpx of displacement-mapped backdrop, x3 cards = 9.07 Mpx

   ...on a screen that is 5.33 Mpx in total. That is the exact cost the comment
   above says was removed, still running, on the exact device class this
   project has now been bitten by twice with width-only breakpoints.

   THIS RULE DELIBERATELY DOES NOT TOUCH THE LAYOUT SWAP. The 480px block also
   trades the whole-card link for the disclosure control and hides the ambient
   spans; those are legibility decisions for a genuinely narrow screen and a
   691px viewport should keep the link variant. Only the displacement term is
   dropped, exactly as the mobile block drops it, so blur and saturate still
   track whichever preset is live and the card still changes with ?glass=.

   Declaration order is load-bearing here as everywhere in this file: the
   -webkit- line first, the standard line second. Both carry the same value,
   so there is no url() left for either to disagree about.

   WHAT IT COSTS: the Ways cards lose their refraction on any coarse-pointer
   device wider than 480px. They keep the blur, the saturate, the tint and the
   rim. Daniel has not chosen a glass preset yet and all three still switch. */
@media (pointer: coarse) and (hover: none) {
  .ways-card {
    -webkit-backdrop-filter: blur(var(--ways-glass-blur)) saturate(var(--ways-glass-saturate));
    backdrop-filter: blur(var(--ways-glass-blur)) saturate(var(--ways-glass-saturate));
  }
}
