﻿/* ===========================================================================
   Erik Kotik â€” portfolio system
   Flat editorial Â· no boxes, no rounded corners Â· images bleed on background
   Type: Inter (all weights) Â· accent: warm coral Â· separation by rules + space
   =========================================================================== */

:root{
  /* Color */
  --ink:#15171c;          /* near-black text & primary buttons */
  --body:#4a5160;         /* body copy */
  --muted:#7b828f;        /* secondary / captions */
  --hair:#e4e2dc;         /* hairline rules */
  --soft:#f6f5f1;         /* faint warm wash (used sparingly, full-bleed only) */
  --surface:#fff;
  --dark:#14161d;         /* full-bleed dark band (footer) */
  --blue:#155eef;         /* brand wordmark / links */
  --blue-ink:#0e3fae;
  --accent:#df5d34;       /* warm coral â€” accent */

  /* Layout */
  --max:1200px;
  --space-section:clamp(72px,9vw,140px);
  --space-block:clamp(36px,5vw,68px);

  /* Type */
  --font:'Plus Jakarta Sans',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  --font-head:'Plus Jakarta Sans','Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
}

*{box-sizing:border-box}
html{background:var(--surface);color:var(--ink);scroll-behavior:smooth;scroll-padding-top:var(--header-h,76px);scrollbar-gutter:stable}
body{margin:0;background:var(--surface);font-family:var(--font);line-height:1.6;-webkit-font-smoothing:antialiased;color:var(--ink)}
a{color:inherit}
img{max-width:100%;height:auto}
h1,h2,h3{font-family:var(--font-head);font-weight:800;letter-spacing:-.02em;margin:0;text-wrap:balance}

.skip{position:absolute;left:-999px}
.skip:focus{left:16px;top:16px;z-index:1000;background:var(--ink);color:#fff;padding:12px 16px}
:focus-visible{outline:2px solid var(--accent);outline-offset:3px}

/* --- Header / nav ---------------------------------------------------------- */
.site-header{position:fixed;top:0;left:0;right:0;z-index:50;display:flex;align-items:center;justify-content:space-between;gap:22px;padding:16px clamp(16px,3vw,42px);background:transparent;border-bottom:1px solid transparent;transition:background .3s ease,border-color .3s ease,backdrop-filter .3s ease,padding .3s ease}
.site-header.is-scrolled{background:rgba(255,255,255,.88);border-bottom-color:var(--hair);backdrop-filter:blur(18px)}
main{padding-top:var(--header-h,76px)}
.brand{min-height:44px;display:inline-flex;align-items:center;gap:4px;text-decoration:none;font-family:var(--font-head);font-weight:800;font-size:19px;letter-spacing:.5px}
.brand span:last-child{color:var(--accent)}
.nav{display:flex;gap:2px;flex-wrap:wrap;justify-content:flex-end}
.nav a{position:relative;min-height:40px;display:inline-flex;align-items:center;padding:8px 11px;text-decoration:none;color:var(--body);font-size:13px;font-weight:500;transition:color .15s}
.nav a:hover{color:var(--accent)}
.nav a[aria-current=page]{color:var(--ink);font-weight:600}
.nav a[aria-current=page]::after{content:"";position:absolute;left:11px;right:11px;bottom:2px;height:2px;background:var(--accent)}

/* --- Eyebrow & text links -------------------------------------------------- */
.eyebrow{display:inline-flex;align-items:center;gap:8px;color:var(--accent);font-size:12px;font-weight:700;letter-spacing:.14em;text-transform:uppercase}
.link-arrow{display:inline-flex;align-items:center;gap:6px;font-weight:600;font-size:15px;color:var(--ink);text-decoration:none;border-bottom:1px solid var(--ink);padding-bottom:2px;transition:color .15s,border-color .15s}
.link-arrow:hover{color:var(--accent);border-color:var(--accent)}
.text-link{display:inline-flex;align-items:center;gap:6px;font-weight:600;color:var(--accent);text-decoration:none}
.text-link:hover{color:var(--blue)}

/* --- Buttons (sharp, rectangular, minimal) -------------------------------- */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:48px;padding:13px 26px;border:1px solid var(--ink);background:var(--ink);color:#fff;text-decoration:none;font-family:var(--font);font-weight:600;font-size:15px;transition:background .15s,color .15s,border-color .15s}
.btn:hover{background:transparent;color:var(--ink)}
.btn.primary{background:var(--ink);border-color:var(--ink);color:#fff}
.btn.primary:hover{background:transparent;color:var(--ink)}
.btn.subtle{background:transparent;color:var(--ink);border-color:var(--ink)}
.btn.subtle:hover{background:var(--ink);color:#fff}

/* --- Hero (flat, light, image bleeds) ------------------------------------- */
.product-hero,.page-hero{padding:var(--space-section) clamp(16px,3vw,42px)}
.hero-grid,.page-hero-inner{max-width:var(--max);margin:auto;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.02fr);gap:clamp(32px,6vw,80px);align-items:center}
.page-hero.hero-wide .page-hero-inner{grid-template-columns:minmax(0,1fr)}

.hero-copy .eyebrow,.page-title .eyebrow{color:var(--accent)}
.hero-copy h1,.page-title h1,.contact-hero h1{font-size:clamp(46px,6.6vw,88px);line-height:1.0;color:var(--ink);margin:18px 0}
.hero-copy p,.page-title p{font-size:clamp(17px,1.5vw,21px);color:var(--body);max-width:60ch;margin:0}
.page-hero.hero-wide .page-title p{max-width:78ch}
.actions{display:flex;gap:24px;align-items:center;flex-wrap:wrap;margin-top:34px}

/* --- Work images (no frames â€” bleed onto background) ----------------------- */
.hero-showcase{display:grid;grid-template-columns:1fr 1fr;gap:clamp(20px,3vw,40px);align-items:start}
.hero-work:first-child{grid-row:span 2}
.page-feature{display:flex;align-items:center}
.page-feature .lightbox-link{width:100%}

.lightbox-link{position:relative;display:block;text-decoration:none}
.work-image{display:block;width:100%;background:transparent;transition:opacity .2s}
.lightbox-link:hover .work-image{opacity:.9}
.hero-img,.feature-img{max-height:600px;object-fit:contain}
.card-img{max-height:420px;object-fit:contain}
.zoom-cue{position:absolute;right:4px;bottom:8px;color:var(--ink);font-size:11px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;opacity:0;transition:opacity .2s;text-shadow:0 1px 6px rgba(255,255,255,.9)}
.lightbox-link:hover .zoom-cue,.lightbox-link:focus-visible .zoom-cue{opacity:1}

/* --- Stat strip (index) â€” rules, not a box -------------------------------- */

/* --- Section intro (index) ------------------------------------------------- */
.section-intro{max-width:var(--max);margin:auto;padding:var(--space-section) clamp(16px,3vw,42px) var(--space-block)}
.section-intro h2{font-size:clamp(32px,4.6vw,60px);line-height:1.03;margin:14px 0}
.section-intro p{max-width:70ch;color:var(--body);font-size:18px}

/* --- Case rows (index) â€” editorial, alternating, no boxes ----------------- */
.case-grid{max-width:var(--max);margin:auto;padding:0 clamp(16px,3vw,42px) var(--space-section);display:grid;gap:0}
.case-card{display:grid;grid-template-columns:minmax(0,1.05fr) minmax(0,1fr);gap:clamp(28px,5vw,72px);align-items:center;padding:var(--space-block) 0;border-top:1px solid var(--hair)}
.case-card:first-child{border-top:0}
.case-card:nth-child(even) .case-media{order:2}
.case-copy h2{font-size:clamp(28px,3.8vw,52px);line-height:1.03;margin:10px 0 14px}
.case-copy h2 a{text-decoration:none}
.case-copy h2 a:hover{color:var(--accent)}
.case-copy p{font-size:17px;color:var(--body);margin:0 0 18px}

/* --- Content system (section pages) â€” boxless sidebar --------------------- */
.content-system{max-width:var(--max);margin:auto;padding:var(--space-section) clamp(16px,3vw,42px);display:grid;grid-template-columns:300px 1fr;gap:clamp(40px,6vw,80px)}
.summary-panel{position:sticky;top:104px;align-self:start}
.summary-panel .eyebrow{color:var(--accent)}
.summary-panel h1,.summary-panel h2{font-size:22px;font-weight:700;line-height:1.2;margin:14px 0 0;color:var(--ink);letter-spacing:-.02em}
.summary-panel h1{font-size:48px;line-height:1.05}
.summary-panel p{color:var(--body);font-size:16px}
.summary-panel dl{border-top:1px solid var(--hair);margin-top:24px;padding-top:20px;display:grid;gap:16px}
.summary-panel dt{font-size:11px;text-transform:uppercase;letter-spacing:.12em;font-weight:700;color:var(--accent)}
.summary-panel dd{margin:5px 0 0;color:var(--body);font-size:15px}
.summary-panel a{color:var(--blue);font-weight:600}

.work-flow{min-width:0}
.work-gallery{display:grid;grid-template-columns:minmax(0,1fr);gap:clamp(28px,4vw,52px)}
.flow-email-design .work-gallery{display:block;columns:2 320px;column-gap:clamp(28px,4vw,52px)}
.work-item{margin:0}
.flow-email-design .work-item{break-inside:avoid;margin:0 0 clamp(28px,4vw,52px)}
.work-item figcaption{display:flex;justify-content:space-between;gap:12px;padding:14px 0 0;font-size:13px;color:var(--muted);font-weight:500}
.work-item figcaption strong{color:var(--ink);font-weight:600}
.work-item figcaption .link-arrow{font-size:13px;border-bottom-width:1px}

/* --- Client sub-sections (email page) â€” hairline divider ------------------ */
.client-section{border-top:1px solid var(--hair);padding:var(--space-block) 0 0;margin-bottom:8px}
.client-section h3,.intro-work h3{font-size:clamp(26px,3.4vw,46px);line-height:1.04;margin:10px 0}
.client-section p,.intro-work p{color:var(--body);font-size:16px}
.client-mark{max-width:220px;margin:0 0 24px}
.logo-mark{max-height:140px;object-fit:contain}
.intro-gallery{display:grid;grid-template-columns:minmax(0,1fr);columns:auto}
/* About page lower section: line it up with the Introduction hero above it.
   .content-system caps at --max but then pads 42px inside that cap, so its
   content ended up 84px narrower than .page-hero-inner, which is simply --max
   centred. Growing the cap by the same padding puts both content boxes on the
   identical left/right boundary at every width. */
.about-page .content-system{max-width:calc(var(--max) + 2 * clamp(16px,3vw,42px))}
/* Artwork left, Background panel right. Done with order rather than by moving
   the aside in the DOM so the stacked layout still leads with the panel; the
   panel holds no focusable content, so visual and tab order cannot diverge.
   Confined to the two-column range: media queries add no specificity, so an
   unscoped override here would outrank the 920px rule that collapses
   .content-system to one column and the page would never stack. */
@media(min-width:921px){
  .about-page .content-system{grid-template-columns:minmax(0,1fr) 300px}
  .about-page .work-flow{order:-1}
}
/* The email accordion is reused here from the Work page, where it is a
   standalone full-bleed section, so it drops that chrome. The column is a flex
   column and the accordion grows into it, which makes the grid row (set by the
   taller Background panel beside it) drive the artwork height: tops already
   share the row's start edge, and the bottoms now land together too. */
.flow-introduction{container-type:inline-size;display:flex;flex-direction:column}
.flow-introduction .expand-cards{max-width:none;margin:0;padding:0;flex:1 1 auto;min-height:clamp(300px,40cqw,560px)}
/* Leave the artwork alone on hover. Two things tinted it: .lightbox-link drops
   every work-image to 90% opacity, and .ec-card::after fades a dark gradient
   over the lower half. Both are cancelled here so pointing at a card only
   expands it and never alters the design being shown. */
.flow-introduction .ec-card:hover .work-image,
.flow-introduction .ec-card:focus-visible .work-image{opacity:1}
.flow-introduction .ec-card::after,
.flow-introduction .ec-card.is-active::after,
.flow-introduction .ec-card:hover::after,
.flow-introduction .ec-card:focus-visible::after{opacity:0}
/* Expanded artwork at full scale. The base component crops with object-fit
   cover and reveals the rest by slowly panning object-position. Here the
   expanded card instead lets the image take its natural height at the card's
   width and scrolls, so a tall email reads top to bottom at its own scale,
   uncropped and unsquashed. Collapsed cards keep the cover crop, including the
   default card while a sibling is hovered. Scoped above the stacking
   breakpoint; below it every card is already full width and tapping opens the
   lightbox. */


/* --- Pager â€” text links, no buttons --------------------------------------- */
.pager{max-width:var(--max);margin:0 auto var(--space-section);padding:var(--space-block) clamp(16px,3vw,42px) 0;border-top:1px solid var(--hair);display:flex;justify-content:space-between;gap:14px}
.pager .btn{min-height:auto;padding:0;border:0;background:none;color:var(--muted);font-weight:600;font-size:15px}
.pager .btn:hover{background:none;color:var(--accent)}
.pager .btn.primary{background:none;color:var(--ink)}
.pager .btn.primary:hover{background:none;color:var(--accent)}

/* --- Contact --------------------------------------------------------------- */
.contact-hero{padding:var(--space-section) 0 var(--space-block)}
.contact-hero-inner{max-width:var(--max);margin:auto;padding:0 clamp(16px,3vw,42px)}
.contact-hero .eyebrow{color:var(--accent)}
.contact-hero h1{font-size:clamp(38px,4.4vw,56px);margin:18px 0 14px;color:var(--ink)}
.contact-hero p{font-size:clamp(17px,1.5vw,21px);color:var(--body);max-width:58ch;margin:0}
/* Contact page polish: balanced headings, no widows/uneven last lines in body copy */
.contact-hero h1,.contact-aside h2,.contact-hero p{text-wrap:balance}
.contact-aside p,.form-note{text-wrap:pretty}

.contact-layout{max-width:var(--max);margin:auto;padding:0 clamp(16px,3vw,42px) var(--space-section);display:grid;grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr);gap:clamp(40px,6vw,90px);align-items:start}
.contact-aside h2{font-size:clamp(24px,3vw,36px);line-height:1.1;margin:8px 0 14px}
.contact-aside p{color:var(--body);font-size:16px}
.contact-methods{list-style:none;margin:32px 0 0;padding:0;display:grid;gap:22px}
.contact-methods li{display:grid;gap:3px}
.contact-methods .label{font-size:11px;text-transform:uppercase;letter-spacing:.12em;font-weight:700;color:var(--accent)}
.contact-methods a{color:var(--ink);text-decoration:none;font-weight:600;font-size:17px;word-break:break-word}
.contact-methods a:hover{color:var(--accent)}

/* underline-only fields â€” no boxes */
.contact-aside{padding-top:clamp(26px,3.5vw,44px)}
.contact-form{display:block;background:rgba(255,255,255,.5);backdrop-filter:blur(14px) saturate(1.08);-webkit-backdrop-filter:blur(14px) saturate(1.08);border:1px solid rgba(255,255,255,.55);box-shadow:0 10px 34px rgba(20,16,10,.07);padding:clamp(26px,3.5vw,44px)}
.field{display:grid;gap:8px;margin-bottom:28px}
.field-row{display:grid;grid-template-columns:1fr 1fr;gap:clamp(20px,4vw,40px)}
.field label{font-size:12px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.08em}
.field label .req{color:var(--accent)}
.field input,.field select,.field textarea{font-family:var(--font);font-size:16px;color:var(--ink);background:none;border:0;border-bottom:1px solid var(--hair);padding:10px 0;width:100%;transition:border-color .15s}
.field textarea{min-height:130px;resize:vertical}
.field select{appearance:none;-webkit-appearance:none}
.field input:focus,.field select:focus,.field textarea:focus{outline:none;border-bottom-color:var(--accent)}
.contact-form .btn{margin-top:8px}
.form-note{margin:18px 0 0;font-size:14px;color:var(--muted)}
.form-note a{color:var(--ink);font-weight:600}

/* --- Footer (full-bleed dark band) ---------------------------------------- */
footer{background:var(--dark);padding:48px clamp(16px,3vw,42px);display:flex;justify-content:space-between;align-items:center;gap:16px;color:rgba(255,255,255,.55);font-size:14px}
footer span:first-child{font-family:var(--font-head);font-weight:800;font-size:18px;letter-spacing:.5px;color:#fff}

/* --- Lightbox -------------------------------------------------------------- */
.lightbox{position:fixed;inset:0;background:rgba(8,9,12,.95);z-index:100;display:grid;place-items:center;padding:78px 24px 24px}
.lightbox[hidden]{display:none}
.lightbox figure{margin:0;max-width:min(96vw,1500px);max-height:86vh}
.lightbox img{max-width:100%;max-height:78vh;object-fit:contain}
.lightbox figcaption{color:#fff;text-align:center;margin-top:14px;font-size:14px}
.lightbox-close{position:fixed;right:22px;top:18px;width:46px;height:46px;border:1px solid rgba(255,255,255,.3);background:none;color:#fff;font-size:28px;cursor:pointer}
.lightbox-close:hover{background:rgba(255,255,255,.1)}

/* --- Responsive ------------------------------------------------------------ */
@media(max-width:920px){
  .flow-email-design .work-gallery{columns:1;display:grid;grid-template-columns:1fr}
  /* header stays a single row; the nav moves into the menu panel further down */
  .hero-grid,.page-hero-inner,.content-system,.case-card,.contact-layout{grid-template-columns:1fr}
  .case-card:nth-child(even) .case-media{order:0}
  .summary-panel{position:static}
  .hero-showcase{grid-template-columns:1fr 1fr}
  .work-gallery{grid-template-columns:1fr}
  .hero-work:first-child{grid-row:auto}
  .work-item figcaption{display:block}
  .work-item figcaption span{display:block;margin-top:3px}
}
@media(max-width:560px){
  .flow-email-design .work-gallery{columns:1;display:grid;grid-template-columns:1fr}
  .hero-showcase,.work-gallery,.field-row{grid-template-columns:1fr}
  .actions{gap:18px}
  .actions .btn{width:100%}
  .content-system,.page-hero,.product-hero{padding-left:16px;padding-right:16px}
  .case-grid,.section-intro,.pager,.contact-layout,.fw,.principles,.expand-cards{padding-left:16px;padding-right:16px}
  .pager{flex-direction:column}
  footer{flex-direction:column;align-items:flex-start;gap:8px}
  .lightbox{padding:74px 12px 20px}
}
@media(prefers-reduced-motion:reduce){*,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;scroll-behavior:auto!important;transition-duration:.01ms!important}}

/* ===========================================================================
   Portfolio 2026 additions â€” services grid + marketing archive
   Additive only. Reuses the existing hairline / no-box / no-radius system.
   =========================================================================== */
.services{max-width:var(--max);margin:0 auto;padding:0 clamp(16px,3vw,42px);display:grid;grid-template-columns:repeat(3,1fr);border-top:1px solid var(--hair)}
.service{padding:clamp(22px,3vw,30px) clamp(20px,3vw,30px) clamp(22px,3vw,30px) 0;border-bottom:1px solid var(--hair)}
.service:not(:nth-child(3n+1)){border-left:1px solid var(--hair);padding-left:clamp(20px,3vw,30px)}
.service h3{font-size:17px;font-weight:700;letter-spacing:-.01em;margin:0 0 6px;color:var(--ink)}
.service p{margin:0;color:var(--body);font-size:15px}

/* --- Homepage refinements: hero scale, stacked showcase, 52px headlines ---- */
.home .hero-copy h1{font-size:clamp(44px,4.8vw,60px);line-height:calc(1em + 6px)}
.nowrap{white-space:nowrap}
.home .section-intro h2,.home .cr-band-copy h2,.home .case-copy h2,.home .fw-intro h2,.home .pr-intro h2{font-size:clamp(27px,3.2vw,42px);line-height:calc(1.03em + 5px)}
.home .hero-showcase{grid-template-columns:1fr 1fr;align-items:stretch}
.hero-col-cr{display:grid;gap:18px;align-content:start}
.hero-col-ss{position:relative}
.hero-col-ss .lightbox-link{position:absolute;inset:0}
.hero-col-ss .work-image{width:100%;height:100%;object-fit:cover;object-position:top}

/* ---- Work page rework: full-bleed image hero + editorial bands (home only) ---- */
.work-hero{position:relative;display:grid;place-items:center;min-height:clamp(560px,78vh,820px);overflow:hidden;background:#f3eee1}
.work-hero .wh-img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center 38%;animation:hero-jolt 12s steps(1,end) infinite;will-change:transform}
.work-hero .wh-copy{position:relative;z-index:1;display:flex;flex-direction:column;align-items:flex-start;text-align:left;width:100%;max-width:var(--max);padding:calc(var(--header-h,76px) + clamp(36px,5.5vw,76px)) clamp(16px,4vw,42px) clamp(140px,20vh,220px)}
/* near-invisible atmospheric haze behind the headline: softens the artwork
   under the type for contrast without reading as a white overlay */
/* The haze box used to be a fixed 984px (82% of the capped 1200px copy
   column), so on wide screens its left edge landed inside the hero: 161px in
   at 1920, 481px at 2560. The gradient has not reached transparent by that
   edge, so the rectangle cut a hard vertical seam across the artwork.
   The box now always starts at the viewport edge, where the hero clips it
   anyway, and the ellipse is pinned in absolute units measured from the
   right edge so its size and position on screen are exactly what they were:
   551px radius, centre 590px in from the right. Only the invisible
   left-hand extent changes. */
.work-hero .wh-copy::before{content:"";position:absolute;left:calc(50% - 50vw);right:34%;top:-8%;height:124%;
  background:radial-gradient(551px 58% at calc(100% - 590px) 54%,rgba(255,255,255,.42) 0%,rgba(255,255,255,.28) 40%,rgba(255,255,255,.1) 64%,rgba(255,255,255,0) 82%);
  z-index:-1;pointer-events:none}
/* the hero starts at the very top of the page, behind the fixed (transparent) nav */
.home main{padding-top:0}
/* organic dissolve: the artwork melts into the page surface instead of ending on a hard edge */
.work-hero::after{content:"";position:absolute;left:0;right:0;bottom:-1px;height:clamp(150px,24vh,300px);background:linear-gradient(180deg,rgba(255,255,255,0) 0%,rgba(255,255,255,.14) 42%,rgba(255,255,255,.42) 68%,rgba(255,255,255,.82) 87%,var(--surface) 100%);pointer-events:none}
/* ---- living scene: ring + orbiting light around the baked sun, and the walking
   figure. Positions are set by work-scene.js from measured sun anchors; the
   animations are pure CSS transforms (GPU), disabled under reduced motion. ---- */
.fo-scene,.wh-figure{position:absolute;width:0;height:0;pointer-events:none}
/* Figure signal-slice glitch, layered ON TOP of the locked bigger figure.
   The base walker snaps 30% bigger / closer / 20px left and holds (frame-slip,
   46-63%); during two fast ~180ms bursts inside that hold it breaks into offset
   horizontal slices, with two leg slices tinted cyan / magenta-red as tight,
   faint fringe. Everything sits at the same bigger transform so the stripes
   land on the displaced figure; then it all snaps back. Reduced-motion: plain
   static figure. Nothing blurs, bounces, or moves vertically off the path. */
@keyframes frame-slip{
  0%,45.99%{transform:translate(-50%,calc(-100% + 49px)) scale(1)}
  46%,62.99%{transform:translate(calc(-50% - 20px),calc(-100% + 84px)) scale(1.3)}
  63%,100%{transform:translate(-50%,calc(-100% + 49px)) scale(1)}
}
.wg-s1,.wg-s2,.wg-cyan,.wg-red{opacity:0;transform-origin:50% 100%;animation-duration:12s;animation-timing-function:steps(1,end);animation-iteration-count:infinite;will-change:opacity,transform}
.wg-s1{clip-path:inset(19% 0 56% 0);animation-name:wg-s1}
.wg-s2{clip-path:inset(44% 0 40% 0);animation-name:wg-s2}
.wg-cyan{clip-path:inset(62% 0 12% 0);filter:url(#wg-cyan);animation-name:wg-cyan}
.wg-red{clip-path:inset(51% 0 27% 0);filter:url(#wg-red);animation-name:wg-red}
video.wg-cyan,video.wg-red{mix-blend-mode:screen}   /* beat .walker's multiply */
@keyframes wg-s1{
  0%,46.49%{opacity:0;transform:translate(-50%,calc(-100% + 49px)) scale(1)}
  46.5%{opacity:1;transform:translate(calc(-50% - 20px + 7px),calc(-100% + 84px)) scale(1.3)}
  47%{opacity:1;transform:translate(calc(-50% - 20px + 3px),calc(-100% + 84px)) scale(1.3)}
  47.5%{opacity:1;transform:translate(calc(-50% - 20px + 9px),calc(-100% + 84px)) scale(1.3)}
  48%,57.99%{opacity:0;transform:translate(calc(-50% - 20px),calc(-100% + 84px)) scale(1.3)}
  58%{opacity:1;transform:translate(calc(-50% - 20px + 5px),calc(-100% + 84px)) scale(1.3)}
  58.5%{opacity:1;transform:translate(calc(-50% - 20px + 8px),calc(-100% + 84px)) scale(1.3)}
  59%{opacity:1;transform:translate(calc(-50% - 20px + 4px),calc(-100% + 84px)) scale(1.3)}
  59.5%,100%{opacity:0;transform:translate(-50%,calc(-100% + 49px)) scale(1)}
}
@keyframes wg-s2{
  0%,46.49%{opacity:0;transform:translate(-50%,calc(-100% + 49px)) scale(1)}
  46.5%{opacity:1;transform:translate(calc(-50% - 20px - 8px),calc(-100% + 84px)) scale(1.3)}
  47%{opacity:1;transform:translate(calc(-50% - 20px - 4px),calc(-100% + 84px)) scale(1.3)}
  47.5%{opacity:1;transform:translate(calc(-50% - 20px - 10px),calc(-100% + 84px)) scale(1.3)}
  48%,57.99%{opacity:0;transform:translate(calc(-50% - 20px),calc(-100% + 84px)) scale(1.3)}
  58%{opacity:1;transform:translate(calc(-50% - 20px - 6px),calc(-100% + 84px)) scale(1.3)}
  58.5%{opacity:1;transform:translate(calc(-50% - 20px - 9px),calc(-100% + 84px)) scale(1.3)}
  59%{opacity:1;transform:translate(calc(-50% - 20px - 3px),calc(-100% + 84px)) scale(1.3)}
  59.5%,100%{opacity:0;transform:translate(-50%,calc(-100% + 49px)) scale(1)}
}
@keyframes wg-cyan{
  0%,46.49%{opacity:0;transform:translate(-50%,calc(-100% + 49px)) scale(1)}
  46.5%{opacity:0.45;transform:translate(calc(-50% - 20px - 3px),calc(-100% + 84px)) scale(1.3)}
  47%{opacity:0.45;transform:translate(calc(-50% - 20px - 2px),calc(-100% + 84px)) scale(1.3)}
  47.5%{opacity:0.45;transform:translate(calc(-50% - 20px - 4px),calc(-100% + 84px)) scale(1.3)}
  48%,57.99%{opacity:0;transform:translate(calc(-50% - 20px),calc(-100% + 84px)) scale(1.3)}
  58%{opacity:0.45;transform:translate(calc(-50% - 20px - 3px),calc(-100% + 84px)) scale(1.3)}
  58.5%{opacity:0.45;transform:translate(calc(-50% - 20px - 4px),calc(-100% + 84px)) scale(1.3)}
  59%{opacity:0.45;transform:translate(calc(-50% - 20px - 2px),calc(-100% + 84px)) scale(1.3)}
  59.5%,100%{opacity:0;transform:translate(-50%,calc(-100% + 49px)) scale(1)}
}
@keyframes wg-red{
  0%,46.49%{opacity:0;transform:translate(-50%,calc(-100% + 49px)) scale(1)}
  46.5%{opacity:0.45;transform:translate(calc(-50% - 20px + 3px),calc(-100% + 84px)) scale(1.3)}
  47%{opacity:0.45;transform:translate(calc(-50% - 20px + 2px),calc(-100% + 84px)) scale(1.3)}
  47.5%{opacity:0.45;transform:translate(calc(-50% - 20px + 4px),calc(-100% + 84px)) scale(1.3)}
  48%,57.99%{opacity:0;transform:translate(calc(-50% - 20px),calc(-100% + 84px)) scale(1.3)}
  58%{opacity:0.45;transform:translate(calc(-50% - 20px + 3px),calc(-100% + 84px)) scale(1.3)}
  58.5%{opacity:0.45;transform:translate(calc(-50% - 20px + 4px),calc(-100% + 84px)) scale(1.3)}
  59%{opacity:0.45;transform:translate(calc(-50% - 20px + 2px),calc(-100% + 84px)) scale(1.3)}
  59.5%,100%{opacity:0;transform:translate(-50%,calc(-100% + 49px)) scale(1)}
}
/* the whole dune image shudders faintly during the episode */
@keyframes hero-jolt{
  0%,45.99%{transform:none}
  46.00%{transform:translateX(1.0px) scale(1.0075)}
  47.30%{transform:translateX(2.4px) scale(1.0087)}
  48.60%{transform:translateX(1.9px) scale(1.0085)}
  49.90%{transform:translateX(-3.8px) scale(1.0058)}
  51.20%{transform:translateX(3.5px) scale(1.0069)}
  52.50%{transform:translateX(3.2px) scale(1.0037)}
  53.80%{transform:translateX(-0.2px) scale(1.0045)}
  55.10%{transform:translateX(0.4px) scale(1.0064)}
  56.40%{transform:translateX(-3.9px) scale(1.0043)}
  57.70%{transform:translateX(-1.8px) scale(1.0085)}
  59.00%{transform:translateX(2.1px) scale(1.004)}
  60.30%{transform:translateX(2.4px) scale(1.0038)}
  61.60%{transform:translateX(0.9px) scale(1.0038)}
  62.90%{transform:translateX(-4.0px) scale(1.0082)}
  63.01%,100%{transform:none}
}
/* horizontal slices of the dune image that displace sideways = frame tearing */
.wh-tear{position:absolute;inset:0;background:url("assets/work/work-hero.webp") center 38%/cover no-repeat;opacity:0;pointer-events:none;will-change:transform,opacity,clip-path;animation:tear-a 12s steps(1,end) infinite}
.wh-tear-2{animation-name:tear-b}.wh-tear-3{animation-name:tear-c}.wh-tear-4{animation-name:tear-d}
@keyframes tear-a{
  0%,45.99%{opacity:0;transform:none;clip-path:inset(28% 0 64% 0)}
  46.00%{opacity:0;transform:translateX(-4.6px);clip-path:inset(27% 0 65% 0)}
  47.30%{opacity:0;transform:translateX(-3.4px);clip-path:inset(29% 0 64% 0)}
  48.60%{opacity:0;transform:translateX(-4.7px);clip-path:inset(29% 0 64% 0)}
  49.90%{opacity:1;transform:translateX(6.3px);clip-path:inset(28% 0 64% 0)}
  51.20%{opacity:0;transform:translateX(-5.7px);clip-path:inset(27% 0 64% 0)}
  52.50%{opacity:1;transform:translateX(-7.9px);clip-path:inset(28% 0 64% 0)}
  53.80%{opacity:1;transform:translateX(5.1px);clip-path:inset(28% 0 64% 0)}
  55.10%{opacity:1;transform:translateX(3.3px);clip-path:inset(27% 0 65% 0)}
  56.40%{opacity:0;transform:translateX(4.0px);clip-path:inset(29% 0 63% 0)}
  57.70%{opacity:1;transform:translateX(-2.1px);clip-path:inset(28% 0 63% 0)}
  59.00%{opacity:0;transform:translateX(-5.1px);clip-path:inset(29% 0 63% 0)}
  60.30%{opacity:1;transform:translateX(6.9px);clip-path:inset(29% 0 64% 0)}
  61.60%{opacity:0;transform:translateX(0.4px);clip-path:inset(29% 0 63% 0)}
  62.90%{opacity:0;transform:translateX(4.8px);clip-path:inset(29% 0 63% 0)}
  63.01%,100%{opacity:0;transform:none;clip-path:inset(28% 0 64% 0)}
}
@keyframes tear-b{
  0%,45.99%{opacity:0;transform:none;clip-path:inset(45% 0 47% 0)}
  46.00%{opacity:0;transform:translateX(-5.7px);clip-path:inset(45% 0 47% 0)}
  47.30%{opacity:1;transform:translateX(-2.2px);clip-path:inset(46% 0 47% 0)}
  48.60%{opacity:1;transform:translateX(-2.6px);clip-path:inset(44% 0 46% 0)}
  49.90%{opacity:1;transform:translateX(2.6px);clip-path:inset(46% 0 46% 0)}
  51.20%{opacity:1;transform:translateX(6.8px);clip-path:inset(45% 0 47% 0)}
  52.50%{opacity:1;transform:translateX(1.3px);clip-path:inset(46% 0 47% 0)}
  53.80%{opacity:1;transform:translateX(-6.2px);clip-path:inset(46% 0 46% 0)}
  55.10%{opacity:0;transform:translateX(4.7px);clip-path:inset(44% 0 47% 0)}
  56.40%{opacity:1;transform:translateX(1.8px);clip-path:inset(46% 0 46% 0)}
  57.70%{opacity:1;transform:translateX(-4.9px);clip-path:inset(46% 0 47% 0)}
  59.00%{opacity:1;transform:translateX(7.0px);clip-path:inset(46% 0 48% 0)}
  60.30%{opacity:1;transform:translateX(-0.2px);clip-path:inset(45% 0 47% 0)}
  61.60%{opacity:0;transform:translateX(-1.3px);clip-path:inset(44% 0 47% 0)}
  62.90%{opacity:1;transform:translateX(6.4px);clip-path:inset(45% 0 47% 0)}
  63.01%,100%{opacity:0;transform:none;clip-path:inset(45% 0 47% 0)}
}
@keyframes tear-c{
  0%,45.99%{opacity:0;transform:none;clip-path:inset(57% 0 35% 0)}
  46.00%{opacity:0;transform:translateX(-6.6px);clip-path:inset(57% 0 36% 0)}
  47.30%{opacity:0;transform:translateX(-2.2px);clip-path:inset(58% 0 36% 0)}
  48.60%{opacity:0;transform:translateX(2.6px);clip-path:inset(58% 0 35% 0)}
  49.90%{opacity:0;transform:translateX(-3.5px);clip-path:inset(57% 0 36% 0)}
  51.20%{opacity:1;transform:translateX(-3.3px);clip-path:inset(58% 0 35% 0)}
  52.50%{opacity:0;transform:translateX(-7.8px);clip-path:inset(57% 0 35% 0)}
  53.80%{opacity:0;transform:translateX(-0.6px);clip-path:inset(58% 0 35% 0)}
  55.10%{opacity:0;transform:translateX(-2.4px);clip-path:inset(57% 0 35% 0)}
  56.40%{opacity:0;transform:translateX(-2.4px);clip-path:inset(58% 0 36% 0)}
  57.70%{opacity:1;transform:translateX(7.6px);clip-path:inset(58% 0 35% 0)}
  59.00%{opacity:1;transform:translateX(-5.3px);clip-path:inset(58% 0 36% 0)}
  60.30%{opacity:1;transform:translateX(3.1px);clip-path:inset(57% 0 35% 0)}
  61.60%{opacity:1;transform:translateX(4.5px);clip-path:inset(57% 0 35% 0)}
  62.90%{opacity:0;transform:translateX(2.0px);clip-path:inset(58% 0 35% 0)}
  63.01%,100%{opacity:0;transform:none;clip-path:inset(57% 0 35% 0)}
}
@keyframes tear-d{
  0%,45.99%{opacity:0;transform:none;clip-path:inset(70% 0 20% 0)}
  46.00%{opacity:0;transform:translateX(-6.6px);clip-path:inset(69% 0 20% 0)}
  47.30%{opacity:1;transform:translateX(-3.9px);clip-path:inset(70% 0 20% 0)}
  48.60%{opacity:1;transform:translateX(-0.4px);clip-path:inset(69% 0 19% 0)}
  49.90%{opacity:1;transform:translateX(-2.6px);clip-path:inset(69% 0 19% 0)}
  51.20%{opacity:0;transform:translateX(-0.5px);clip-path:inset(70% 0 20% 0)}
  52.50%{opacity:1;transform:translateX(-3.7px);clip-path:inset(71% 0 19% 0)}
  53.80%{opacity:0;transform:translateX(-3.1px);clip-path:inset(70% 0 19% 0)}
  55.10%{opacity:1;transform:translateX(-1.8px);clip-path:inset(69% 0 20% 0)}
  56.40%{opacity:0;transform:translateX(0.6px);clip-path:inset(70% 0 20% 0)}
  57.70%{opacity:0;transform:translateX(4.5px);clip-path:inset(70% 0 21% 0)}
  59.00%{opacity:0;transform:translateX(-1.8px);clip-path:inset(69% 0 20% 0)}
  60.30%{opacity:1;transform:translateX(6.4px);clip-path:inset(71% 0 20% 0)}
  61.60%{opacity:0;transform:translateX(5.5px);clip-path:inset(69% 0 19% 0)}
  62.90%{opacity:0;transform:translateX(1.6px);clip-path:inset(69% 0 20% 0)}
  63.01%,100%{opacity:0;transform:none;clip-path:inset(70% 0 20% 0)}
}
/* monochrome scanlines + static over the whole frame (soft-light, no color) */
.wh-signal{position:absolute;inset:0;pointer-events:none;opacity:0;mix-blend-mode:soft-light;background:repeating-linear-gradient(0deg,rgba(0,0,0,.4) 0 1px,transparent 1px 3px),url("data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20width='160'%20height='160'%3E%3Cfilter%20id='n'%3E%3CfeTurbulence%20type='fractalNoise'%20baseFrequency='0.82'%20numOctaves='2'%20stitchTiles='stitch'/%3E%3CfeColorMatrix%20type='saturate'%20values='0'/%3E%3C/filter%3E%3Crect%20width='100%25'%20height='100%25'%20filter='url(%23n)'/%3E%3C/svg%3E");background-size:auto,160px 160px;animation:signal 12s steps(1,end) infinite;will-change:opacity,background-position}
@keyframes signal{
  0%,45.99%{opacity:0;background-position:0 0,0 0}
  46.00%{opacity:0.32;background-position:0 0px,36px 46px}
  47.30%{opacity:0.47;background-position:0 0px,153px 146px}
  48.60%{opacity:0.37;background-position:0 0px,156px 79px}
  49.90%{opacity:0.33;background-position:0 0px,157px 78px}
  51.20%{opacity:0.29;background-position:0 0px,19px 99px}
  52.50%{opacity:0.34;background-position:0 0px,125px 132px}
  53.80%{opacity:0.22;background-position:0 1px,43px 149px}
  55.10%{opacity:0.42;background-position:0 0px,42px 24px}
  56.40%{opacity:0.48;background-position:0 0px,97px 75px}
  57.70%{opacity:0.39;background-position:0 1px,118px 18px}
  59.00%{opacity:0.42;background-position:0 0px,85px 53px}
  60.30%{opacity:0.3;background-position:0 1px,74px 57px}
  61.60%{opacity:0.41;background-position:0 1px,5px 40px}
  62.90%{opacity:0.34;background-position:0 1px,142px 87px}
  63.01%,100%{opacity:0;background-position:0 0,0 0}
}
/* walking figure: a fixed-size, fixed-position walk-cycle video anchored just
   below the sun. The clip's pure-white background vanishes via multiply blend;
   the figure never moves or scales â€” only his gait animates. Two staggered
   copies of the same clip (whose first and last frames match) crossfade at the
   loop point so the walk never visibly resets. */
.walker{position:absolute;left:0;top:0;height:calc(var(--sun-d,300px)*.266 - 10px);width:auto;transform:translate(-50%,calc(-100% + 49px));transform-origin:50% 100%;mix-blend-mode:multiply;opacity:1;transition:opacity .8s ease;pointer-events:none}
.walker--a,.walker--b{animation:frame-slip 12s steps(1,end) infinite}
.walker--b{opacity:0}
/* faint warm haze pooling around the figure's lower half so he settles into
   the valley floor instead of sitting on top of it */
.wh-figure::after{content:"";position:absolute;left:0;top:0;width:calc(var(--sun-d,300px)*.52);height:calc(var(--sun-d,300px)*.2);transform:translate(-50%,calc(-50% + 42px));background:radial-gradient(ellipse 50% 50% at center,rgba(246,238,225,.36) 0%,rgba(246,238,225,.15) 55%,rgba(246,238,225,0) 75%);pointer-events:none}
.home .wwm-band{position:relative;overflow:hidden}
.home .wwm-band .section-intro,.home .wwm-band .services{position:relative;z-index:1}
@media(prefers-reduced-motion:reduce){
  .wg-s1,.wg-s2,.wg-cyan,.wg-red{display:none}
  .wh-tear,.wh-signal{display:none}
  .wh-figure::after{display:none}
}
.work-hero .eyebrow{color:var(--accent)}
.work-hero h1{font-size:clamp(36px,3.9vw,60px);line-height:1.08;letter-spacing:-.02em;color:var(--ink);margin:16px 0 0;max-width:min(16ch,100%);text-wrap:balance}
.work-hero p{font-size:clamp(16px,1.35vw,19px);color:var(--body);max-width:min(44ch,100%);margin:18px 0 0;text-wrap:pretty}
.work-hero .actions{margin-top:clamp(26px,2.8vw,36px)}

/* ---- Featured work: alternating editorial rows, exhibition numbering ---- */
.fw{max-width:var(--max);margin:auto;padding:clamp(70px,9vw,130px) clamp(16px,3vw,42px) clamp(24px,3vw,44px)}
.fw-intro{max-width:64ch}
.fw-intro h2{margin:14px 0}
.fw-intro p{color:var(--body);font-size:18px;margin:0}
.fw-item{display:grid;grid-template-columns:minmax(0,1.25fr) minmax(0,.75fr);gap:clamp(28px,5vw,76px);align-items:center;padding:clamp(44px,6vw,84px) 0}
.fw article+article{border-top:1px solid var(--hair)}
.fw-item:nth-of-type(even){grid-template-columns:minmax(0,.75fr) minmax(0,1.25fr)}
.fw-item:nth-of-type(even) .fw-media{order:2}
.fw-media{position:relative;display:block;height:330px;overflow:hidden;border:1px solid var(--hair);box-shadow:0 24px 60px rgba(20,16,10,.14)}
/* The pan duration is per item because these captures are wildly different
   heights: cover scales each to the same width, so a 12,327px tall page has
   to travel 3632px where a 3661px one travels 1432. One fixed duration made
   Frazier run at 404px/s against Steinway's 159 and it read as broken. Each
   item now gets a duration matched to its own distance, so they share a pace.
   Same --var pattern the campaign cards already use for their pan. */
.fw-media .work-image{width:100%;height:100%;max-height:none;object-fit:cover;object-position:top;transition:object-position var(--fw-pan,10.5s) linear}
.fw-media:hover .work-image,.fw-media:focus-visible .work-image{object-position:bottom;opacity:1}
.fw-num{display:block;font-family:var(--font-head);font-weight:800;font-size:clamp(14px,1.3vw,16px);letter-spacing:.24em;color:var(--accent)}
.fw-copy h3{font-family:var(--font-head);font-weight:800;font-size:clamp(26px,3vw,40px);letter-spacing:-.015em;line-height:1.06;color:var(--ink);margin:10px 0 14px}
.fw-copy p{color:var(--body);font-size:17px;margin:0 0 18px;max-width:44ch}
.zoom-cue.zoom-cue-light{color:#fff;text-shadow:0 1px 6px rgba(0,0,0,.85)}

/* ---- How I work: three principles ---- */
.principles{max-width:var(--max);margin:auto;padding:clamp(40px,5vw,72px) clamp(16px,3vw,42px) clamp(80px,10vw,150px)}
.pr-intro h2{margin:14px 0 0}
.pr-list{list-style:none;display:grid;grid-template-columns:repeat(3,1fr);gap:clamp(24px,4vw,48px);margin:clamp(30px,4vw,52px) 0 0;padding:clamp(30px,4vw,48px) 0 0;border-top:1px solid var(--hair)}
.pr-list li{margin:0;padding:0}
.pr-num{display:block;font-family:var(--font-head);font-weight:800;font-size:clamp(14px,1.3vw,16px);letter-spacing:.24em;color:var(--accent)}
.pr-list h3{font-size:clamp(19px,1.8vw,23px);font-weight:700;letter-spacing:-.01em;color:var(--ink);margin:12px 0 10px}
.pr-list p{margin:0;color:var(--body);font-size:16px;max-width:38ch}

/* ---- Campaign highlights: expand-on-hover image accordion ----
   Cards sit as equal strips; hovering/focusing one expands it to reveal the
   full piece while the rest collapse. One is expanded by default at rest. */
.expand-cards{max-width:var(--max);margin:0 auto;padding:12px clamp(16px,3vw,42px) 28px;display:flex;gap:10px;height:clamp(320px,42vw,460px)}
.ec-card{position:relative;flex:1 1 0%;min-width:0;overflow:hidden;display:block;border:1px solid var(--hair);background:#fff;cursor:pointer;box-shadow:0 8px 22px rgba(20,16,10,.06);transition:flex-grow .55s cubic-bezier(.22,.61,.36,1),box-shadow .4s ease}
.ec-card .work-image{position:absolute;inset:0;width:100%;height:100%;max-width:none;max-height:none;object-fit:cover;object-position:top center;-webkit-user-drag:none;user-select:none;transition:object-position var(--pan,5.5s) linear}
/* the expanded card slowly pans top->bottom, revealing the full length of the
   taller pieces while it is hovered/focused (returns to top on leave). Per-card
   speed via --pan; .ec-static opts out entirely (e.g. wide banners). */
.ec-card:not(.ec-static):hover .work-image,.ec-card:not(.ec-static):focus-visible .work-image{object-position:bottom center}
.ec-card::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,rgba(20,16,10,0) 58%,rgba(20,16,10,.16) 100%);opacity:0;transition:opacity .4s ease;pointer-events:none}
.ec-label{position:absolute;left:0;bottom:0;z-index:2;padding:9px 15px;background:rgba(255,255,255,.92);color:var(--ink);font-size:12px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;white-space:nowrap;opacity:0;transform:translateY(6px);transition:opacity .35s ease .05s,transform .35s ease .05s}
.ec-card.is-active{flex-grow:6}
.ec-card:hover,.ec-card:focus-visible{flex-grow:6;box-shadow:0 22px 48px rgba(20,16,10,.16)}
.ec-card:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.ec-card.is-active .ec-label,.ec-card:hover .ec-label,.ec-card:focus-visible .ec-label{opacity:1;transform:none}
.ec-card.is-active::after,.ec-card:hover::after,.ec-card:focus-visible::after{opacity:1}
/* while the row is hovered, the default-active card yields unless it's the one hovered */
.expand-cards:hover .ec-card.is-active:not(:hover){flex-grow:1}
.expand-cards:hover .ec-card.is-active:not(:hover) .ec-label{opacity:0;transform:translateY(6px)}
.expand-cards:hover .ec-card.is-active:not(:hover)::after{opacity:0}
/* vertical variant: same hover-to-expand idea stacked top-to-bottom (Banner
   Design gallery), but sized differently from the horizontal original.
   All 9 banner assets share one exact 760x235 ratio, so rather than
   flex-grow splitting an arbitrary fixed container height (which either
   crops the expanded banner or letterboxes it depending on the row it
   lands on), each card's flex-basis is a definite length: a short fixed
   height while collapsed, or a container-query-computed height that
   exactly matches width/aspect-ratio while expanded. That second value is
   never "auto" (which browsers can't reliably transition), so the flex
   -basis change animates as a smooth height tween with the image itself
   staying full width throughout, per the artwork's own proportions:
   - collapsed: object-fit:cover fills the row's full width and lets
     overflow:hidden crop whatever height doesn't fit, i.e. a "peek".
   - expanded: the row's height already equals width/ratio exactly, so
     cover renders the complete banner with no crop and no letterbox.
   container-type on the gallery itself makes cqw resolve to its own
   rendered width, independent of viewport, and .work-flow's max-width/
   margin/padding are reset since this lives nested in that column
   rather than standing alone as its own section. */
.expand-cards.is-vertical{max-width:none;margin:0;padding:0;flex-direction:column;height:auto;container-type:inline-size}
.expand-cards.is-vertical .ec-card{flex:0 0 64px;transition:flex-basis .55s cubic-bezier(.22,.61,.36,1),box-shadow .4s ease}
.expand-cards.is-vertical .ec-card .work-image{object-fit:cover;object-position:center;transition:none}
.expand-cards.is-vertical .ec-card.is-active,
.expand-cards.is-vertical .ec-card:hover,
.expand-cards.is-vertical .ec-card:focus-visible{flex-basis:calc(100cqw * 235 / 760)}
.expand-cards.is-vertical:hover .ec-card.is-active:not(:hover){flex-basis:64px}
/* Banner Design page layout: reuses .logo-selector/.ls-copy (same copy-left,
   gallery-right grid as Brand Identity/Landing Pages) without their tab
   selector. The Overview/Formats block keeps .summary-panel's type styles
   but drops the sticky positioning that pattern uses elsewhere, since here
   it's flowing inside .ls-copy alongside the page title, not standing alone
   beside a long scrolling list. */
.banner-page .ls-copy .summary-panel{position:static;margin-top:clamp(16px,2vw,26px)}

.home .case-card{padding:clamp(48px,6.5vw,96px) 0}
/* The middle case card is the only one with its copy on the left, and its text
   measures 285px in a 535px column, so 250px of dead column sat between the
   copy and the artwork: a 361px gap against 111px either side. Pushing the
   copy to the end of its column closed the gap but read as right aligned
   against the neighbouring sections. Instead both tracks are sized to their
   content and the pair is centred, so the card reads as one composition
   rather than two elements drifting apart. minmax so the tracks can still
   shrink on a narrow desktop. Below 921px the card stacks and none of this
   applies. */
@media(min-width:921px){
  .home .case-card:nth-child(even){grid-template-columns:minmax(0,300px) minmax(0,510px);justify-content:center}
  .home .case-card:nth-child(even) .case-copy{max-width:300px}
}

/* ---- bottom frame: companion dune artwork behind Work With Me + footer ----
   Anchored to the page's bottom edge; the -30px vertical offset lowers the
   whole image (its baked-in top fade moves down with it by the same 30px)
   while leaving background-size/position otherwise untouched, so the crop
   and the fade at the very bottom of the page render exactly as before.
   Shared by every standard page (Work, Brand Identity, Landing Pages, UX/UI,
   About, Contact, Email Design, Product Design, UX Research, Banner Design)
   so the treatment, position, and crop stay identical across all of them;
   Cosmic Reader keeps its own separate night footer. ---- */
/* Layer 1 (on top) deepens the fade at the very bottom of the page. The artwork
   carries its own bottom fade, but it is baked into roughly the last 80px of the
   render, which is tight enough that the dunes still read as a warm band behind
   the footer. This gradient starts the dissolve above the footer and carries it
   most of the way to the page colour, so the artwork settles into white instead
   of stopping against it. Kept deliberately light: it stays under .35 until it
   reaches the footer and stops at .88 rather than full white, so the dune crests
   still read. Anchored to the page bottom (no -30px offset, which belongs to the
   image alone) and sized in vw so it scales with the artwork. */
body.home,body.uxui,body.contact-page,body.logo-page,body.landing-page,body.about-page,body.email-page,body.product-page,body.research-page,body.banner-page{background-image:linear-gradient(180deg,rgba(255,255,255,0) 0%,rgba(255,255,255,.14) 35%,rgba(255,255,255,.34) 62%,rgba(255,255,255,.62) 84%,rgba(255,255,255,.88) 100%),url("assets/work/footer-bg-faded.webp");background-repeat:no-repeat,no-repeat;background-position:center bottom,center bottom -30px;background-size:100% clamp(150px,17vw,270px),max(100%,980px) auto}
.home .wwm-band{background:transparent;border-bottom:0}
.home .wwm-band h2{color:var(--ink)}
.home .wwm-band .section-intro p{color:var(--body)}
.home .wwm-band .services{border-top-color:var(--hair)}
.home .wwm-band .service{border-bottom-color:var(--hair)}
.home .wwm-band .service:not(:nth-child(3n+1)){border-left-color:var(--hair)}
.home .wwm-band .service h3{color:var(--ink)}
.home .wwm-band .service p{color:var(--body)}
.home .wwm-band .btn.primary{background:var(--ink);border-color:var(--ink);color:#fff}
.home .wwm-band .btn.primary:hover{background:transparent;border-color:var(--ink);color:var(--ink)}
.home .wwm-band .link-arrow{color:var(--ink);border-color:rgba(21,23,28,.4)}
.home .wwm-band .link-arrow:hover{color:var(--accent);border-color:var(--accent)}
.home footer,.uxui footer,.contact-page footer,.logo-page footer,.landing-page footer,.about-page footer,.email-page footer,.product-page footer,.research-page footer,.banner-page footer{background:transparent;color:var(--body)}
.home footer span:first-child,.uxui footer span:first-child,.contact-page footer span:first-child,.logo-page footer span:first-child,.landing-page footer span:first-child,.about-page footer span:first-child,.email-page footer span:first-child,.product-page footer span:first-child,.research-page footer span:first-child,.banner-page footer span:first-child{color:var(--ink)}

/* ---- desert hero band: UX/UI + Contact top heroes.
   Static work-hero.jpg (no walker, no glitch); the fixed nav blends over it
   like the Work page. Scoped to these two pages only â€” the Work page is untouched. ---- */
.uxui main,.contact-page main{padding-top:0}
.uxui .page-hero,.contact-page .contact-hero{position:relative;overflow:hidden;min-height:clamp(440px,58vh,640px);padding-top:calc(var(--header-h,76px) + clamp(34px,5vw,72px));background:#f3eee1 url("assets/work/work-hero.webp") center 38%/cover no-repeat}
.uxui .page-hero-inner,.contact-page .contact-hero-inner{position:relative;z-index:1}
.uxui .page-hero::after,.contact-page .contact-hero::after{content:"";position:absolute;left:0;right:0;bottom:-1px;height:clamp(220px,46vh,460px);background:linear-gradient(180deg,rgba(255,255,255,0) 0%,rgba(255,255,255,.2) 32%,rgba(255,255,255,.52) 58%,rgba(255,255,255,.86) 82%,var(--surface) 100%);pointer-events:none;z-index:0}

@media(max-width:920px){
  .work-hero{min-height:clamp(520px,70vh,700px)}
  .work-hero .wh-copy{padding-top:calc(var(--header-h,112px) + 30px)}
}
/* accordion doesn't suit touch/hover; below 760px stack as tappable tiles */
@media(max-width:760px){
  .expand-cards{flex-direction:column;height:auto;gap:12px}
  .ec-card{flex:1 1 auto;height:clamp(180px,50vw,240px)}
  .ec-card.is-active,.ec-card:hover,.ec-card:focus-visible{flex-grow:1}
  .expand-cards:hover .ec-card.is-active:not(:hover){flex-grow:1}
  .ec-label{opacity:1;transform:none}
  .expand-cards:hover .ec-card.is-active:not(:hover) .ec-label{opacity:1;transform:none}
  /* vertical variant: no reliable hover on touch, so every card just sits at
     its full 760:235 aspect ratio instead of accordion-collapsing to 64px. */
  .expand-cards.is-vertical .ec-card{flex:0 0 auto;height:auto;aspect-ratio:760/235;transition:none}
  .expand-cards.is-vertical .ec-card .work-image{object-fit:cover}
  .expand-cards.is-vertical:hover .ec-card.is-active:not(:hover){flex-basis:auto}
  /* the About page copy of the accordion sets a fixed column height on desktop;
     release it here so the stacked tiles size themselves like everywhere else */
  .flow-introduction .expand-cards{height:auto}
}
.cr-band-logo{display:block;width:150px;height:auto;margin:0 0 8px}

/* --- Work With Me: dark conversion band (coral accent, mirrors the CR band tone) - */
.wwm-band{background:var(--dark);padding-top:var(--space-section);padding-bottom:var(--space-section);border-bottom:1px solid rgba(255,255,255,.1)}
.wwm-band .section-intro{padding-top:0}
.wwm-band .section-intro:last-child{padding-bottom:0}
.wwm-band h2{color:#fff}
.wwm-band .section-intro p{color:rgba(255,255,255,.72)}
.wwm-band .services{border-top-color:rgba(255,255,255,.16)}
.wwm-band .service{border-bottom-color:rgba(255,255,255,.12)}
.wwm-band .service:not(:nth-child(3n+1)){border-left-color:rgba(255,255,255,.12)}
.wwm-band .service h3{color:#fff}
.wwm-band .service p{color:rgba(255,255,255,.66)}
.wwm-band .btn.primary{background:var(--accent);border-color:var(--accent);color:#211009}
.wwm-band .btn.primary:hover{background:#fff;border-color:#fff;color:var(--ink)}
.wwm-band .link-arrow{color:#fff;border-color:rgba(255,255,255,.5)}
.wwm-band .link-arrow:hover{color:var(--accent);border-color:var(--accent)}
/* Brand lockups: each live-site shot "stamped" with its brand mark, two up */
.brand-lockups{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:clamp(18px,2.4vw,32px);align-items:center}
.brand-lockups .cl-stage{position:relative;padding:14px 6px 14px 0}
.brand-lockups .cl-shot{position:relative;display:block;margin-left:clamp(30px,4vw,52px);max-width:321px;aspect-ratio:321/400;height:auto;overflow:hidden;border:1px solid var(--hair);border-radius:0;box-shadow:0 18px 40px rgba(20,16,10,.20)}
.brand-lockups .cl-shot .work-image{width:100%;height:100%;max-height:none;object-fit:cover;object-position:top;transition:object-position 7s linear}
.brand-lockups .cl-shot:hover .work-image,.brand-lockups .cl-shot:focus-visible .work-image{object-position:bottom;opacity:1}
.brand-lockups .cl-mark{position:absolute;left:0;top:50%;transform:translateY(-50%);display:grid;place-items:center;background:#fff;box-shadow:0 16px 38px rgba(20,16,10,.26);transition:transform .2s}
.brand-lockups .cl-mark img{display:block}
.cl-mark-round{width:clamp(121px,13vw,159px);aspect-ratio:1/1;border:1px solid #ece3cf;border-radius:50%}
.cl-mark-round img{width:80%;height:auto}
.cl-mark-rect{width:clamp(133px,15vw,167px);aspect-ratio:1.85/1;border:1px solid var(--hair);border-radius:12px}
.cl-mark-rect img{width:auto;height:auto;max-width:92%;max-height:84%}
.cl-mark--lg{width:clamp(158px,18vw,214px)}
.brand-lockups .cl-mark--dark{background:#0b0c10;border-color:#fff;border-width:2px}
.brand-lockups .cl-mark:hover{transform:translateY(calc(-50% - 4px)) scale(1.03)}
@media(min-width:921px){.case-card:has(.brand-lockups){grid-template-columns:minmax(0,1.52fr) minmax(0,.48fr)}.case-card:has(.brand-lockups) .case-media{order:0}.case-card:has(.brand-lockups) .case-copy{order:1}}
.case-media-duo{display:grid;grid-template-columns:minmax(0,.9fr) minmax(0,1.1fr);gap:0;align-items:end}
.case-media-duo a:first-child .work-image{transform:translateX(32%)}
.case-media-duo a:last-child .work-image{transform:translateX(-23%)}
@media(min-width:921px){.case-card:has(.case-media-duo){grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr)}}
.case-media-duo a:first-child .card-img{max-height:410px}
.case-media-duo a:last-child .card-img{max-height:480px}
.uxui .case-grid .case-card:first-child .card-img{max-height:436px}
.uxui .case-grid .case-card:nth-child(2) .card-img{max-height:520px}
/* the case study images stay completely static on hover and focus: still
   linked, but no fade, and the Open cue is gone from the markup. The link
   keeps the global focus-visible outline for keyboard users. */
.uxui .case-grid .lightbox-link .work-image{transition:none}
.uxui .case-grid .lightbox-link:hover .work-image,
.uxui .case-grid .lightbox-link:focus-visible .work-image{opacity:1}
.slash{font-size:1.15em}
@media(max-width:560px){.case-media-duo{grid-template-columns:1fr}.case-media-duo .work-image{transform:none}}
.card-img-ux{max-height:410px}
.brand-lockups.lockup-solo{grid-template-columns:1fr}
.brand-lockups.logo-grid{align-items:start;row-gap:clamp(30px,4vw,52px)}
.brand-lockups figure{margin:0}
.brand-lockups figcaption{padding:16px 0 0 clamp(30px,4vw,52px);font-size:14px;font-weight:600;color:var(--ink)}
.brand-lockups figcaption span{display:block;color:var(--muted);font-weight:500;font-size:13px;margin-top:2px}
.brand-lockups .cl-placeholder{position:relative;display:grid;place-items:center;margin-left:clamp(30px,4vw,52px);height:clamp(250px,26vw,360px);border:1px dashed var(--hair);border-radius:12px;background:#faf9f6}
.brand-lockups .cl-placeholder span{padding-left:34%;color:var(--muted);font-size:11px;font-weight:700;letter-spacing:.16em;text-transform:uppercase}
@media(max-width:560px){.brand-lockups{grid-template-columns:1fr;gap:30px}}
.case-media-row{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;align-items:start}
.case-media-row-two{grid-template-columns:repeat(2,minmax(0,256px))}
/* 256x360 at the width the two columns are designed for. Expressed as a ratio
   rather than a fixed height so the frame keeps that shape when the columns
   shrink; a pinned 360px turned them into 170x360 slivers on a phone. */
.case-media-row .lightbox-link{display:block;aspect-ratio:256/360;height:auto;overflow:hidden;border:1px solid var(--hair)}
.case-media-row .work-image{width:100%;height:100%;max-height:none;object-fit:cover;object-position:top;transition:object-position 7s linear}
.case-media-row .lightbox-link:hover .work-image,.case-media-row .lightbox-link:focus-visible .work-image{object-position:bottom;opacity:1}
.home .section-intro.divided{border-top:1px solid var(--hair)}


/* --- Cosmic Reader page: full-page starfield dark theme -------------------- */
html:has(body.cr-page){background:var(--dark)}
.cr-page{background:var(--dark);color:rgba(255,255,255,.85)}
.cr-page .cr-stars{position:fixed;inset:0;width:100%;height:100%;z-index:0;pointer-events:none}
.cr-page main,.cr-page footer{position:relative;z-index:1}
/* Footer: the Work page's footer dunes regraded to the same night as the hero,
   so the desert reads as continuing past the content rather than as a second
   image. It cannot go on the body like the Work treatment, because the body
   background paints under the fixed star canvas and the stars would show over
   the sand. Instead it is its own layer after the footer in the DOM at z-index
   0: later than the canvas so it covers the stars, but below main and footer
   at z-index 1 so it can never sit over any content. Its sky dissolves into
   --dark at the top, continuing the hero's night sky. */
.cr-page{position:relative}
/* A second layer fades the dune crests down into the exact page background
   colour (--dark), the same idea as the Work page's footer fade. This uses a
   gradient overlay rather than opacity, so contrast and sand detail hold; a
   uniform opacity drop would wash the whole image toward grey instead. It
   starts around the brightest crest band (measured ~lum 78 at image y 50%)
   and reaches near solid --dark by the bottom edge, sinking the dunes into
   the space background rather than just dimming a strip at the very foot. */
/* Measured against the Work page footer: at the same viewport width, both
   pages' dune image sit roughly the same distance above the actual footer, but
   the Work page's last section ends well clear of that reveal, while here the
   full aspect-ratio height (≈652px) reached ~300px above the pager divider and
   overlapped the previous paragraph. Height is now a fixed crop instead of the
   image's own aspect ratio, so the reveal starts right at the pager line, the
   same "footer sits below the gray divider" composition as the Work page.
   The image keeps its full width-driven scale (background-size 100% auto) and
   is bottom-anchored, so only its lower slice shows through this shorter box:
   cropped, not squeezed. */
/* Full bleed width, anchored to the TOP of the source image so its natural
   sky/horizon shows and fades in from the page background exactly like the
   hero does; whatever exceeds the box lower down (the foreground sand,
   closest to camera) is what gets cropped, hidden by the existing dark fade
   at the bottom rather than by a hard edge. Both ends of the gradient dip
   back to --dark so the box has no visible top or bottom seam. The box is
   80px taller than the "starts after the pager" crop, with the extra height
   added at the top only (bottom stays pinned to the footer's own bottom
   edge) so the image and its top fade shift up together by 80px. */
.cr-dunes{position:absolute;left:0;right:0;bottom:0;height:calc(clamp(200px,22vw,340px) + 80px);z-index:0;pointer-events:none;
  background-image:linear-gradient(180deg,rgba(20,22,29,.85) 0%,rgba(20,22,29,.15) 12%,
    rgba(20,22,29,0) 22%,rgba(20,22,29,0) 50%,rgba(20,22,29,.40) 66%,
    rgba(20,22,29,.72) 80%,rgba(20,22,29,.92) 90%,rgba(20,22,29,1) 100%),
    url("assets/work/cr-footer-night.webp");
  background-position:center top,center top;
  background-size:100% 100%,100% auto;
  background-repeat:no-repeat,no-repeat}
.cr-page footer{background:transparent}
/* Hero: the same dunes as the Work and UX/UI heroes, regraded to night. The
   asset's sky is transparent and ramps to opaque at the horizon, so the fixed
   star canvas behind main shows through the night sky and fades out naturally
   into the horizon haze, while the moonlit dunes stay solid. The bottom of the
   asset dissolves back into --dark so the hero meets the page with no seam. */
/* main keeps its header-height padding on ordinary pages, but here that padding
   showed as a band of --dark above the artwork, so the fixed nav sat on a stripe
   instead of blending into the sky. The hero now starts at the very top, as it
   already does on Work and UX/UI, and takes the header clearance as its own
   padding so the copy stays exactly where it was. */
.cr-page main{padding-top:0}
.cr-page .page-hero{position:relative;background:url("assets/work/cr-hero-night.webp") center bottom/cover no-repeat;
  padding-top:calc(var(--space-section) + var(--header-h,76px));
  /* min-height applies to the border box, so the extra top padding would eat
     into the space the copy is centred in and lift it. Growing the minimum by
     the same amount keeps the content box exactly the size it was, which
     matters on phones where this hero is minimum-height driven rather than
     content driven. */
  min-height:calc(clamp(460px,64vh,620px) + var(--header-h,76px));display:flex;align-items:center;
  /* the enlarged plume runs ~30px past the right edge between 900 and 1370px
     wide, which would otherwise add a horizontal scrollbar to the page. Only
     that already fully masked tail is trimmed; nothing else in the hero reaches
     an edge. overflow-x:clip rather than hidden so no scroll container is
     created and the vertical axis stays untouched; neither one creates a
     stacking context, so the plume keeps blending with the hero background. */
  overflow-x:hidden;overflow-x:clip}
.cr-page .page-hero .page-hero-inner{position:relative;z-index:2;width:100%}
/* Lift the title block 62px. transform rather than margin so the hero's own
   height and the flex centering of everything else stay exactly as they were,
   and the rocket/plume keep their existing position against the dunes. */
.cr-page .page-title{transform:translateY(-62px)}
/* Starship hovering over the open valley on the right. .cr-launch is a zero
   size anchor pinned to the landing zone in the dunes, so the rocket and its
   plume scale and move together as one. The rocket is a still image and never
   animates; only the exhaust video moves. */
/* no z-index here on purpose: a stacking context would isolate the plume's
   screen blend from the hero background and leave a black box. DOM order plus
   the z-index on .page-hero-inner already keeps the copy above the rocket. */
/* --rh is the single scale knob: the rocket, plume, scorch, flame, their offsets
   and the drop shadows are all expressed as multiples of it, so raising it scales
   the whole assembly rigidly and keeps every effect attached to the vehicle.
   Sized up 37.5% (104/15.5/196 * 1.375) to balance the large page title.
   The top offset carries a correction so the launch point does not drift: the
   plume's ground impact sits 0.228 * --rh below this anchor, so growing --rh by
   k would push that line down by 0.228 * rh * (1 - 1/k), which at k = 1.375 is
   0.0622 * rh. Subtracting exactly that keeps the contact line on the same dune
   floor it was aligned to, and the rocket grows upward out of it. */
/* Measured from the bottom, not the top. The hero now grows upward by the header
   height, and a percentage taken from the top would have dragged the whole launch
   assembly down with it. The bottom is also where the landing zone actually is.
   42.5% = 100% - 57.5%, and the --rh correction flips sign with the direction.
   The hero is also taller by the header height now, and 42.5% of a taller hero
   is a longer distance from the bottom, so that share of the growth is taken
   back off: 0.425 * --header-h. Net effect is that the rocket does not move. */
.cr-launch{--rh:clamp(143px,21.31vw,269.5px);position:absolute;left:71.5%;bottom:calc(42.5% - var(--header-h,76px) * 0.425 + var(--rh) * 0.0622);width:0;height:0;pointer-events:none}
/* exhaust: generated on black, screen blended so the black drops out and only
   the luminous vapour remains over the dunes. Its ground impact sits ~76% down
   the frame, so the clip is offset to put that line exactly on the valley floor. */
.cr-plume{position:absolute;left:50%;bottom:calc(var(--rh) * -0.42);
  width:calc(var(--rh) * 2.9);height:calc(var(--rh) * 0.80);
  object-fit:cover;object-position:50% 100%;
  transform:translateX(-50%);mix-blend-mode:screen;opacity:1;
  /* read more clearly from a distance: ~35% more light overall (opacity
     plus brightness) with a touch of contrast for definition. The clip is
     unchanged, so the plume gains presence without growing or thickening. */
  filter:brightness(1.16) contrast(1.07);
  /* the radial alone was still ~98% opaque at the element's bottom edge, which
     cut the rolling vapour off with a straight line. A second linear layer
     feathers the last stretch to nothing; the two are intersected so the rest
     of the shape is unchanged. */
  -webkit-mask-image:radial-gradient(50% 62% at 50% 78%,#000 34%,rgba(0,0,0,.45) 72%,transparent 100%),
    linear-gradient(180deg,#000 56%,rgba(0,0,0,.55) 78%,transparent 100%);
  mask-image:radial-gradient(50% 62% at 50% 78%,#000 34%,rgba(0,0,0,.45) 72%,transparent 100%),
    linear-gradient(180deg,#000 56%,rgba(0,0,0,.55) 78%,transparent 100%);
  -webkit-mask-composite:source-in;mask-composite:intersect}
/* soft engine light spilling onto the surrounding sand */
.cr-scorch{position:absolute;left:50%;bottom:calc(var(--rh) * -0.10);width:calc(var(--rh) * 2.3);height:calc(var(--rh) * 0.5);
  transform:translateX(-50%);mix-blend-mode:screen;
  background:radial-gradient(50% 50% at 50% 50%,rgba(158,201,255,.30),rgba(130,174,238,.11) 45%,rgba(0,0,0,0) 78%)}
/* the vehicle itself: static, tilted for composition, pivoting on its base so
   the engines stay planted over the plume */
/* Raptor flame: a white hot core just under the engine bells easing into a soft
   blue glow that dies away into the vapour below. Screen blended, so it adds
   light to the existing smoke and sand instead of covering them, and it shares
   the rocket's base pivot and 12 degree tilt so it fires along the engine axis.
   It sits before the rocket in the DOM, so the bells occlude its top. */
.cr-flame{position:absolute;left:50%;bottom:calc(var(--rh) * -0.30);
  width:calc(var(--rh) * 0.30);height:calc(var(--rh) * 0.38);
  transform-origin:50% 0;mix-blend-mode:screen;pointer-events:none;
  /* Raptor intensity: every radius below is unchanged, so the plume keeps its
     exact shape and size. The extra power comes from holding the white core at
     full strength across a wider plateau before it falls away, and from lifting
     the blue stops, with a brightness pass on top. */
  background:
    radial-gradient(24% 13% at 50% 10%,rgba(255,255,255,1) 0,rgba(255,255,255,.99) 34%,rgba(232,247,255,.74) 64%,rgba(186,225,255,0) 100%),
    radial-gradient(19% 38% at 50% 24%,rgba(228,244,255,.97),rgba(164,210,255,.62) 52%,rgba(112,176,255,0) 100%),
    radial-gradient(30% 72% at 50% 40%,rgba(154,205,255,.74),rgba(98,164,255,.32) 58%,rgba(80,150,255,0) 100%),
    radial-gradient(92% 116% at 50% 46%,rgba(108,170,255,.38),rgba(80,140,240,0) 72%);
  filter:brightness(1.14) saturate(1.05);
  /* the gradients still carried a little alpha at the element's bottom, so the
     tail is feathered out and dissipates into the vapour instead of stopping */
  -webkit-mask-image:linear-gradient(180deg,#000 52%,rgba(0,0,0,.42) 76%,transparent 100%);
  mask-image:linear-gradient(180deg,#000 52%,rgba(0,0,0,.42) 76%,transparent 100%);
  animation:cr-flicker 1.15s ease-in-out infinite alternate}
@keyframes cr-flicker{
  from{opacity:.93;transform:translateX(-50%) rotate(12deg) scaleY(.96)}
  to  {opacity:1;  transform:translateX(-50%) rotate(12deg) scaleY(1.06)}
}
/* explicit width (270/900 of the source) plus max-width:none: the global
   img{max-width:100%} resolves to 100% of the zero width anchor, which would
   otherwise collapse the rocket to nothing */
.cr-rocket{position:absolute;left:50%;bottom:calc(var(--rh) * 0.06);height:var(--rh);width:calc(var(--rh) * 0.3000);max-width:none;
  transform:translateX(-50%) rotate(12deg);transform-origin:50% 100%;
  filter:drop-shadow(0 0 calc(var(--rh)*.05) rgba(150,190,255,.26))
         drop-shadow(0 calc(var(--rh)*.02) calc(var(--rh)*.05) rgba(4,10,24,.55))}
@media(max-width:900px){.cr-launch{display:none}}
@media(prefers-reduced-motion:reduce){.cr-plume{display:none}
  .cr-flame{animation:none;transform:translateX(-50%) rotate(12deg)}}
.cr-page .site-header.is-scrolled{background:rgba(20,22,29,.85);border-bottom-color:rgba(255,255,255,.1)}
.cr-page .brand{color:#fff}
.cr-page .brand span:last-child{color:#00f0ff}
.cr-page .summary-panel{top:max(88px,calc(50vh - 303px))}
.cr-page .nav a{color:rgba(255,255,255,.65)}
.cr-page .nav a:hover{color:#00f0ff}
.cr-page .nav a[aria-current=page]{color:#fff}
.cr-page .nav a[aria-current=page]::after{background:#00f0ff}
.cr-page h1,.cr-page h2,.cr-page h3{color:#fff}
.cr-page .eyebrow{color:#00f0ff}
.cr-page .page-title p,.cr-page .summary-panel p,.cr-page .client-section p{color:rgba(255,255,255,.72)}
.cr-page .summary-panel h2{color:#fff}
.cr-page .summary-panel dl{border-top-color:rgba(255,255,255,.15)}
.cr-page .summary-panel dt{color:#00f0ff}
.cr-page .summary-panel dd{color:rgba(255,255,255,.72)}
.cr-page .summary-panel a{color:#00f0ff}
.cr-page .client-section{border-top-color:rgba(255,255,255,.12)}
.cr-page .work-item figcaption{color:rgba(255,255,255,.55)}
.cr-page .work-item figcaption strong{color:#fff}
.cr-page .link-arrow{color:#fff;border-color:rgba(255,255,255,.5)}
.cr-page .link-arrow:hover{color:#00f0ff;border-color:#00f0ff}
.cr-page .pager{border-top-color:rgba(255,255,255,.12)}
.cr-page .pager .btn{color:rgba(255,255,255,.6)}
.cr-page .pager .btn:hover,.cr-page .pager .btn.primary:hover{color:#00f0ff}
.cr-page .pager .btn.primary{color:#fff}
.cr-page .zoom-cue{color:#fff;text-shadow:0 1px 6px rgba(0,0,0,.9)}
.cr-page .work-image{border:1px solid rgba(0,240,255,.18)}
/* even rhythm: equal space above and below each divider, single-spaced CTA rows */
.home .case-grid{padding-bottom:0}
.home .section-intro.divided{padding-top:var(--space-block)}
.home .section-intro .actions{margin-top:0}
.home .section-intro p:last-child{margin-bottom:0}
@media(max-width:560px){
  .hero-col-ss .lightbox-link{position:static}
  .hero-col-ss .work-image{height:auto;max-height:420px}
}

/* --- Cosmic Reader constellation band (homepage feature) ------------------- */
.cr-band{position:relative;background:var(--dark);overflow:hidden;padding:calc(var(--space-section) + 16px) clamp(16px,3vw,42px) var(--space-section)}.cr-stars{position:absolute;inset:0;width:100%;height:100%;display:block}
/* Home page: the star canvas fills the whole band (whose top overlaps up into
   the hero). Clip the stars to the dark cosmic region only â€” matches the band's
   hard white->dark edge (188px), so they never bleed into the hero above. */
.cr-band-inner{position:relative;z-index:2;max-width:var(--max);margin:auto;display:grid;grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr);gap:clamp(32px,6vw,80px);align-items:center}
.cr-band-copy{opacity:var(--cr-vis,1);transform:translateY(calc(var(--cr-p,0) * -24px));will-change:opacity,transform}
.cr-constellation{opacity:var(--cr-vis,1);transform:translateY(calc(var(--cr-p,0) * -54px));will-change:opacity,transform}
.cr-band-copy .eyebrow{color:#00f0ff}
.cr-band-copy h2{font-size:clamp(34px,4.6vw,60px);line-height:1.03;color:#fff;margin:14px 0}
.cr-band-copy p{color:rgba(255,255,255,.72);font-size:17px;max-width:48ch;margin:0}
.cr-band-copy .actions{margin-top:30px}
.btn.ghost-light{background:transparent;border-color:rgba(255,255,255,.5);color:#fff}
.btn.ghost-light:hover{background:#fff;border-color:#fff;color:var(--ink)}
.link-arrow.light{color:#fff;border-color:rgba(255,255,255,.5)}
.link-arrow.light:hover{color:#00f0ff;border-color:#00f0ff}
.cr-constellation{position:relative;min-height:460px}
.cr-lines{position:absolute;inset:0;width:100%;height:100%;pointer-events:none}
.cr-lines line{stroke:#00f0ff;stroke-width:1;stroke-opacity:.3}
.cr-node{position:absolute;display:block;width:46%;text-decoration:none;transition:transform .2s,opacity .2s}
.cr-node:hover{transform:translateY(-4px)}
.cr-node img{display:block;width:100%;border:1px solid rgba(0,240,255,.25)}
.cr-node span{display:block;margin-top:9px;color:rgba(255,255,255,.85);font-size:11px;font-weight:700;letter-spacing:.14em;text-transform:uppercase}
.cr-node:hover span{color:#00f0ff}
.cr-node-1{top:0;left:0;width:54%}
.cr-node-2{top:16%;right:0;width:38%}
.cr-node-3{bottom:0;left:18%;width:44%}
@media(max-width:920px){
  .fw-item,.fw-item:nth-of-type(even){grid-template-columns:1fr;gap:24px}
  .fw-item:nth-of-type(even) .fw-media{order:0}
  .fw-media{height:clamp(220px,40vw,330px)}
  .pr-list{grid-template-columns:1fr;gap:28px}
  .cr-band{padding-top:calc(var(--space-section) + 30px)}
  .services{grid-template-columns:1fr}
  .service,.service:not(:nth-child(3n+1)){border-left:0;padding-left:0}
  .cr-band-inner{grid-template-columns:minmax(0,1fr)}
  .cr-constellation{min-height:0;display:grid;grid-template-columns:minmax(0,1fr);gap:22px}
  .cr-node,.cr-node-1,.cr-node-2,.cr-node-3{position:static;width:100%}
  .cr-lines{display:none}
}

/* ============================================================
   Identity & Logo page â€” interactive brand selector
   Two-column: copy (â‰ˆ40%) / stage + selector (â‰ˆ60%).
   Visual language mirrors .expand-cards (hairline cards, white
   surfaces, flex-grow expansion, same easing) and reuses the
   .cl-stage lockup presentation inside crossfading scenes.
   ============================================================ */
.logo-selector{max-width:var(--max);margin:0 auto;padding:var(--space-block) clamp(16px,3vw,42px) var(--space-section);display:grid;grid-template-columns:minmax(0,.66fr) minmax(0,1fr);gap:clamp(30px,4.5vw,64px)}
.ls-copy{display:flex;flex-direction:column;padding-top:14px}
.ls-copy h1{font-size:clamp(38px,4.2vw,58px);line-height:1.02;margin:12px 0 10px}
.ls-intro{font-size:clamp(15px,1.25vw,18px);color:var(--body);max-width:52ch;margin:0 0 clamp(16px,2vw,26px)}
.ls-brand-info{border-top:1px solid var(--hair);padding-top:clamp(16px,2vw,24px);margin-bottom:18px}
.ls-panel{display:none}
.ls-panel.is-active{display:block;animation:ls-fade .5s cubic-bezier(.22,.61,.36,1)}
@keyframes ls-fade{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
.ls-panel h2{font-size:clamp(22px,2vw,28px)}
.ls-tag{display:inline-block;margin-top:6px;color:var(--muted);font-size:12px;font-weight:700;letter-spacing:.14em;text-transform:uppercase}
.ls-panel p{color:var(--body);font-size:15.5px;line-height:1.65;max-width:46ch;margin:10px 0 14px}
.ls-colors{display:flex;gap:8px;margin:0 0 18px}
.ls-colors span{width:26px;height:26px;border-radius:50%;background:var(--c);border:1px solid var(--hair);box-shadow:inset 0 0 0 2px #fff}
.ls-panel .btn{min-height:42px;padding:10px 20px;font-size:14px}
.ls-soon{background:transparent;color:var(--muted);border-color:var(--hair);cursor:default}
.ls-scope{margin:16px 0 0;margin-top:auto;border-top:1px solid var(--hair);padding-top:14px;color:var(--muted);font-size:13px;line-height:1.6;max-width:52ch}

.ls-show{display:flex;flex-direction:column}
.ls-stage{position:relative;flex:1 1 auto;min-height:clamp(330px,34vw,500px)}
.ls-scene{position:absolute;inset:0;opacity:0;pointer-events:none;transition:opacity .45s ease}
.ls-scene.is-active{opacity:1;pointer-events:auto}
.ls-scene .cl-stage{position:relative;height:100%;padding:14px 6px 14px 0}
.ls-scene .cl-shot{position:relative;display:block;margin-left:clamp(30px,4vw,52px);height:100%;overflow:hidden;border:1px solid var(--hair);box-shadow:0 18px 40px rgba(20,16,10,.20)}
.ls-scene .cl-shot .work-image{width:100%;height:100%;max-height:none;object-fit:cover;object-position:top;transition:object-position 7s linear}
.ls-scene .cl-shot:hover .work-image,.ls-scene .cl-shot:focus-visible .work-image{object-position:bottom;opacity:1}
.ls-scene .cl-mark{position:absolute;left:0;top:50%;transform:translateY(-50%);display:grid;place-items:center;background:#fff;box-shadow:0 16px 38px rgba(20,16,10,.26);transition:transform .2s;width:clamp(158px,18vw,214px)}
.ls-scene .cl-mark img{display:block}
.ls-scene .cl-mark--dark{background:#0b0c10;border-color:#fff;border-width:2px}
.ls-scene .cl-mark:hover{transform:translateY(calc(-50% - 4px)) scale(1.03)}
.ls-scene .cl-placeholder{position:relative;display:grid;place-items:center;margin-left:clamp(30px,4vw,52px);height:100%;border:1px dashed var(--hair);border-radius:12px;background:#faf9f6}
.ls-scene .cl-placeholder span{padding-left:34%;color:var(--muted);font-size:11px;font-weight:700;letter-spacing:.16em;text-transform:uppercase}

.ls-selector{display:flex;gap:10px;margin-top:16px;height:clamp(84px,8.5vw,118px)}
.ls-item{position:relative;flex:1 1 0%;min-width:0;overflow:hidden;display:grid;place-items:center;border:1px solid var(--hair);background:#fff;padding:0;cursor:pointer;box-shadow:0 8px 22px rgba(20,16,10,.06);transition:flex-grow .55s cubic-bezier(.22,.61,.36,1),box-shadow .4s ease,border-color .3s ease}
.ls-item img{max-width:62%;max-height:64%;width:auto;height:auto;object-fit:contain;-webkit-user-drag:none;user-select:none}
.ls-item--dark{background:#0b0c10}
.ls-item.is-active{flex-grow:2.1;border-color:var(--ink)}
.ls-item:hover,.ls-item:focus-visible{flex-grow:2.1;box-shadow:0 18px 40px rgba(20,16,10,.14)}
.ls-item:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}
.ls-item .ec-label{padding:6px 11px;font-size:11px}
.ls-item.is-active .ec-label,.ls-item:hover .ec-label,.ls-item:focus-visible .ec-label{opacity:1;transform:none}
.ls-item--dark .ec-label{background:rgba(11,12,16,.88);color:#fff}

@media(max-width:920px){
  .logo-selector{grid-template-columns:1fr}
  .ls-copy{order:1}
  .ls-show{order:0}
  .ls-stage{flex:none;height:clamp(280px,52vw,420px);min-height:0}
  .ls-copy{padding-top:0}
  .ls-scope{margin-top:24px}
  .ls-selector{height:clamp(64px,14vw,84px)}
  .ls-item .ec-label{opacity:1;transform:none}
}
@media(prefers-reduced-motion:reduce){
  .ls-panel.is-active{animation:none}
  .ls-scene{transition:none}
  .ls-item{transition:none}
  .ls-scene .cl-shot .work-image{transition:none}
}

/* Landing Pages variant: full-bleed screenshot previews and cover-fit
   thumbnail tiles (no logo marks). Everything else reuses .ls-*. */
.lp-select .ls-scene .cl-shot{margin-left:0}
.lp-select .ls-item img{max-width:none;max-height:none;width:100%;height:100%;object-fit:cover;object-position:top}
.lp-select .ls-item.is-active,.lp-select .ls-item:hover,.lp-select .ls-item:focus-visible{flex-grow:2.6}
@media(max-width:920px){
  .lp-select .ls-item:not(.is-active) .ec-label{opacity:0}
}

/* ---- widow control: short description paragraphs balance their last line so
   it never ends on one or two orphaned words. Line counts stay the same;
   browsers only apply balance to short blocks, so long copy is untouched. ---- */
.case-copy p,.fw-copy p,.service p,.pr-list li p,.ls-panel p,.ls-intro,
.cap-card p,.contact-aside p,.page-title p,.fw-intro p,.pr-intro p,
.section-intro p,.contact-hero p,.ls-scope{text-wrap:balance}
/* Long body copy: balance is ignored past ~6 lines, so use pretty to stop
   the last line ending on a single orphaned word. */
.summary-panel p,.client-section p{text-wrap:pretty}

/* ============================================================
   UX/UI hero: a premium phone rising out of the dunes, looping
   through four interface screens. Pure CSS, no images, no JS.
   Everything scales from --w so the device stays proportional.
   Scoped to the UX/UI page only.
   ============================================================ */
.ux-phone{--w:clamp(86px,min(10vw,19.2vh),140px);position:absolute;z-index:2;right:calc(23.5% + 30px);bottom:9%;width:var(--w);aspect-ratio:449/1400;pointer-events:none}
/* ground shadows sit on .ux-phone, not on the floating shell, so they stay
   planted on the dune while the device drifts: a broad elliptical cast shadow
   plus a tighter contact shadow where the phone meets the sand */
.uxp-cast{position:absolute;z-index:-2;left:-52%;right:-64%;bottom:-4%;height:16%;background:radial-gradient(50% 50% at 50% 50%,rgba(68,45,20,.44),rgba(68,45,20,.20) 44%,rgba(68,45,20,0) 76%);filter:blur(calc(var(--w)*.045))}
.uxp-contact{position:absolute;z-index:-1;left:2%;right:6%;bottom:.2%;height:3.2%;background:radial-gradient(50% 50% at 50% 50%,rgba(40,25,8,.66),rgba(40,25,8,0) 70%);filter:blur(calc(var(--w)*.012))}
.uxp-glow{position:absolute;left:50%;top:46%;width:190%;height:58%;transform:translate(-50%,-50%);z-index:-3;background:radial-gradient(closest-side,rgba(255,244,228,.34),rgba(255,240,220,.10) 58%,rgba(255,255,255,0) 80%);filter:blur(calc(var(--w)*.05))}
/* photoreal device (seedream render, background keyed to alpha) + the live UI
   composited into its screen, both animated together so they stay locked */
.uxp-float{position:relative;width:100%;will-change:transform;
  filter:drop-shadow(calc(var(--w)*.028) calc(var(--w)*.055) calc(var(--w)*.075) rgba(58,38,16,.30));
  animation:uxp-float 11s ease-in-out infinite}
/* background-image (not <img>) so hidden mobile never downloads it. the
   drop-shadow is offset down and right so the device separates from the dune */
.uxp-photo{display:block;position:relative;z-index:2;width:100%;aspect-ratio:449/1400;background:url("assets/work/ux-phone-bronze-shell.png") center/contain no-repeat}
/* Screen viewport, masked by the supplied ux-phone-bronze-screen-mask.png:
   white = glass, everything else transparent. It shares the exact box of the
   shell layer (both inset to the same float, untransformed), so the two PNGs
   stay in pixel registration and UI can never appear outside the opening. */
.uxp-screen{position:absolute;inset:0;z-index:1;overflow:hidden;background:#f4f6f9;
  -webkit-mask:url("assets/work/ux-phone-bronze-screen-mask.png") center/100% 100% no-repeat;
  mask:url("assets/work/ux-phone-bronze-screen-mask.png") center/100% 100% no-repeat}
/* the UI plane sits on the mask's white bounding box; script.js applies a
   matrix3d homography onto the glass quad measured from the mask itself
   (TL 1.47,8.90  TR 87.75,-0.16  BR 87.75,99.05  BL 0.58,95.74 % of the
   canvas), oversized 16% each side so the UI always bleeds behind the mask. */
.uxp-plane{position:absolute;left:.668%;top:1.214%;width:87.305%;height:97.286%;background:#0d1018}
@keyframes uxp-float{
  0%,100%{transform:translateY(0) rotate(-.2deg)}
  50%    {transform:translateY(-4px) rotate(.2deg)}
}
/* Generated dark interface (nano-banana via kie.ai), scrolled as one seamless
   loop. Two identical stacked slabs of the composed panel; translating the feed
   by exactly one slab (-50%) lands on identical pixels, so it never jumps or
   blanks. The panel stacks a dashboard view over an analytics/reports view, so
   the loop reads as one product cycling dashboard -> analytics -> back. */
.uxp-feed{position:absolute;left:0;top:0;width:100%;will-change:transform;
  animation:uxp-scroll 24s linear infinite}
.uxp-slab{display:block;width:100%;aspect-ratio:832/2496;
  background:url("assets/work/ux-screen-feed.webp") center/100% 100% no-repeat}
/* three slabs; translate one slab (-33.3333%) so the plane stays fully
   covered even at the loop extreme, and the wrap is pixel-identical */
@keyframes uxp-scroll{from{transform:translateY(0)} to{transform:translateY(-33.3333%)}}

/* Static glass overlays: direct children of .uxp-screen, above the animated
   plane and below the bronze shell. Each shares the plane's exact box and gets
   the same homography from script.js, so its gradients align with the true
   perspective of the glass; the transparent border insets the paint to the
   glass region (the plane is oversized 16%: y inset = 6.9% of plane height
   = .2093w, x inset = 6.9% of plane width = .0602w) and the screen's mask is
   the final clip. They never animate, so they stay fixed while the UI moves.
   Order: plane, bottom fade, sun reflection, edge rim; shell above all. */
.uxp-bottom-fade,.uxp-reflection,.uxp-edge-reflection{position:absolute;left:.668%;top:1.214%;width:87.305%;height:97.286%;pointer-events:none;
  border:solid transparent;border-width:calc(var(--w)*.2093) calc(var(--w)*.0602);background-clip:padding-box;background-origin:padding-box}
/* restrained depth over the lower fifth only (unchanged strength) */
.uxp-bottom-fade{z-index:2;background-image:linear-gradient(0deg,rgba(24,24,38,.15) 0,rgba(24,24,38,.06) 9%,rgba(24,24,38,0) 18%)}
/* readable sunlight across the upper left third, brightest ~.48, no hard edge */
.uxp-reflection{z-index:3;background-image:linear-gradient(133deg,rgba(255,249,233,.48) 0,rgba(255,243,218,.28) 11%,rgba(255,236,204,.10) 24%,rgba(255,255,255,0) 40%)}
/* Curved-glass edge light, lit for the scene: the sun sits low and to the left,
   and the device stands in bright sand that bounces warm light up into the
   bottom of the glass. Against a dark screen the glass reads mostly as specular
   rim, so all four edges and all four corners now carry light. The earlier dark
   slate "depth" gradients were removed: they were tuned for the old light UI and
   became invisible against the dark dashboard. Sizes scale with --w. */
.uxp-edge-reflection{z-index:4;background-image:
  /* corner glints: brightest upper left (direct sun), lower left catches sun
     plus sand bounce, lower right is bounce only, upper right is sky */
  radial-gradient(calc(var(--w)*.23) calc(var(--w)*.26) at 0% 0%,rgba(255,248,232,.58),rgba(255,243,214,.24) 45%,rgba(255,243,214,0) 80%),
  radial-gradient(calc(var(--w)*.20) calc(var(--w)*.23) at 0% 100%,rgba(255,240,214,.32),rgba(255,232,196,.13) 45%,rgba(255,232,196,0) 80%),
  radial-gradient(calc(var(--w)*.18) calc(var(--w)*.21) at 100% 0%,rgba(255,244,220,.42),rgba(255,240,210,.17) 45%,rgba(255,240,210,0) 80%),
  radial-gradient(calc(var(--w)*.18) calc(var(--w)*.21) at 100% 100%,rgba(255,236,206,.22),rgba(255,230,194,.09) 45%,rgba(255,230,194,0) 80%),
  /* rims on all four edges, a touch brighter and thicker so the glass edge
     reads clearly: warmest on the sun side, dimmer and cooler on the shaded
     right, held back along the bottom so it does not wash out the UI */
  linear-gradient(90deg,rgba(255,246,226,.62) 0,rgba(255,246,226,.34) 2px,rgba(255,246,226,.12) 3.5px,rgba(255,246,226,0) 5.5px),
  linear-gradient(180deg,rgba(255,247,228,.54) 0,rgba(255,247,228,.28) 2px,rgba(255,247,228,.10) 3.5px,rgba(255,247,228,0) 5px),
  linear-gradient(0deg,rgba(255,238,210,.44) 0,rgba(255,238,210,.22) 2px,rgba(255,238,210,.08) 3.5px,rgba(255,238,210,0) 5px),
  linear-gradient(270deg,rgba(228,234,246,.40) 0,rgba(228,234,246,.20) 2px,rgba(228,234,246,.07) 3.5px,rgba(228,234,246,0) 5px),
  /* faint warm bounce off the sunlit sand, kept low so the lower UI stays clean */
  linear-gradient(0deg,rgba(255,231,196,.08) 0,rgba(255,231,196,.035) 6%,rgba(255,231,196,0) 14%)}

/* keep the headline copy clear of the device */
.uxui .page-hero.hero-wide .page-title p{max-width:min(58ch,58%)}
@media(max-width:1100px){.uxui .page-hero.hero-wide .page-title p{max-width:min(58ch,64%)}}
@media(max-width:900px){.ux-phone{display:none}.uxui .page-hero.hero-wide .page-title p{max-width:78ch}}
/* reduced motion: a fully opaque, perfectly still device showing the dashboard */
@media(prefers-reduced-motion:reduce){
  .ux-phone{opacity:1}
  .uxp-float{animation:none;transform:none}
  .uxp-feed{animation:none;transform:none}
}

/* ===========================================================================
   Mobile menu
   Below 920px the seven nav links no longer fit on one line. They used to sit
   in a horizontally scrolling strip, which read as cut off, so the header now
   keeps the wordmark on the left and a labelled menu button on the right, and
   the same <nav> becomes a full height panel under the header. Nothing is
   duplicated: it is the one nav element, restyled, so the current page marker
   and link order stay in sync with desktop automatically.
   This block sits at the end of the sheet on purpose, after the .cr-page
   overrides, so the dark page's panel colours win on source order.
   =========================================================================== */
.nav-toggle{display:none}

@media(max-width:920px){
  .nav-toggle{display:inline-flex;align-items:center;gap:10px;min-height:44px;padding:8px 2px 8px 14px;
    border:0;background:none;color:var(--ink);font-family:var(--font);font-size:12px;font-weight:700;
    letter-spacing:.14em;text-transform:uppercase;cursor:pointer}
  .nav-toggle__text{line-height:1}
  /* three rules that fold into an X. The middle bar is the element itself, the
     outer two are its pseudo elements, so the whole control is one span. */
  .nav-toggle__bars{position:relative;display:block;width:24px;height:2px;background:currentColor;transition:background .1s ease .12s}
  .nav-toggle__bars::before,.nav-toggle__bars::after{content:"";position:absolute;left:0;width:24px;height:2px;
    background:currentColor;transition:transform .28s cubic-bezier(.22,.61,.36,1)}
  .nav-toggle__bars::before{top:-7px}
  .nav-toggle__bars::after{top:7px}
  .nav-toggle[aria-expanded=true] .nav-toggle__bars{background:transparent;transition-delay:0s}
  .nav-toggle[aria-expanded=true] .nav-toggle__bars::before{transform:translateY(7px) rotate(45deg)}
  .nav-toggle[aria-expanded=true] .nav-toggle__bars::after{transform:translateY(-7px) rotate(-45deg)}

  /* the panel. visibility (not display) so the links leave the tab order when
     closed while the open/close still animates. */
  .nav{position:fixed;top:var(--header-h,76px);left:0;right:0;bottom:0;z-index:45;
    display:flex;flex-direction:column;justify-content:flex-start;flex-wrap:nowrap;gap:0;
    padding:6px clamp(16px,4vw,42px) max(40px,env(safe-area-inset-bottom));
    background:var(--surface);overflow-y:auto;overscroll-behavior:contain;
    opacity:0;visibility:hidden;transform:translateY(-10px);
    transition:opacity .26s ease,transform .26s cubic-bezier(.22,.61,.36,1),visibility 0s linear .26s}
  .nav.is-open{opacity:1;visibility:visible;transform:none;transition-delay:0s,0s,0s}
  .nav a{min-height:60px;padding:16px 2px;font-size:19px;font-weight:600;color:var(--ink);
    border-bottom:1px solid var(--hair)}
  .nav a:last-child{border-bottom:0}
  .nav a[aria-current=page]{color:var(--accent)}
  /* the desktop underline is absolutely positioned to a short inline link box;
     on a full width row it would run the whole way and fight the hairline */
  .nav a[aria-current=page]::after{display:none}

  /* While the panel is open the header must read as solid, whatever the scroll
     position, or the wordmark floats on a transparent strip above the panel.
     backdrop-filter must be off here, and explicitly off to beat .is-scrolled:
     a filtered element becomes the containing block for its fixed descendants,
     which would resolve the panel's top/bottom against the 77px header instead
     of the viewport and collapse it to a 46px sliver. The background is fully
     opaque instead, which is what the blur was standing in for anyway. */
  .site-header.nav-is-open{background:var(--surface);border-bottom-color:var(--hair);backdrop-filter:none;-webkit-backdrop-filter:none}
  html.nav-open,html.nav-open body{overflow:hidden}

  /* Cosmic Reader is a night page: panel and control follow it */
  .cr-page .nav-toggle{color:#fff}
  .cr-page .nav{background:#0f1118}
  .cr-page .nav a{border-bottom-color:rgba(255,255,255,.12)}
  .cr-page .nav a[aria-current=page]{color:#00f0ff}
  /* same containing block trap on the night page: .cr-page .site-header.is-scrolled
     outranks the rule above, so the filter has to be cleared at this weight too */
  .cr-page .site-header.nav-is-open{background:#0f1118;border-bottom-color:rgba(255,255,255,.1);backdrop-filter:none;-webkit-backdrop-filter:none}
}

/* ===========================================================================
   Touch targets
   The flat design uses text links as primary calls to action, so they measure
   18 to 29px tall. Padding them would change the vertical rhythm the desktop
   layout depends on, so each gets an invisible centred strip instead: the
   tappable area reaches 44px while the type stays exactly where it is. Because
   the effect is purely in the hit test and never paints, it can safely run all
   the way up to the 920px breakpoint, covering tablets as well as phones.
   =========================================================================== */
@media(max-width:920px){
  .text-link,.link-arrow,.pager .btn,main dd a,.case-copy h2 a{position:relative}
  .text-link::after,.link-arrow::after,.pager .btn::after,main dd a::after,.case-copy h2 a::after{
    content:"";position:absolute;left:0;right:0;top:50%;height:44px;transform:translateY(-50%)}
}

/* Phones: the form's underline fields sit at 41px, just under a comfortable
   touch target. Extra bottom padding lifts them to 48 without moving the label
   or changing the underline treatment the layout is built on. */
@media(max-width:560px){
  .field input,.field select{padding:10px 0 17px}
  .field{margin-bottom:24px}
}

/* ---------------------------------------------------------------------------
   Product design: the Overview panel ran 994px, past the sticky viewport
   budget on every common laptop (616px at 1280x720), so its bottom was clipped
   while the right column scrolled. The copy is condensed and the panel's type
   and list spacing tightened until the whole thing fits the shortest common
   window, so it simply sits still and complete while the gallery moves.
   Scoped to this page and to desktop; below 921px the panel stacks normally
   and keeps the standard sizes.
   --------------------------------------------------------------------------- */
@media(min-width:921px){
  /* Pull the hero 110px closer to the nav and close the gap under it by 60.
     --space-section is a clamp, so both subtractions ride it rather than
     pinning a fixed value. The small floors keep the padding positive once the
     clamp has shrunk past the subtraction on narrower desktops. The container
     can sit this close because .page-hero-inner centres its columns against
     the tall image, so the copy still starts well below the header. */
  .product-page .page-hero,.research-page .page-hero{
    padding-top:max(8px,calc(var(--space-section) - 110px));
    padding-bottom:max(20px,calc(var(--space-section) - 60px))}
  /* 88px clears the 77px header with room to spare and buys back 16px of the
     budget, which at 720p is the difference between fitting and not */
  .product-page .summary-panel,.research-page .summary-panel{top:88px}
  .product-page .summary-panel p,.research-page .summary-panel p{font-size:15px;line-height:1.52;margin:10px 0 0}
  .product-page .summary-panel h2,.research-page .summary-panel h2{font-size:20px;line-height:1.18;margin-top:10px}
  .product-page .summary-panel dl,.research-page .summary-panel dl{margin-top:14px;padding-top:12px;gap:9px}
  .product-page .summary-panel dt,.research-page .summary-panel dt{font-size:10.5px}
  .product-page .summary-panel dd,.research-page .summary-panel dd{font-size:14px;line-height:1.45;margin-top:2px}
}
/* A spec list value holding several short links: lay them out as one row
   instead of stacking them. Flex rather than plain inline so the gap is even
   and independent of the arrow glyphs, and it wraps rather than overflowing
   if the column is ever too narrow for all three. */
.summary-panel .link-row{display:flex;flex-wrap:wrap;gap:4px 16px}

/* ===========================================================================
   Work page: reveal on scroll
   Blocks rise and fade as they enter the viewport. Opacity and transform only,
   so each reveal is a compositor job; 600ms with the strong ease-out curve,
   which is the entrance curve used elsewhere in this sheet.

   The hidden state hangs off .reveal-on, a class added by the observer in
   script.js and by nothing else. If that script never runs, or the browser has
   no IntersectionObserver, or the visitor prefers reduced motion, the class is
   absent and every block simply renders visible. The stylesheet can never
   strand content off screen on its own.

   Deliberately NOT included: .work-hero and everything inside .cr-band. The
   hero's glitch keyframes already own transform on their elements, and the
   Cosmic Reader band drives its own scroll response through --cr-p / --cr-vis.
   Adding a second transform to either would fight the existing motion.
   =========================================================================== */
/* The hidden state carries NO transition. .rv and .reveal-on are both added by
   script.js after first paint, so if the transition lived here the blocks would
   animate from visible down to hidden, flashing on load before revealing. The
   transition is declared on .is-in instead: it is the state being entered, so
   only the way in animates and the initial hide is instant. */
/* Three start positions, chosen per element in script.js from where it sits:
   a block left of centre comes in from the left, one right of centre from the
   right, so the two halves of every row meet in the middle. Blocks that span
   the middle have no side to come from and rise instead. */
.reveal-on .rv{opacity:0}
.reveal-on .rv-up{transform:translateY(16px)}
.reveal-on .rv-l{transform:translateX(calc(var(--dx) * -1))}
.reveal-on .rv-r{transform:translateX(var(--dx))}
.home .rv{--dx:20px}
.home .rv.is-in{opacity:1;transform:none;
  transition:opacity .6s cubic-bezier(.23,1,.32,1),transform .6s cubic-bezier(.23,1,.32,1)}
/* Narrower screens stack almost everything to full width, so most blocks rise
   rather than slide; the few that still travel do so over a shorter distance. */
@media(max-width:920px){.home .rv{--dx:14px}}
/* A hidden block still contributes to scrollWidth, so a block parked 44px off
   to the side would add a horizontal scrollbar at widths where the content
   column runs close to the viewport edge. clip rather than hidden: it trims the
   overflow without turning main into a scroll container. */
.home main{overflow-x:clip}
@media(prefers-reduced-motion:reduce){
  .reveal-on .rv{opacity:1;transform:none}
  .home .rv.is-in{transition:none}
}
