/* =====================================================================================================
   /work deck (Experience Bot · motion; 1.3 = card motion pass tokens; 1.4 = open-cue lift + press token;
   1.5 = phones up to 600px: full-size deck cards, Shah Oct 30 6:46am ET; 1.6 = next-card peek, pill notch, once-per-session nudge;
   1.7 = production card hover feel + brand-colour edge trace, Shah Oct 30 7:01am ET, section at the end;
   1.8 = "Pick a random one" on phones too, under the cards, Shah via PSB Oct 30 2026; work-deck.js 2.3). Phone (<768px): a sideways deck at the top of the list; scrolling
   deals every card out of a small dock into the list, in grid order. Tablet/desktop (>=768px): the normal
   grid plus "Pick a random one" (shuffle, one card lands centre). Behaviour: assets/js/work-deck.js.
   - The real grid stays the accessible list; every copy the deck or the shuffle shows is aria-hidden.
   - Only transform + opacity move. No blur. Nothing here is glass except the two dialog buttons (desktop).
   - Reduced motion: phone = plain grid with a fade; desktop = a crossfade straight to the pick.
   - No JS (or JS fails): the plain grid. The phone pre-pose below hides the list only while scripting is on,
     and a failsafe brings it back after 3.5s if the script never says it is ready.
   ===================================================================================================== */

:root {
  /* PROPOSED motion tokens (local to /work until PSB adopts them into tokens.css). JS reads the --wd-* values. */
  --wd-deal-span: .25;          /* scroll each card takes to be dealt, as a share of the screen height */
  --wd-front-scale: .72;        /* phone deck front card: 1.2 scale (max 260px); 1.3 keeps it as the floor */
  --wd-front-max: 1;            /* 1.3: front card up to a full grid card (list gutters), never upscaled */
  --wd-front-clear: 56;         /* px kept clear under it on short screens (dots + chat pill band) */
  --wd-dock-scale: .36;         /* docked deck scale (capped at 128px wide) */
  --wd-swipe-tau: 85;           /* ms; (1.0 carousel settle; kept for reference, 1.1 uses the spring below) */
  --wd-swipe-spring: 16;        /* rad/s; phone carousel critically damped spring (settles ~0.4s, keeps flick speed) */
  --wd-scroll-tau: 70;          /* ms; phone deal progress follows a damped copy of the scroll (no stepping) */
  --wd-gather: 380;             /* ms; desktop: grid cards gather into a deck (first shuffle) */
  --wd-riffle-split: 150;       /* ms; one riffle: split the deck */
  --wd-riffle-merge: 190;       /* ms; one riffle: interleave back */
  --wd-reveal: 520;             /* ms; the picked card comes to centre */
  --wd-fan: 480;                /* ms; the others settle behind it */
  --wd-fan-dim: .24;            /* opacity of the cards behind the pick */
  --wd-step: 340;               /* ms; desktop pick prev/next: one step round the fan (+60ms per extra card, max +180) */
  --wd-pick-height: 66vh;       /* picked card height (max 560px), ~1.2x a 1440 grid card */
  --wd-dot: 4px;                /* phone deck dots */
  --wd-cue-press: .94;          /* 1.4: open cue (.wcard-open) scale while pressed; JS reads it */
  --wd-cue-lift: -2px;          /* 1.4: open cue lift on fine-pointer hover / keyboard focus */
  /* 1.6 card motion pass, feel A "calm luxury" (storyboard: experience-bot/work-card-motion-2026-10-29) */
  --wd-intro: 720;              /* ms; phone: the deck opens out of one stack once at load (0 = off) */
  --wd-flight-sway: .05;        /* phone deal: sideways drift, share of screen width (1.5: .2) */
  --wd-flight-tilt: 4;          /* deg; phone deal: lean in flight, resolves on landing (1.5: 20) */
  --wd-flight-arc: 14;          /* px; phone deal: lift above the straight path (1.5: 40) */
  --wd-flight-pop: .015;        /* phone deal: scale bump mid-flight (1.5: .08) */
  --wd-enter-rise: 20;          /* px; desktop/tablet: grid cards below the fold rise this far as they enter */
  --wd-enter-dur: 640;          /* ms */
  --wd-enter-stagger: 70;       /* ms between cards entering together (max 3 steps) */
  --wd-enter-ease: cubic-bezier(.22, 1, .36, 1);
  /* 1.5 (2.0 in work-deck.js): phones up to 600px, full-size deck cards (Shah, Oct 30 6:46am ET) */
  --wd-full-gutter-l: 20;       /* px; front card's left gutter (the pre-pose guess uses 20 + 28 = 48px) */
  --wd-full-gutter-r: 28;       /* px; right gutter = the grid card's right edge */
  --wd-full-gap: 8;             /* px; between cards in the row */
  --wd-nudge: 28;               /* px; 1.6: the once-per-session sideways nudge (0 = off) */
  --wd-nudge-delay: 700;        /* ms after the row opens */
  /* 1.8 phone pick (work-deck.js 2.3): reuses --wd-fan (highlight in) and --wd-fan-dim (the others) */
  --wd-pick-up: 900;            /* ms, at most; the page glides back up to the deck (shorter for a short trip) */
  --wd-pick-pop: .02;           /* the chosen deck card swells this much and settles back as it is picked out */
}
/* SKIN HOOKS (1.3). Nothing visual is set from JS: the copies are clones of the real .bento-card (same classes,
   plus .wd-copy and .wd-ghost on the phone deck / .wd-pc in the desktop pick), so a skin that styles .bento-card
   reskins them too. Only transform, opacity, z-index, visibility and geometry come from JS. These custom properties
   are read with fallbacks and never defined here, so a skin can set them on :root, html, .wd-deck or .wd-stage
   in any load order:
     --wd-ghost-shadow    none                                              phone deck copies' shadow (raster cost!)
     --wd-dot-color       var(--white, #fff)                                phone dots
     --wd-dot-opacity     .22             --wd-dot-on-opacity .7            --wd-dot-on-scale 1.5
     --wd-scrim           color-mix(in srgb, var(--navy-950) 90%, transparent)   desktop pick backdrop
     --wd-focus-ring      var(--color-focus)   --wd-focus-width 3px   --wd-focus-offset 4px   picked card focus */

/* ---------- Phone pre-pose: set before first paint so the grid never flashes before the deck takes over ---------- */
@keyframes wd-failsafe-show { to { opacity: 1; } }
@keyframes wd-failsafe-pad { to { padding-top: 2rem; } }
@media (max-width: 767.98px) and (prefers-reduced-motion: no-preference) and (scripting: enabled) {
  /* room for the deck above the first card: front-card height x scale + 64px (JS writes the measured value) */
  html:not(.wd-ready) #portfolio-cards-container,
  html.wd-phone #portfolio-cards-container {
    padding-top: calc(2rem + var(--wd-hero-h, calc(max(min((100vw - 56px) * 1.2335 * var(--wd-front-scale), 260px * 1.2335),
      min((100vw - 56px) * 1.2335, 100vh - 414px)) + 64px)));   /* first-paint guess of what size() sets (top of slot ~358px) */
  }
  /* 1.5 (Shah, Oct 30 6:46am ET): up to 600px the front card is full size and sits on the first card's own grid slot */
  @media (max-width: 600px) {
    html:not(.wd-ready) #portfolio-cards-container,
    html.wd-phone #portfolio-cards-container {
      padding-top: calc(2rem + var(--wd-hero-h, 0px));   /* the row sits on the first card's own slot: no room to keep */
    }
  }
  html:not(.wd-ready) #portfolio-cards-container { animation: wd-failsafe-pad 1ms linear 3.5s forwards; }
  html:not(.wd-ready) #portfolio-cards-container > .bento-card { opacity: 0; animation: wd-failsafe-show 1ms linear 3.5s forwards; }
  html.wd-phone #portfolio-cards-container > .bento-card.wd-undealt { opacity: 0; }
  html.wd-phone #portfolio-cards-container > .bento-card.wd-undealt .bento-ring::before { animation-play-state: paused; }
}

/* ---------- Phone deck: one position:fixed layer, under the nav (99999) and the chat (9999+) ---------- */
.wd-deck { display: none; }
html.wd-phone .wd-deck {
  display: block; position: fixed; inset: 0; z-index: var(--wp-deck-z); pointer-events: none; contain: layout style;
  opacity: 1; visibility: visible;
  transition: opacity var(--dur-fast) var(--ease-linear), visibility 0s linear 0s;
}
html.wd-phone .wd-deck.is-off {
  opacity: 0; visibility: hidden;                                  /* hidden and idle once every card is dealt */
  transition: opacity var(--dur-fast) var(--ease-linear), visibility 0s linear var(--dur-fast);
}
.wd-copy {
  position: absolute !important; left: 0; top: 0; margin: 0 !important;
  pointer-events: none; transition: none !important; animation: none !important;
  transform-origin: 50% 50%; backface-visibility: hidden;
  -webkit-user-drag: none; user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent; touch-action: pan-y;
}
.wd-copy img { -webkit-user-drag: none; }
/* phone deck copies: no card shadow (2% black on navy, invisible) so a copy whose scale changes every frame
   is not re-rasterised with a 24px blur each time (1.1 buttery pass) */
html.wd-phone .wd-deck .wd-ghost.bento-card { box-shadow: var(--wd-ghost-shadow, none) !important; }
.wd-copy .bento-ring { display: none; }
.wd-copy.is-hit { pointer-events: auto; }
/* tap-only card-to-case names on a copy (copies have no id, so the names ride on custom properties) */
.wd-copy[data-vt-tap] .bento-mockup-img { view-transition-name: var(--wd-vt-img, none); }
.wd-copy[data-vt-tap] .editorial-title { view-transition-name: var(--wd-vt-title, none); }

.wd-dots { position: absolute; left: 0; right: 0; top: 0; display: flex; justify-content: center; gap: var(--wp-dots-gap); pointer-events: none; }
.wd-dots i {
  display: block; width: var(--wd-dot); height: var(--wd-dot); border-radius: var(--radius-pill);
  background: var(--wd-dot-color, var(--white, #fff)); opacity: var(--wd-dot-opacity, .22);
  transition: opacity var(--dur-base) var(--ease-linear), transform var(--dur-base) var(--ease-enter);
}
.wd-dots i.is-on { opacity: var(--wd-dot-on-opacity, .7); transform: scale(var(--wd-dot-on-scale, 1.5)); }

html.wd-full .wd-dots { z-index: 1000; }            /* 1.5: the dots sit on the full-size front card (bottom-left) */
/* 1.6: the peeking card never runs under the chat pill: the layer is clipped right of the front card, from just above
   the pill's laid-out top down (--wd-nx / --wd-ny are geometry, set by work-deck.js on resize). Static, no animation.
   The polygon reaches well past the layer's box, so nothing at the edges is cut while the nudge slides the layer. */
html.wd-full .wd-deck.wd-notch {
  clip-path: polygon(-100vw -100vh, 200vw -100vh, 200vw var(--wd-ny, 200vh), var(--wd-nx, 200vw) var(--wd-ny, 200vh), var(--wd-nx, 200vw) 200vh, -100vw 200vh);
}

/* ---------- Phone, reduced motion: plain grid; cards below the fold fade in once ---------- */
html.wd-rm #portfolio-cards-container > .bento-card.wd-fade { opacity: 0; }
html.wd-rm #portfolio-cards-container > .bento-card.wd-fade.wd-in { opacity: 1; transition: opacity var(--dur-slow) var(--ease-linear); }

/* ---------- "Pick a random one" at the very bottom of the list: tablet / desktop, and (1.8) phones ---------- */
.wd-pickbar { display: none; }
html.wd-desk .wd-pickbar { display: flex; justify-content: center; padding: var(--wp-pickbar-pad); }
/* 1.8 phones (deck and reduced-motion grid): the same button and skin (work-skin.css .wd-pickbar .btn: 15px, ~49px tall,
   ~220px wide), the same fluid spacing (48px / 96px at 390). Never wider than the screen, never under 44px tall. */
html:is(.wd-phone, .wd-rm) .wd-pickbar { display: flex; justify-content: center; padding: var(--wp-pickbar-pad); }
html:is(.wd-phone, .wd-rm) .wd-pickbar .btn { max-width: 100%; min-height: var(--fd-btn-hit-min); touch-action: manipulation; }
/* the picked deck card's brand edge (copies otherwise hide .bento-ring): one lap, the phones' own in-view trace */
html body.work-page .wd-deck .bento-card.wd-ghost.wd-picked {
  --wh-edge: var(--card-brand, var(--title-hover-color, var(--gold-500)));
}
html body.work-page .wd-deck .bento-card.wd-ghost.wd-picked .bento-ring {
  display: block; inset: 0; animation: wh-edge-once var(--wh-edge-once) var(--ease-linear, linear) both;
}
html body.work-page .wd-deck .bento-card.wd-ghost.wd-picked .bento-ring::before {
  background: conic-gradient(transparent 0%,
    color-mix(in srgb, var(--wh-edge) 10%, transparent) 20%, color-mix(in srgb, var(--wh-edge) 40%, transparent) 40%,
    color-mix(in srgb, var(--wh-edge) 10%, transparent) 60%, color-mix(in srgb, var(--wh-edge) 80%, transparent) 85%, var(--wh-edge) 100%);
  animation: wh-turn var(--wh-edge-once) var(--wh-ease) both;
}
html body.work-page .wd-deck .bento-card.wd-ghost.wd-picked .wh-edge .wh-h { animation: wh-lap-h var(--wh-edge-once) var(--wh-ease) both; }
html body.work-page .wd-deck .bento-card.wd-ghost.wd-picked .wh-edge .wh-t { animation: wh-lap-t var(--wh-edge-once) var(--wh-ease) both; }
@supports (-moz-appearance: none) {
  html body.work-page .wd-deck .bento-card.wd-ghost.wd-picked .bento-ring { padding: 0; -webkit-mask: none; mask: none; }
  html body.work-page .wd-deck .bento-card.wd-ghost.wd-picked .bento-ring::before { content: none; display: none; animation: none; }
}
/* reduced motion (the plain grid): the picked grid card keeps a static brand edge, no lap. An outline, not the ring:
   the ring's one-off in-view fade (.wh-trace) may still be running on that card, and an animation would win over it. */
html.wd-rm body.work-page .bento-card.wd-picked:not(.wd-copy):not(:focus-visible) {
  outline: 1.5px solid color-mix(in srgb, var(--wh-edge) 70%, transparent); outline-offset: -1px;
}

.wd-stage { position: fixed; inset: 0; z-index: var(--wp-stage-z); display: none; }   /* above the nav, under the chat panel */
.wd-stage.is-open { display: block; }
.wd-scrim { position: absolute; inset: 0; background: var(--wd-scrim, color-mix(in srgb, var(--navy-950) 90%, transparent)); opacity: 0; }
.wd-stage .wd-copy { will-change: transform, opacity; }
.wd-stage .wd-copy.is-picked { pointer-events: auto; }
.wd-stage .wd-copy.is-picked:focus-visible { outline: var(--wd-focus-width, 3px) solid var(--wd-focus-ring, var(--color-focus)); outline-offset: var(--wd-focus-offset, 4px); }
.wd-ctl { position: absolute; left: 0; right: 0; top: 0; display: flex; justify-content: center; gap: var(--wp-ctl-gap); opacity: 0; pointer-events: none; }
.wd-close { position: absolute !important; top: var(--wp-close-top); right: var(--wp-close-right); opacity: 0; pointer-events: none; }
.wd-ctl.is-on, .wd-close.is-on { pointer-events: auto; }
.wd-stage .btn { min-width: 0; }
/* 1.4 prev / next in the pick: the fanned cards are clickable, the arrows are quiet round outline buttons
   (skin: .wd-step / .wd-prev / .wd-next are .btn .btn-white-outline; --wd-step-opacity .72 at rest, 1 on hover/focus) */
.wd-stage.is-steppable .wd-copy:not(.is-picked) { pointer-events: auto; cursor: pointer; }
html .wd-stage .wd-step { width: var(--wp-step-size); padding: 0; display: inline-flex; align-items: center; justify-content: center; opacity: var(--wd-step-opacity, .72); }
html .wd-stage .wd-step:hover, html .wd-stage .wd-step:focus-visible { opacity: 1; }
.wd-stage .wd-step svg { display: block; }
html.wd-locked { overflow: hidden; scrollbar-gutter: stable; }     /* no page scroll (and no layout jump) under the dialog */

.wd-sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.wd-stage:focus { outline: none; }

/* =====================================================================================================
   1.7 · CARD HOVER FEEL FROM PRODUCTION + BRAND-COLOUR EDGE TRACE (Shah, Oct 30 7:01am ET via PSB)
   Production byshah.com/work values: lift translateY(-6px) scale(1.01) and mockup zoom 1.10 → 1.13 (×1.0273, from the
   bottom centre), both .5s cubic-bezier(.16,1,.3,1) (= --ease-emphasized); a thin edge line fades in (.8s) and slowly
   traces round the card (one lap per 6s) while hovered. The line is each card's own brand colour (--title-hover-color,
   the per-card value in work/index.html / work-skin.css that PPB's skin already uses; a --card-brand hook wins if set).
   - The trace, measured per engine (hover/perf-*.json): WebKit + Chromium use the card's existing .bento-ring (1px
     mask) with its oversized conic layer turning by transform only (compositor; WebKit steady hover p95 17ms, the SVG
     dash 102ms). Firefox repaints that mask every frame (p95 117ms), so in Firefox only (@supports -moz-appearance)
     the line is an SVG rounded rect (work-deck.js adds it, aria-hidden) whose dash moves by stroke-dashoffset (p95
     33ms). Same colour, speed and fade; the conic draws a soft comet, the dash a bright head + faint tail.
     It runs only while hovered (paused otherwise). No @property, no blur.
   - The zoom uses the individual `scale` property, so work-skin.css's cover lock (transform: none !important) stays
     as written; a click sets .wh-reset (work-deck.js, synchronously, before the navigation's view transition is
     captured), which drops zoom, lift and line with no transition, so the morph starts from the true rest crop.
   - Phones (≤600px) and touch screens: no hover. The line plays ONCE as each card comes into view (work-deck.js
     adds .wh-trace; a deck card only after it has been dealt), and pauses while offscreen.
   - Reduced motion: no trace, no zoom, no lift; a static thin edge with a short fade.
   - Copies (.wd-copy: phone deck, desktop pick) never get any of this. The lockup never takes a view-transition name
     (it fades with the page, opacity only).
   ===================================================================================================== */
:root {
  --wh-lift: -6px;              /* production */
  --wh-lift-scale: 1.01;        /* production */
  --wh-zoom: 1.0273;            /* production 1.10 → 1.13 */
  --wh-dur: 500ms;              /* production .5s */
  --wh-ease: var(--ease-emphasized, cubic-bezier(.16, 1, .3, 1));
  --wh-edge-in: 800ms;          /* production line fade */
  --wh-edge-lap: 6s;            /* production: one lap per 6s while hovered */
  --wh-edge-once: 2400ms;       /* touch / phones: one lap as the card comes into view */
}
html body.work-page .bento-card:not(.wd-copy) { --wh-edge: var(--card-brand, var(--title-hover-color, var(--gold-500))); }
/* the frame: re-shown under PPB's skin, which hid the old coral line ("card-only bits would vanish mid-open"): this one
   is cleared at the click (.wh-reset) before the open is captured. inset 0 (card.css keeps -1px, where the card's
   overflow clips the line away, "a Shah decision": Shah asked for the line back, so it sits just inside the edge) */
html body.work-page .bento-card:not(.wd-copy) .bento-ring {
  display: block; inset: 0; opacity: 0; transition: opacity var(--wh-edge-in) var(--ease-linear, linear);
}
/* WebKit + Chromium: the conic comet (card.css geometry: 1px mask, 170% square layer), brand colour, turned by transform */
html body.work-page .bento-card:not(.wd-copy) .bento-ring::before {
  background: conic-gradient(transparent 0%,
    color-mix(in srgb, var(--wh-edge) 10%, transparent) 20%, color-mix(in srgb, var(--wh-edge) 40%, transparent) 40%,
    color-mix(in srgb, var(--wh-edge) 10%, transparent) 60%, color-mix(in srgb, var(--wh-edge) 80%, transparent) 85%, var(--wh-edge) 100%);
  animation: wh-turn var(--wh-edge-lap) linear infinite paused;
}
@keyframes wh-turn { from { transform: translate(-50%, -50%) rotate(0turn); } to { transform: translate(-50%, -50%) rotate(1turn); } }
.bento-ring .wh-edge { display: none; position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
/* Firefox: the SVG dash instead (no per-frame mask repaint) */
@supports (-moz-appearance: none) {
  html body.work-page .bento-card:not(.wd-copy) .bento-ring { padding: 0; -webkit-mask: none; mask: none; }
  html body.work-page .bento-card:not(.wd-copy) .bento-ring::before { content: none; display: none; animation: none; }
  .bento-ring .wh-edge { display: block; }
}
/* stroke 2px centred on the card edge: the ring's rounded overflow keeps the inner 1px */
.bento-ring .wh-edge rect { fill: none; stroke: var(--wh-edge); stroke-width: 2; vector-effect: non-scaling-stroke; }
.bento-ring .wh-edge .wh-h { stroke-dasharray: 12 88; stroke-dashoffset: 12; }
.bento-ring .wh-edge .wh-t { stroke-dasharray: 34 66; stroke-dashoffset: 34; opacity: .35; }
html body.work-page .bento-card:not(.wd-copy) .wh-edge .wh-h { animation: wh-lap-h var(--wh-edge-lap) linear infinite paused; }
html body.work-page .bento-card:not(.wd-copy) .wh-edge .wh-t { animation: wh-lap-t var(--wh-edge-lap) linear infinite paused; }
@keyframes wh-lap-h { from { stroke-dashoffset: 12; } to { stroke-dashoffset: -88; } }   /* head at 0 → 100% of the perimeter */
@keyframes wh-lap-t { from { stroke-dashoffset: 34; } to { stroke-dashoffset: -66; } }   /* tail ends on the same head */

/* desktop / fine pointer, wider than a phone */
@media (hover: hover) and (pointer: fine) and (min-width: 601px) and (prefers-reduced-motion: no-preference) {
  html body.work-page .editorial-project.bento-card:not(.wd-copy) {
    transition: transform var(--wh-dur) var(--wh-ease), box-shadow var(--wh-dur) var(--wh-ease), border-color var(--wh-dur) ease;
  }
  html body.work-page .editorial-project.bento-card:not(.wd-copy):not(.is-leaving):not(.wh-reset):hover {
    transform: translateY(var(--wh-lift)) scale(var(--wh-lift-scale));
  }
  html body.work-page .editorial-project.bento-card:not(.wd-copy):not(.is-leaving):not(.wh-reset):hover:active {
    transform: translateY(calc(var(--wh-lift) / 2)) scale(.995);
  }
  html body.work-page .bento-card:not(.wd-copy) .bento-mockup-img {
    transform-origin: 50% 100%; scale: 1; transition: scale var(--wh-dur) var(--wh-ease);
  }
  html body.work-page .bento-card:not(.wd-copy):not(.wh-reset):hover .bento-mockup-img { scale: var(--wh-zoom); }
  html body.work-page .bento-card:not(.wd-copy):not(.wh-reset):is(:hover, :focus-visible) .bento-ring { opacity: 1; }
  html body.work-page .bento-card:not(.wd-copy):not(.wh-reset):is(:hover, :focus-visible) .wh-edge rect,
  html body.work-page .bento-card:not(.wd-copy):not(.wh-reset):is(:hover, :focus-visible) .bento-ring::before { animation-play-state: running; }
}
/* touch / phones: one lap as the card comes into view (.wh-trace from work-deck.js), not looping; paused offscreen */
html body.work-page .bento-card.wh-trace:not(.wd-copy) .bento-ring { transition: none; animation: wh-edge-once var(--wh-edge-once) var(--ease-linear, linear) both; }
html body.work-page .bento-card.wh-trace:not(.wd-copy) .wh-edge .wh-h { animation: wh-lap-h var(--wh-edge-once) var(--wh-ease) both; }
html body.work-page .bento-card.wh-trace:not(.wd-copy) .wh-edge .wh-t { animation: wh-lap-t var(--wh-edge-once) var(--wh-ease) both; }
html body.work-page .bento-card.wh-trace:not(.wd-copy) .bento-ring::before { animation: wh-turn var(--wh-edge-once) var(--wh-ease) both; }
html body.work-page .bento-card.wh-trace.wh-paused .bento-ring,
html body.work-page .bento-card.wh-trace.wh-paused .bento-ring::before,
html body.work-page .bento-card.wh-trace.wh-paused .wh-edge rect { animation-play-state: paused; }
@keyframes wh-edge-once { 0% { opacity: 0; } 18% { opacity: 1; } 72% { opacity: 1; } 100% { opacity: 0; } }
/* reduced motion: a static thin edge (no dash, no lap), faded; no zoom, no lift */
@media (prefers-reduced-motion: reduce) {
  html body.work-page .bento-card:not(.wd-copy) .wh-edge .wh-h,
  html body.work-page .bento-card.wh-trace:not(.wd-copy) .wh-edge .wh-h { animation: none; stroke-dasharray: none; opacity: .7; }
  html body.work-page .bento-card:not(.wd-copy) .wh-edge .wh-t { display: none; }
  html body.work-page .bento-card:not(.wd-copy) .bento-ring::before,
  html body.work-page .bento-card.wh-trace:not(.wd-copy) .bento-ring::before { animation: none; background: color-mix(in srgb, var(--wh-edge) 70%, transparent); }
  html body.work-page .bento-card:not(.wd-copy) .bento-ring { transition: opacity var(--dur-fast, 150ms) linear; }
}
@media (hover: hover) and (pointer: fine) and (min-width: 601px) and (prefers-reduced-motion: reduce) {
  html body.work-page .bento-card:not(.wd-copy):not(.wh-reset):is(:hover, :focus-visible) .bento-ring { opacity: 1; }
  html body.work-page .editorial-project.bento-card:not(.wd-copy):hover,
  html body.work-page .editorial-project.bento-card:not(.wd-copy):hover:active { transform: none; }
}
/* the click: no zoom, no lift, no line, no transition, so the morph captures the resting card (set synchronously) */
@media (hover: hover) and (pointer: fine) and (min-width: 601px) {   /* touch keeps its .98 press (.is-leaving) */
  html body.work-page .editorial-project.bento-card.wh-reset,
  html body.work-page .editorial-project.bento-card.wh-reset:hover,
  html body.work-page .editorial-project.bento-card.wh-reset:hover:active { transform: none; transition: none; }
}
html body.work-page .bento-card.wh-reset .bento-mockup-img { scale: 1; transition: none; }
html body.work-page .bento-card.wh-reset .bento-ring { opacity: 0; transition: none; animation: none; }
/* during any view transition (e.g. Back landing under a resting mouse), the cover is never zoomed */
html:active-view-transition body.work-page .bento-card .bento-mockup-img { scale: 1; transition: none; }
/* the brand lockup PPB is restoring (.bento-company-lockup today, or .wcard-lockup): never a morph element, never
   transformed by the hover; it fades with the page (opacity only). Defensive: matches nothing until it is shown. */
html body.work-page .bento-card :is(.bento-company-lockup, .wcard-lockup) { view-transition-name: none; }
