/* ============================================================
   Andrew Parton - shared theme
   Palette 204 (cool-blue) re-skin of the Doubleslash tokens,
   plus the site's utility classes and cross-document
   View Transitions. Linked from every page after the DS tokens.
   ============================================================ */

/* ---- Theme tokens ----------------------------------------
   Deep Navy is the site's only palette. There was a light mode and a
   header button that cycled between them; both are gone, and these are
   the navy values folded into :root.

   Showcase panels that were always dark regardless of mode (hero,
   .apw-lamp band, portrait, demo cards, footer) still carry their own
   literal dark colours, so they are unaffected. */
:root {
  /* Surfaces */
  --canvas: #0b1018;                    /* page background */
  --surface: #131c2e;                   /* raised cards */
  --surface-tint: #0f1727;              /* banded-chapter tint */
  --header-bg: rgba(11,16,24,.82);      /* sticky header (over inline) */
  --mnav-bg: rgba(11,16,24,.98);        /* mobile slide-down panel */

  /* Text */
  --ink: #e8edf7;                       /* headings / strong foreground */
  --body-ink: #c5cede;                  /* body copy */
  --muted: rgba(205,216,236,.62);       /* roles, dates */
  /* .48 measured 3.4-3.8:1 on the surfaces it is actually used on (accordion
     dates, photo captions) - below the 4.5:1 AA floor for text this small.
     .68 clears it with margin and still reads as the quiet tone. */
  --muted-soft: rgba(205,216,236,.68);  /* captions */

  /* Accent - brightened for legibility over the dark surfaces. */
  --accent: #9db8ee;                    /* links, labels, focus, hairlines */
  --accent-solid: #4568bc;              /* filled controls (light text on top) */
  --accent-ink: #cdddf8;                /* text on the pale accent wash */
  --accent-light: #9db8ee;
  --on-light-accent: #101827;           /* dark text on a light accent pill */
  --accent-weak: rgba(120,150,220,.14); /* tag / accordion wash */
  --accent-weak-2: rgba(120,150,220,.22);/* hover / open wash */

  /* Borders */
  --border: rgba(160,180,220,.16);
  --border-strong: rgba(160,180,220,.32);

  /* Legacy aliases (still referenced in places) */
  --electric: var(--accent);
  --magenta: var(--accent-solid);
  --orchid: var(--accent-light);
  --burgundy: #2a3550;
  --footer: #0e1420;
  --white: #ffffff;
  --soft: #1a2740;                      /* career.html pull-quote panels */
  --grad-mark: linear-gradient(135deg, #303f5c, #4568bc);
  --grad-mark-tri: linear-gradient(135deg, #303f5c, #4568bc 60%, #799ae1);
}

body { margin: 0; background: var(--canvas); color: var(--body-ink); font-family: "Libre Franklin", system-ui, sans-serif; }
html { scroll-behavior: smooth; }
/* Lenis takes over scrolling when active - native smooth-scroll fights it
   (causes sluggish, catch-up scrolling), so disable it. Lenis adds .lenis. */
html.lenis { scroll-behavior: auto; }
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
.lenis.lenis-stopped { overflow: hidden; }
::selection { background: var(--accent-solid); color: #fff; }
:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; border-radius: 3px; }

/* ---- Lava-lamp WebGL background (reusable) ----------------
   Calming, slow-drifting "//" marks rendered via WebGL (apw-lamp.js),
   meant for CTA-style dark panels. Apply .apw-lamp to the panel and
   give it a <canvas class="apw-lamp__canvas" aria-hidden="true">
   as a child; the script finds and animates it. If WebGL is
   unavailable the script adds .apw-lamp--fallback, which hides the
   canvas so the panel's own inline background shows through. */
.apw-lamp { position:relative; }
.apw-lamp__canvas { position:absolute; inset:0; width:100%; height:100%; display:block; pointer-events:none; }
.apw-lamp--fallback .apw-lamp__canvas { display:none; }

/* ---- Nav links -------------------------------------------- */
a.apw-nav { position: relative; text-decoration: none; }
a.apw-nav::after { content:""; position:absolute; left:0; right:0; bottom:-6px; height:2px; background:var(--accent); transform:scaleX(0); transform-origin:left; transition:transform .28s cubic-bezier(.2,.7,.2,1); }
a.apw-nav:hover::after, a.apw-nav[aria-current="page"]::after { transform:scaleX(1); }

/* Brand lockup + the "Work with me" button, held together on the left of the
   header row. The nav stays pushed right by .u-container-9's space-between. */
.apw-header__brand { display:flex; align-items:center; gap:20px; }

/* Footer nav. Same links, spacing and type as the header row (.u-row-13), but
   it must wrap - unlike the header, it is never replaced by a hamburger. */
.apw-footer-nav { display:flex; align-items:center; gap:26px; flex-wrap:wrap; }

/* ---- GitHub icon link ------------------------------------- */
/* Icon-only in the header and footer. The label is there for screen
   readers and for the mobile menu, which reveals it beside the mark. */
.apw-gh { display:inline-flex; align-items:center; gap:8px; color:inherit; text-decoration:none;
  transition:color .2s ease, transform .2s ease; }
.apw-gh svg { display:block; width:20px; height:20px; fill:currentColor; flex:none; }
.apw-gh:hover { color:var(--accent); transform:translateY(-1px); }
.apw-gh__label { position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden;
  clip-path:inset(50%); white-space:nowrap; border:0; }

/* ---- Buttons / links -------------------------------------- */
/* One button system. Every button on the site is .apw-cta plus, at most, one
   colour variant and one size. Do not hand-roll another - that is how the
   site ended up with three different "light pill" buttons.
     (none)          solid accent - the primary action
     --light         white pill - the primary action on a dark band
     --ghost         outline - the secondary action
     --sm            the compact size, for the header and inline rows
   Layout (align-self, margins) is the container's job, not the button's. */
.apw-cta { display:inline-flex; align-items:center; gap:9px; text-decoration:none; cursor:pointer; border:none;
  font:600 19px/1 "Titillium Web",system-ui,sans-serif; color:#fff; background:var(--accent-solid); padding:16px 30px; border-radius:9px;
  transition:transform .2s ease, box-shadow .2s ease, background .2s ease, color .2s ease, border-color .2s ease; }
.apw-cta:hover { transform:translateY(-2px); box-shadow:0 12px 26px -12px rgba(69,104,188,.75); }
.apw-cta--sm { font-size:16px; padding:11px 20px; border-radius:8px; }
.apw-cta--ghost { background:transparent; color:var(--ink); border:1.5px solid var(--border-strong); }
.apw-cta--ghost:hover { box-shadow:none; border-color:var(--accent); color:var(--accent); }
/* White pill. The standard on every dark band: header, hero, CTA band, features. */
.apw-cta--light { background:#fff; color:#0e1420; }
.apw-cta--light:hover { background:var(--orchid); box-shadow:0 14px 30px -14px rgba(0,0,0,.6); }

/* One text-link style, to go with the one button system. Stays inline: it is
   used mid-sentence in the career copy, and must be able to break across lines. */
.apw-link { font:600 17px "Titillium Web",system-ui,sans-serif; color:var(--accent);
  text-decoration:none; border-bottom:2px solid rgba(69,104,188,.32); padding-bottom:2px;
  transition:border-color .2s ease; }
.apw-link:hover { border-bottom-color:currentColor; }

/* ---- Full-bleed contact CTA band --------------------------
   Edge-to-edge dark band that closes the page. No rounding and no
   max-width: it spans the viewport and butts straight up against the
   footer (which drops its top margin when preceded by one of these),
   so the two dark blocks read as one. The inner wrapper keeps the copy
   aligned to the same 1360px column as the rest of the page. */
.apw-cta-band { position:relative; overflow:hidden; background:var(--ink); }
.apw-cta-band__inner { position:relative; max-width:1360px; margin:0 auto; padding:64px 96px;
  display:flex; align-items:center; justify-content:space-between; gap:32px; flex-wrap:wrap; }
/* Whichever dark band closes a page, it butts straight onto the footer: the
   band already supplies the dark mass, so the footer's 96px top margin would
   just show the page canvas through as a stripe. Every band that can be last
   is listed here - add to this rather than writing another copy of the rule.
   It has to reach through <main>, because the footer is a sibling of main, not
   of the band inside it. */
.apw-cta-band + .apw-footer,
main:has(.apw-cta-band:last-child) + .apw-footer,
main:has(.apw-contact-band:last-child) + .apw-footer,
main:has(.apw-404-band:last-child) + .apw-footer { margin-top:0 !important; }

/* About closes on a tinted band, and the CTA band's own 96px top margin left a
   stripe of bare page canvas between the two - the same "gap" this rule exists
   to kill at the footer. Butt them together. !important because the margin
   comes from the doubled .u-45.u-45 utility, in a stylesheet loaded after this. */
.apw-bio-band + .apw-cta-band { margin-top:0 !important; }

/* ---- Full-bleed page hero ---------------------------------
   Same idea as the CTA band, at the top of the page: the dark panel spans
   the viewport with no max-width and no rounding, sitting flush under the
   sticky header. The inner wrapper holds the copy on the usual 1360px
   column so the title still lines up with the content below it. */
.apw-hero-band { position:relative; overflow:hidden; background:var(--ink); }
/* Scrim over the lamp. The 20px lead paragraph measured 3.3-4.3:1 against the
   lamp's brightest marks - under the 4.5:1 floor. (The huge title is fine: it
   counts as large text, which only needs 3:1.)
   Paint order is the fiddly part. The canvas and __inner are both positioned
   with z-index:auto, so they paint in DOM order; a bare ::after is treated as
   the last child and would land ON TOP of the copy. Giving the scrim z-index:0
   and __inner z-index:1 forces canvas → scrim → copy. */
.apw-hero-band::after { content:""; position:absolute; inset:0; z-index:0;
  pointer-events:none; background:rgba(11,16,26,.42); }
.apw-hero-band__inner { position:relative; z-index:1; max-width:1360px; margin:0 auto;
  padding:clamp(56px,7vw,92px) 96px; }
/* The page-title base. It used to live only in the per-page u-h1-* utilities,
   so .apw-page-title on its own (contact heading, 404) rendered as a default
   h1. Folded here so it works standalone. */
.apw-page-title { font:800 66px/1.04 'Heebo',system-ui,sans-serif; color:#fff;
  letter-spacing:-.03em; margin:0 0 20px; max-width:18ch; text-wrap:balance; }
@media (max-width: 860px) {
  .apw-hero-band__inner { padding:clamp(44px,7vw,72px) 24px; }
}

/* ---- Contact form (contact.html) --------------------------
   Posts JSON to the Lambda/SES endpoint in apw-contact.js. Validation
   is client-side only, for the message - the Lambda validates again. */
/* Compact by default, and the same on both pages - the hero panel used to
   re-declare a tighter copy of these, which is how the two forms drifted apart. */
.apw-form { display:flex; flex-direction:column; gap:16px; }
.apw-field { display:flex; flex-direction:column; gap:6px; }
.apw-field label { font:600 12px 'Titillium Web',system-ui,sans-serif;
  text-transform:uppercase; letter-spacing:.1em; color:var(--muted); }
.apw-field input, .apw-field textarea {
  font:400 15px/1.5 'Libre Franklin',system-ui,sans-serif; color:var(--ink);
  background:var(--canvas); border:1.5px solid var(--border-strong); border-radius:9px;
  padding:11px 14px; width:100%; box-sizing:border-box;
  transition:border-color .2s ease, box-shadow .2s ease; }
.apw-field textarea { resize:vertical; min-height:92px; }
.apw-field input::placeholder, .apw-field textarea::placeholder { color:var(--muted-soft); }
.apw-field input:focus, .apw-field textarea:focus { outline:none; border-color:var(--accent);
  box-shadow:0 0 0 3px var(--accent-weak); }
.apw-field.is-invalid input, .apw-field.is-invalid textarea { border-color:#d4553f; }

/* Error text. Hidden until the field fails, so it does not reserve space.
   Visibility is class-driven, not the [hidden] attribute: support.js mounts
   the page through React, whose hydration pass strips `hidden` off these
   spans, leaving every empty error slot rendered and its role="alert" region
   permanently live. A class survives hydration. */
.apw-err { display:none; font:500 14px/1.4 'Libre Franklin',system-ui,sans-serif; color:#d4553f; }
.apw-err.is-shown { display:block; }

/* Honeypot: off-screen rather than display:none, so bots that skip hidden
   fields still fill it. Never shown to a real user. */
.apw-hp { position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden; }

.apw-form__send[disabled] { opacity:.6; cursor:default; transform:none; box-shadow:none; }

/* The form's last row: the "no email addresses" note sits beside the button,
   which stays right-aligned like every other button on the site. It wraps to
   two rows on a phone rather than squeezing the note into a column. */
.apw-form__foot { display:flex; align-items:center; justify-content:space-between;
  gap:24px; flex-wrap:wrap; }
.apw-form__foot .apw-cta { margin-left:auto; }

/* The form is now the only thing on the page, so centre it and hold it to a
   comfortable measure rather than letting it run the full width. */
.apw-contact-single { max-width:760px; margin:0 auto; }

/* Single glass panel holding the enquiry form. Narrower than the full band so
   the form doesn't stretch to an unreadable line length on wide screens. */
.apw-contact-pair { max-width:720px; margin:0 auto; display:grid;
  grid-template-columns:1fr; gap:24px; }

/* Booking link under the form: a divider plus a right-aligned ghost CTA, so it
   reads as the secondary route in rather than a second Send button. */
.apw-contact-book { display:flex; justify-content:flex-end; margin-top:24px;
  padding-top:20px; border-top:1px solid rgba(255,255,255,.12); }

/* Success state: replaces the form once the Lambda accepts the message.
   Hidden by default and revealed with .is-shown, for the same hydration
   reason as .apw-err above. */
.apw-sent { display:none; flex-direction:column; gap:12px; align-items:flex-end; }
.apw-sent.is-shown { display:flex; }
.apw-sent h2 { font:800 30px/1.14 'Heebo',system-ui,sans-serif; color:var(--ink);
  letter-spacing:-.02em; margin:0; align-self:flex-start; }
.apw-sent p { font:400 17px/1.62 'Libre Franklin',system-ui,sans-serif; color:var(--body-ink);
  margin:0; max-width:44ch; align-self:flex-start; }

.apw-noemail { font:400 15px/1.6 'Libre Franklin',system-ui,sans-serif; color:var(--muted);
  margin:0; max-width:44ch; flex:1 1 280px; }

/* The "Read more" button that closes the about page. It lives inside the last
   chapter's band rather than in a section of its own: a standalone section sat
   on the page canvas, which is darker than the tinted band above it, so it read
   as a slab of empty space between the story and the CTA band. Sharing the
   chapter's 1180px column also keeps the button on the content edge instead of
   drifting out to the viewport edge. */
.apw-bio-outro { display:flex; justify-content:flex-end;
  max-width:1180px; margin:44px auto 0; }

/* The form is swapped out for the confirmation on a successful send. Uses a
   class for the same reason .apw-err does - see the note there. Doubled so
   it outranks the .apw-form display rule regardless of source order. */
.apw-form.is-done.is-done { display:none; }

@media (max-width:860px){
  .apw-form { gap:18px; }
}

/* ---- Tags / cards ----------------------------------------- */
.apw-tag { font:600 13px "Titillium Web",system-ui,sans-serif; padding:5px 11px; border-radius:6px; background:var(--accent-weak); color:var(--accent-ink); letter-spacing:.02em; }
.apw-proj, .apw-xcard { transition:transform .3s ease, box-shadow .3s ease; }
.apw-proj:hover, .apw-xcard:hover { transform:translateY(-4px); box-shadow:0 22px 50px -26px rgba(48,63,92,.4); }

/* ---- Studio band (services) -------------------------------
   Logo · copy · outbound CTA, in a tinted strip with an accent rule
   down the left edge. Sits between the hero and the service list. */
.apw-studio { display:grid; grid-template-columns:44px 1fr auto; gap:28px; align-items:center;
  padding:30px 34px; background:var(--surface-tint); border-left:3px solid var(--accent-solid); }
.apw-studio__logo { display:block; width:44px; height:44px; }
.apw-studio__eyebrow { font:600 13px 'Titillium Web',system-ui,sans-serif; text-transform:uppercase;
  letter-spacing:.12em; color:var(--accent); margin:0 0 8px; }
.apw-studio__body p { font:400 16px/1.65 'Libre Franklin',system-ui,sans-serif; color:var(--body-ink);
  margin:0; max-width:66ch; }
.apw-studio__body a { color:var(--accent); text-decoration:none;
  border-bottom:1px solid rgba(69,104,188,.4); }
.apw-studio__body a:hover { border-bottom-color:currentColor; }
@media (max-width:860px){
  .apw-studio { grid-template-columns:1fr; justify-items:start; gap:18px; padding:26px 22px; }
}

/* ---- Service list (services) ------------------------------
   Title left, copy right, hairline between rows. Replaces the card
   grid: reads as a document rather than a set of boxes. */
.apw-svc-heading { font:800 44px/1.1 'Heebo',system-ui,sans-serif; color:var(--ink);
  letter-spacing:-.03em; margin:0 0 8px; }
.apw-svc-list { margin:56px 0 0; border-top:1px solid var(--border); }
.apw-svc { display:grid; grid-template-columns:minmax(220px,1fr) 2.1fr; gap:40px;
  padding:34px 0; border-bottom:1px solid var(--border); }
.apw-svc__title { font:700 26px/1.2 'Heebo',system-ui,sans-serif; color:var(--ink);
  letter-spacing:-.02em; margin:0; }
.apw-svc__body p { font:400 17px/1.65 'Libre Franklin',system-ui,sans-serif; color:var(--body-ink);
  margin:0; max-width:64ch; }
/* The service's button sits on its own line, right-aligned like every card's. */
.apw-svc__body .apw-cta { margin-top:18px; }
.apw-svc__actions { display:flex; justify-content:flex-end; }

/* ---- "How projects run" — glass cards on one lamp band ------ */
/* One lamp runs behind the whole band (.apw-lamp owns the single canvas and is
   pinned to #0e1420); the three cards are glass (.apw-glass — the same blurred,
   translucent panel as the contact form), so the lamp shows through them. The
   __inner wrapper holds the heading and grid above the canvas. */
.apw-steps-band { position:relative; overflow:hidden; margin:80px 0 0; }
.apw-steps-band__inner { position:relative; z-index:1; max-width:1360px; margin:0 auto;
  padding:64px 96px; }
.apw-steps { list-style:none; margin:34px 0 0; padding:0;
  display:grid; grid-template-columns:repeat(3,1fr); gap:24px; }
/* .apw-glass supplies the blur, tint, border and radius; the card only sets its
   own padding, smaller than the panel default. */
.apw-step.apw-glass { padding:30px 28px; border-radius:16px; }
.apw-step__title { font:700 24px/1.2 'Heebo',system-ui,sans-serif; color:var(--ink);
  letter-spacing:-.02em; margin:0 0 10px; }
.apw-step__body { font:400 17px/1.65 'Libre Franklin',system-ui,sans-serif; color:var(--body-ink);
  margin:0; }
@media (max-width:860px) {
  .apw-steps { grid-template-columns:1fr; }
  .apw-steps-band__inner { padding:48px 24px; }
}
@media (max-width:860px){
  .apw-svc-heading { font-size:32px; }
  .apw-svc { grid-template-columns:1fr; gap:12px; padding:26px 0; }
}

/* ---- Full-bleed photo hero (index) ------------------------ */
/* min-height in px, not vh. It was 88vh once, which grew with the viewport and
   left ~190-270px of dead space above and below the ~410px of content. This is
   a fixed target - half again the ~540px the content alone produces - so the
   hero reads as a deliberate 810px band rather than a window-height guess. On a
   phone the stacked columns already exceed this, so mobile is unaffected.
   align-items:flex-end sits the content on the floor of the band - the extra
   height above min-height opens up over the copy, not around it. */
.apw-hero { position:relative; overflow:hidden; background:#0e1420; min-height:608px;
  display:flex; align-items:flex-end; }
.apw-hero__media { position:absolute; inset:0; z-index:0; background:#0e1420 center/cover no-repeat;
  transform:scale(1); transform-origin:center; will-change:transform;
  animation:apw-hero-zoom 34s ease-in-out infinite alternate; }
@keyframes apw-hero-zoom { from { transform:scale(1); } to { transform:scale(1.07); } }
@media (prefers-reduced-motion:reduce) { .apw-hero__media { animation:none; transform:none; } }
/* Scrims. The headline now sits on the LEFT, so that side can no longer stay
   image-forward - over the photo alone the title measured under the 3:1 large-
   text floor. Both sides are held down; the middle stays open so the portrait
   and the screens still read. */
.apw-hero__scrim { position:absolute; inset:0; z-index:1; pointer-events:none;
  background:
    linear-gradient(90deg, rgba(14,20,32,.88) 0%, rgba(14,20,32,.72) 30%, rgba(14,20,32,.34) 50%, rgba(14,20,32,.66) 72%, rgba(14,20,32,.86) 100%),
    linear-gradient(0deg, rgba(14,20,32,.55) 0%, rgba(14,20,32,0) 30%); }
/* Headline, lead and button on the left; the form panel alone on the right.
   align-items:end sits the copy and the form on a shared bottom line, so the
   two columns and the whole block all hang from the floor of the hero. */
.apw-hero__inner { position:relative; z-index:2; width:100%; max-width:1360px; margin:0 auto;
  padding:64px 96px; display:grid; grid-template-columns:1fr 1fr; gap:56px; align-items:end; }
/* The emphasis in the headline. The site accent, so it reads as part of the
   palette rather than a sticker. */
.apw-hero__hi { color:#b7cbf4; } /* accent, one step lighter: var(--accent) measured 2.94:1 over the photo, a hair under the 3:1 large-text floor */

/* The hero's glass form panel - the form and nothing else.
   The padding has to be set on .apw-hero__panel.apw-glass: .apw-glass carries
   its own 40px/44px and is declared later in this file, so a single-class rule
   here loses to it on source order and quietly does nothing. */
/* The panel now sits in a column wrapper (.apw-hero__col) alongside the
   "Book a time" button, so the grid placement/width lives on the wrapper and
   the panel just fills it. */
.apw-hero__col { justify-self:start; width:100%; max-width:520px; }
.apw-hero__panel { width:100%; }
.apw-hero__panel.apw-glass { padding:24px 26px; }
.apw-hero__panel .apw-form__foot { justify-content:flex-end; }
/* Alt CTA directly under the form, right-aligned to line up with Send enquiry. */
.apw-hero__book { display:flex; justify-content:flex-end; margin-top:16px; }

/* 16px/1.6 - the same size as the footer's copy of this very paragraph
   (.u-body-28) and in step with the 17px header/footer nav. */
.apw-hero__lead { font:400 16px/1.6 'Libre Franklin',system-ui,sans-serif; color:rgba(255,255,255,.82);
  margin:24px 0 0; max-width:52ch; }
/* Left-aligned under the copy it belongs to. The site's right-align convention
   is for buttons in cards and sections, not the hero. */
.apw-hero__actions { display:flex; justify-content:flex-start; margin-top:28px; }

@media (max-width: 980px) {
  .apw-hero__inner { grid-template-columns:1fr; gap:32px; padding:72px 24px; }
  .apw-hero__col { justify-self:stretch; max-width:none; }
}
/* Liquid Glass panel for the hero copy: a web approximation
   (backdrop-filter + layered translucency + inner highlight/shadow)
   of Apple's Liquid Glass material, floating the copy over the photo.
   Trialled across the whole home page; kept here only - the rest of
   the page reverted to flat cool-blue cards. */
/* Reusable glass panel: the hero form and the contact form both float on one.
   The tint is heavier than the original hero-only version - these panels now
   carry form labels and placeholder text, and the lighter mix could not hold
   4.5:1 over the brightest marks of the lamp behind them. */
.apw-glass {
  background: linear-gradient(135deg, rgba(14,20,32,.86) 0%, rgba(14,20,32,.72) 100%);
  backdrop-filter: blur(22px) saturate(150%) contrast(1.05);
  -webkit-backdrop-filter: blur(22px) saturate(150%) contrast(1.05);
  border: 1px solid rgba(255,255,255,.14);
  border-radius: 22px;
  padding: 40px 44px;
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.16),
    inset 0 -1px 0 rgba(0,0,0,.25),
    0 30px 70px -34px rgba(0,0,0,.55); }
/* No blur available: fall back to the solid card surface. (This used to say
   var(--ink), which is a near-white *text* tone - it would have painted a white
   panel under white text.) */
@media (prefers-reduced-transparency: reduce) {
  .apw-glass { background:var(--surface) !important;
    backdrop-filter:none !important; -webkit-backdrop-filter:none !important; }
}

/* Contact page: one lamp band for the whole page - heading and form together. */
.apw-contact-band { position:relative; overflow:hidden; }
/* Scrim over the lamp, so the heading's lead paragraph (which sits straight on
   the band, unlike the form which has its own glass) keeps 4.5:1. Same paint
   trick as the hero band: canvas → scrim (z-index:0) → content (z-index:1). */
.apw-contact-band::after { content:""; position:absolute; inset:0; z-index:0;
  pointer-events:none; background:rgba(11,16,26,.42); }
.apw-contact-band__inner { position:relative; z-index:1; max-width:1360px; margin:0 auto;
  padding:72px 96px 88px; }
.apw-contact-head { max-width:60ch; margin:0 0 48px; }
.apw-contact-head__lead { font:400 20px/1.6 'Libre Franklin',system-ui,sans-serif;
  color:rgba(255,255,255,.82); margin:16px 0 0; }
@media (max-width: 860px) {
  .apw-contact-band__inner { padding:56px 24px 64px; }
}

/* ---- 404 --------------------------------------------------- */
/* The band fills the space between the header and the footer, so a short page
   does not leave a slab of empty canvas above a floating footer. calc() covers
   the 71px sticky header. (The footer flush-up lives with the other page-closing
   bands, near .apw-cta-band.) */
.apw-404-band { display:flex; align-items:center; min-height:calc(100vh - 71px); }
.apw-404__code { font:900 clamp(64px,9vw,120px)/1 'Heebo',system-ui,sans-serif;
  color:var(--accent); opacity:.28; letter-spacing:-.04em; margin:0 0 6px; }
.apw-404__lead { font:400 20px/1.6 'Libre Franklin',system-ui,sans-serif;
  color:var(--body-ink); margin:20px 0 0; max-width:56ch; }
.apw-404__actions { display:flex; gap:14px; flex-wrap:wrap; margin-top:34px; }
.apw-hero__mark { position:absolute; z-index:1; right:18px; top:6%; font:900 300px/1 "Heebo",system-ui,sans-serif;
  letter-spacing:-.06em; background:var(--grad-mark-tri); -webkit-background-clip:text; background-clip:text;
  color:transparent; opacity:.14; pointer-events:none; user-select:none; }
/* 68px on a wide screen - a shade above the other pages' 66px hero titles. The
   clamp scales it down as the column narrows, so it never has to be rescued by
   a media query. */
.apw-hero__title { font:800 clamp(35px,4.9vw,68px)/1.06 "Heebo",system-ui,sans-serif; color:#fff;
  letter-spacing:-.03em; margin:0; text-wrap:balance; }
/* .apw-hero__lead lives with the panel rules above. A second copy used to sit
   here at 20px - left over from the old hero, where the lead was the main copy
   rather than a note in the form panel - and it silently beat the panel's own
   rule on source order. .apw-hero__eyebrow and .apw-hero__foot went with it;
   nothing has used them since the hero was rebuilt. */

/* Logo + studio name travel together: never break across a line, never drift apart. */
.inline-brand { display:inline-flex; align-items:center; gap:.35em; white-space:nowrap;
  vertical-align:baseline; }
.inline-logo { display:block; width:1.05em; height:1.05em; flex:none; }
.inline-link { color:inherit; text-decoration:underline; text-underline-offset:2px; }
.inline-link:hover { color:var(--accent); }
/* The footer is always dark, so the mid-blue accent hover would sink into it. */
.apw-footer .inline-link:hover { color:#fff; }
.apw-hero .apw-link { color:#9db8ee; border-bottom-color:rgba(157,184,238,.4); }
.apw-hero .apw-link:hover { color:#fff; }

@media (max-width: 900px) {
  .apw-hero { min-height:0; }
  /* Portrait sits at the top, copy drops below over a near-solid floor.
     The stops are in PIXELS, not percentages, and they MUST track the top
     padding below: the copy has to begin exactly where the floor turns solid,
     so if you move one, move the other. Percentages here were tied to the
     hero's height, and the hero's height depends on how much copy is in it -
     the floor used to land at 60%, far below the text once the form was added,
     leaving the headline and lead on a barely-dimmed photograph at 2.5:1. */
  .apw-hero__media { background-position:22% top; }
  .apw-hero__scrim {
    background:
      linear-gradient(180deg,
        rgba(14,20,32,.22) 0px,
        rgba(14,20,32,.55) 42px,
        rgba(14,20,32,.95) 74px,
        #0e1420 88px); }
  .apw-hero__inner { padding:88px 24px 44px; }
  .apw-hero__copy { justify-self:stretch; max-width:none; }
  .apw-glass { padding:28px 26px; border-radius:18px; }
  .apw-hero__mark { font-size:140px; top:2%; right:8px; opacity:.12; }
}


/* ---- Biography portrait ----------------------------------- */
.apw-portrait { position:relative; overflow:hidden; border-radius:18px; aspect-ratio:1/1;
  border:1px solid var(--border); background:#0e1420;
  box-shadow:0 24px 60px -34px rgba(20,30,55,.6);
  transition:transform .5s cubic-bezier(.34,1.4,.4,1), box-shadow .5s cubic-bezier(.34,1.4,.4,1); }
.apw-portrait:hover { transform:translateY(-4px); box-shadow:0 34px 74px -34px rgba(20,30,55,.72); }
/* The headshot is already square, matching the frame's 1/1 aspect, so it needs
   no crop offset - the previous landscape portrait was pulled to 60% 30%. */
.apw-portrait__img { position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  object-position:center; transition:transform 1.1s cubic-bezier(.2,.7,.2,1); }
/* No hover-zoom: the headshot fills its square frame exactly, so scaling it up
   only crops the face. The frame's own hover-lift still gives the interaction. */
/* Cool-blue floor so the caption + marks read and it locks to the palette. */
.apw-portrait__scrim { position:absolute; inset:0; pointer-events:none;
  background:linear-gradient(0deg, rgba(11,16,26,.82) 0%, rgba(11,16,26,.28) 34%, rgba(11,16,26,0) 60%),
             linear-gradient(120deg, rgba(69,104,188,.20) 0%, rgba(11,16,26,0) 50%); }
.apw-portrait__mark { position:absolute; right:12px; top:6px; z-index:2;
  font:900 132px/1 'Heebo',system-ui,sans-serif; letter-spacing:-.06em;
  color:#fff; opacity:.12; pointer-events:none; user-select:none; }
.apw-portrait__mono { position:absolute; left:18px; bottom:16px; z-index:2;
  font:900 34px/1 'Heebo',system-ui,sans-serif; letter-spacing:-.04em;
  background:var(--grad-mark-tri); -webkit-background-clip:text; background-clip:text; color:transparent;
  opacity:.95; }
@media (prefers-reduced-motion: reduce) {
  .apw-portrait, .apw-portrait__img { transition:none !important; }
}

/* ---- Biography: intro band + chapter sections ------------- */
/* Intro: portrait beside a short lead-in. */
.apw-bio-intro { display:grid; grid-template-columns:340px 1fr; gap:60px; align-items:start;
  max-width:1180px; margin:0 auto; }

/* Alternating shaded / plain full-bleed bands. One accent-tinted wash,
   used consistently, so the page reads as banded chapters. */
.apw-bio-band { padding:74px 0; }
.apw-bio-band--tint { background:var(--surface-tint); }

/* Each chapter: story text beside its contextual photo. */
.apw-chapter { display:grid; grid-template-columns:1fr 1fr; gap:64px; align-items:center;
  max-width:1180px; margin:0 auto; }
.apw-chapter__meta { font:600 14px/1 'Titillium Web',system-ui,sans-serif; letter-spacing:.14em;
  text-transform:uppercase; color:var(--accent); margin:0 0 14px; }
.apw-chapter__title { font:800 34px/1.12 'Heebo',system-ui,sans-serif; color:var(--ink);
  letter-spacing:-.025em; margin:0 0 18px; text-wrap:balance; }
.apw-chapter__text p { font:400 18px/1.72 'Libre Franklin',system-ui,sans-serif; color:var(--body-ink);
  margin:0 0 16px; max-width:58ch; }
.apw-chapter__text p:last-child { margin-bottom:0; }

.apw-chapter__media { justify-self:center; width:100%; max-width:420px; margin:0; }
.apw-chapter__media--wide { max-width:560px; }
.apw-chapter__media img { display:block; width:100%; height:auto; border-radius:14px;
  border:1px solid var(--border); box-shadow:0 26px 56px -30px rgba(20,30,55,.55); }
/* A logo, not a photograph: it needs room to breathe rather than a photo frame,
   so drop the border, shadow and rounding, and give the caption clear space so
   it never crowds the mark. rounded corners kept off; the mark is its own shape. */
.apw-chapter__media--logo { max-width:300px; }
.apw-chapter__media--logo img { border:0; border-radius:0; box-shadow:none; }
.apw-chapter__media figcaption { font:400 13px/1.5 'Libre Franklin',system-ui,sans-serif;
  color:var(--muted-soft); margin-top:11px; }
/* After the base rule (equal specificity), so the extra clearance actually wins. */
.apw-chapter__media--logo figcaption { margin-top:22px; }
/* Real-photo chapters: a shorter landscape frame that crops the picture, so
   there is more image than shows, room for the scroll-parallax (apw-motion.js)
   to drift it behind the frame edge. Wraps only the <img>, so the caption
   stays below. Brand logos below the 'I was out' beat keep their natural size. */
.apw-chapter__frame { position:relative; overflow:hidden; border-radius:14px; aspect-ratio:3/2;
  border:1px solid var(--border); box-shadow:0 26px 56px -30px rgba(20,30,55,.55); background:#0e1420; }
.apw-chapter__frame img { position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  border:0; border-radius:0; box-shadow:none; }

/* Flip: photo on the left, text on the right. */
.apw-chapter--flip .apw-chapter__text  { order:2; }
.apw-chapter--flip .apw-chapter__media { order:1; }

/* Text-only chapters: a single readable measure, centred in the band. */
.apw-chapter--solo { display:block; max-width:760px; }
.apw-chapter--solo .apw-chapter__text p { max-width:none; }

/* Full-bleed emphatic beat between chapters (e.g. "I was out."). */
.apw-bio-beat { max-width:1180px; margin:0 auto; text-align:center; }
.apw-bio-beat p { font:800 clamp(40px,6vw,72px)/1.02 'Heebo',system-ui,sans-serif; color:var(--ink);
  letter-spacing:-.035em; margin:0; }
.apw-bio-beat span { background:var(--grad-mark); -webkit-background-clip:text; background-clip:text;
  color:transparent; }

@media (max-width:860px){
  .apw-bio-intro { grid-template-columns:1fr; gap:30px; }
  .apw-bio-band { padding:52px 0; }
  .apw-chapter, .apw-chapter--flip { grid-template-columns:1fr; gap:26px; }
  /* Always photo above its text on narrow screens. */
  .apw-chapter__media, .apw-chapter--flip .apw-chapter__media { order:1; }
  .apw-chapter__text,  .apw-chapter--flip .apw-chapter__text  { order:2; }
  .apw-chapter__media { max-width:420px; justify-self:start; }
}

/* ---- Demo cards + expand-to-play game frame --------------- */
.apw-demo { position:relative; display:block; border-radius:16px; overflow:hidden;
  border:1px solid var(--border); background:#0e1420; text-decoration:none; aspect-ratio:16/10;
  box-shadow:0 10px 30px -20px rgba(20,30,55,.5);
  /* Spring: overshoot cubic-bezier gives a bouncy lift on hover in/out. */
  transition:transform .5s cubic-bezier(.34,1.56,.4,1), box-shadow .5s cubic-bezier(.34,1.56,.4,1);
  will-change:transform; }
.apw-demo:hover, .apw-demo:focus-visible {
  transform:translateY(-12px) scale(1.035);
  box-shadow:0 40px 80px -32px rgba(20,30,55,.72), 0 12px 28px -18px rgba(20,30,55,.5);
  z-index:3; }
.apw-demo__img { position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  transition:transform .6s cubic-bezier(.2,.7,.2,1); }
.apw-demo:hover .apw-demo__img { transform:scale(1.07); }
/* Stronger scrim: near-solid where the text sits, fading up the card, so
   copy stays readable over busy game art. Two layers: a tall bottom
   gradient plus a slight overall darken. */
/* The copy grew a View button, which pushed the title up into the thin part of
   this gradient: over the brighter game art (Slot Games, Roulette, Franton
   Fruits) the title fell to 2.5:1 and the description to 2.9:1. The near-solid
   band now reaches the full height of the text block. The art above it is
   untouched - only the strip the copy actually sits on is held down.
   The text-shadow below helps it look readable but earns nothing under WCAG. */
.apw-demo__scrim { position:absolute; inset:0;
  background:
    linear-gradient(0deg, rgba(11,16,26,.98) 0%, rgba(11,16,26,.96) 46%, rgba(11,16,26,.78) 64%, rgba(11,16,26,.3) 84%, rgba(11,16,26,0) 100%),
    linear-gradient(0deg, rgba(11,16,26,.28), rgba(11,16,26,.28)); }
.apw-demo__tag { position:absolute; right:14px; top:14px; z-index:2;
  font:600 12px 'Titillium Web',system-ui,sans-serif; letter-spacing:.14em; text-transform:uppercase;
  color:#e6edfb; background:rgba(11,16,26,.66); padding:5px 10px; border-radius:6px;
  backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px); }
/* The scrim above is anchored to the CARD, so its percentage stops move
   relative to the copy whenever the card resizes - widening the page gutters
   narrowed these cards and dropped the title to 2.2:1. This gradient is
   anchored to the TEXT BLOCK instead, so the copy always sits on the same floor
   no matter what size the card is. The tall padding-top is the fade's runway. */
.apw-demo__body { position:absolute; left:0; right:0; bottom:0; z-index:2;
  padding:64px 24px 24px;
  background:linear-gradient(0deg,
    rgba(11,16,26,.97) 0%, rgba(11,16,26,.95) 55%, rgba(11,16,26,.72) 80%, rgba(11,16,26,0) 100%); }
.apw-demo__title { font:700 24px 'Heebo',system-ui,sans-serif; color:#fff; letter-spacing:-.02em; margin:0 0 6px;
  text-shadow:0 1px 12px rgba(0,0,0,.55), 0 1px 2px rgba(0,0,0,.6); }
.apw-demo__desc { font:400 15px/1.55 'Libre Franklin',system-ui,sans-serif; color:rgba(255,255,255,.92); margin:0 0 16px; max-width:44ch;
  text-shadow:0 1px 8px rgba(0,0,0,.6), 0 1px 2px rgba(0,0,0,.55); }
/* .apw-play carries no styling of its own - it was a third hand-rolled white
   pill. It is now purely the hook apw-gameframe.js delegates from (it reads
   data-title / data-aspect off it), so the card's View button is a plain
   .apw-cta that also happens to open the expand-to-play frame. */

/* The demo card's button row. Right-aligned, like every other card button. */
.apw-demo__actions { display:flex; justify-content:flex-end; }

/* ---- Featured demo (a shipped product, not a prototype) ----
   Wide split card that leads the grid: dark copy panel + live app
   screenshot. Stays dark in every theme, like the other showcase
   panels. On narrow screens it stacks, image first. */
.apw-feature { position:relative; display:grid; grid-template-columns:0.86fr 1.14fr; align-items:stretch;
  border-radius:20px; overflow:hidden; border:1px solid rgba(255,255,255,.08); background:#0e1420;
  box-shadow:0 30px 72px -42px rgba(20,30,55,.65); margin-bottom:22px;
  transition:transform .5s cubic-bezier(.34,1.56,.4,1), box-shadow .5s cubic-bezier(.34,1.56,.4,1);
  will-change:transform; }
.apw-feature:hover { transform:translateY(-6px); box-shadow:0 48px 96px -44px rgba(20,30,55,.72); }
.apw-feature__copy { position:relative; z-index:2; padding:clamp(32px,3.6vw,54px);
  display:flex; flex-direction:column; justify-content:center;
  background:linear-gradient(180deg,#111c2e,#0b1320); }
.apw-feature__badge { display:inline-flex; align-items:center; gap:9px; align-self:flex-start; margin:0 0 22px;
  font:700 12px 'Titillium Web',system-ui,sans-serif; letter-spacing:.16em; text-transform:uppercase; color:#8fe3b0;
  background:rgba(60,200,120,.12); border:1px solid rgba(96,220,150,.34); padding:6px 13px; border-radius:999px; }
.apw-feature__badge::before { content:""; width:8px; height:8px; border-radius:50%; background:#39d17e;
  box-shadow:0 0 0 0 rgba(57,209,126,.6); animation:apw-live-pulse 2.4s ease-out infinite; }
@keyframes apw-live-pulse {
  0% { box-shadow:0 0 0 0 rgba(57,209,126,.55); }
  70% { box-shadow:0 0 0 9px rgba(57,209,126,0); }
  100% { box-shadow:0 0 0 0 rgba(57,209,126,0); } }
.apw-feature__title { font:800 40px/1.04 'Heebo',system-ui,sans-serif; color:#fff; letter-spacing:-.03em; margin:0 0 14px; }
.apw-feature__desc { font:400 17px/1.62 'Libre Franklin',system-ui,sans-serif; color:rgba(255,255,255,.82);
  margin:0 0 16px; max-width:46ch; }
.apw-feature__meta { font:600 13px 'Titillium Web',system-ui,sans-serif; letter-spacing:.06em; text-transform:uppercase;
  color:rgba(255,255,255,.48); margin:0 0 28px; }
/* .apw-feature__cta was a hand-rolled copy of the white pill. The button is now
   .apw-cta--light; __copy is a flex column, so it must not stretch. Right-aligned,
   like every other card and section button. */
.apw-feature__copy .apw-cta { align-self:flex-end; }
.apw-feature__media { position:relative; overflow:hidden; min-height:340px; background:#0b0f18; }
.apw-feature__media img { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; object-position:center top;
  transition:transform .6s cubic-bezier(.2,.7,.2,1); }
.apw-feature:hover .apw-feature__media img { transform:scale(1.04); }
/* Soften the seam where the dark copy panel meets the screenshot. */
.apw-feature__media::before { content:""; position:absolute; inset:0; z-index:1; pointer-events:none;
  background:linear-gradient(90deg, rgba(14,20,32,.6) 0%, rgba(14,20,32,0) 15%); }
.apw-feature__cover { position:absolute; inset:0; z-index:3; }
.apw-feature__cover:focus-visible { outline:2px solid var(--orchid); outline-offset:-4px; }

/* Reversed split (media left, copy right) - alternates the rhythm when
   two feature cards stack. */
.apw-feature--rev { grid-template-columns:1.14fr 0.86fr; }
.apw-feature--rev .apw-feature__media { order:1; }
.apw-feature--rev .apw-feature__copy { order:2; }
.apw-feature--rev .apw-feature__media::before {
  background:linear-gradient(270deg, rgba(14,20,32,.6) 0%, rgba(14,20,32,0) 15%); }

/* Amber "in development" badge (vs the green LIVE one) - no pulse. */
.apw-feature__badge--soon { color:#f0c07a; background:rgba(230,170,70,.12); border-color:rgba(240,190,110,.34); }
.apw-feature__badge--soon::before { background:#e6a844; animation:none; box-shadow:none; }

/* Built kanban-board illustration, used in place of a screenshot for a
   product that has no public screen worth showing yet. */
.apw-feature__media--art { background:radial-gradient(120% 120% at 50% 0%, #17253d 0%, #0b1320 68%); }
.apw-kanban { position:absolute; inset:0; z-index:1; display:grid; grid-template-columns:repeat(3,1fr);
  gap:clamp(8px,1.3vw,15px); padding:clamp(18px,2.4vw,30px); align-content:center; }
.apw-kanban__col { background:rgba(255,255,255,.035); border:1px solid rgba(255,255,255,.07); border-radius:12px;
  padding:11px; display:flex; flex-direction:column; gap:9px; }
.apw-kanban__coltitle { display:flex; justify-content:space-between; align-items:center;
  font:700 10px 'Titillium Web',system-ui,sans-serif; letter-spacing:.14em; text-transform:uppercase; color:rgba(255,255,255,.42); }
.apw-kanban__card { background:#18253c; border:1px solid rgba(255,255,255,.08); border-radius:9px; padding:11px 12px;
  display:flex; flex-direction:column; gap:7px; box-shadow:0 8px 18px -12px rgba(0,0,0,.7); }
.apw-kanban__line { height:6px; border-radius:4px; background:rgba(255,255,255,.24); display:block; }
.apw-kanban__line--sm { height:5px; background:rgba(255,255,255,.14); }
.apw-kanban__card--ghost { opacity:.6; }
.apw-kanban__card--ghost .apw-kanban__line { background:rgba(255,255,255,.16); }
.apw-kanban__card--active { border-color:rgba(120,154,225,.5); box-shadow:0 12px 24px -12px rgba(69,104,188,.7);
  animation:apw-kanban-lift 4.5s ease-in-out infinite; }
.apw-kanban__card--active .apw-kanban__line { background:rgba(157,184,238,.62); }
@keyframes apw-kanban-lift { 0%,100% { transform:translateY(0); } 50% { transform:translateY(-5px); } }
.apw-kanban__timer { display:inline-flex; align-items:center; gap:6px; align-self:flex-start; margin-top:1px;
  font:700 11px 'Titillium Web',system-ui,sans-serif; letter-spacing:.04em; color:#9db8ee;
  background:rgba(69,104,188,.16); border:1px solid rgba(120,154,225,.34); padding:3px 8px; border-radius:6px; }
.apw-kanban__dot { width:6px; height:6px; border-radius:50%; background:#39d17e;
  box-shadow:0 0 0 0 rgba(57,209,126,.6); animation:apw-live-pulse 2s ease-out infinite; }
.apw-kanban__card--done { opacity:.82; }
.apw-kanban__card--done .apw-kanban__line { background:rgba(120,200,150,.34); }

@media (max-width: 860px) {
  .apw-feature, .apw-feature--rev { grid-template-columns:1fr; }
  .apw-feature__media, .apw-feature--rev .apw-feature__media { order:-1; min-height:0; aspect-ratio:16/10; }
  .apw-feature__copy, .apw-feature--rev .apw-feature__copy { order:0; }
  .apw-feature__media::before { background:linear-gradient(0deg, rgba(14,20,32,.6) 0%, rgba(14,20,32,0) 18%); }
  .apw-feature__title { font-size:32px; } }

@media (prefers-reduced-motion: reduce) {
  .apw-feature__badge::before, .apw-kanban__card--active, .apw-kanban__dot { animation:none; } }

/* Overlay + expanding frame */
.apw-gf-overlay { position:fixed; inset:0; z-index:2000; background:rgba(8,12,20,0);
  transition:background .45s ease; }
.apw-gf-overlay.is-in { background:rgba(8,12,20,.78); }
.apw-gf-frame { position:fixed; z-index:2001; overflow:hidden; background:#0e1420;
  box-shadow:0 40px 120px -30px rgba(0,0,0,.7); will-change:top,left,width,height; }
.apw-gf-iframe { position:absolute; inset:0; width:100%; height:100%; border:0; display:block;
  background:#0e1420; }
.apw-gf-frame.is-loading .apw-gf-iframe { opacity:0; }
.apw-gf-iframe { transition:opacity .3s ease .1s; }
.apw-gf-spinner { position:absolute; left:50%; top:50%; width:44px; height:44px; margin:-22px 0 0 -22px;
  border:3px solid rgba(255,255,255,.18); border-top-color:#799ae1; border-radius:50%;
  animation:apw-gf-spin .8s linear infinite; z-index:1; }
.apw-gf-frame:not(.is-loading) .apw-gf-spinner { display:none; }
@keyframes apw-gf-spin { to { transform:rotate(360deg); } }
.apw-gf-close { position:absolute; left:50%; bottom:20px; transform:translateX(-50%); z-index:3;
  font:600 15px/1 'Titillium Web',system-ui,sans-serif; color:#fff; cursor:pointer;
  background:rgba(14,20,32,.72); border:1px solid rgba(255,255,255,.22); padding:11px 26px;
  border-radius:999px; backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
  opacity:0; transition:opacity .3s ease, transform .2s ease, background .2s ease; }
.apw-gf-frame.is-open .apw-gf-close { opacity:1; }
.apw-gf-close:hover { background:rgba(69,104,188,.9); transform:translateX(-50%) translateY(-2px); }

@media (prefers-reduced-motion: reduce) {
  .apw-gf-overlay, .apw-gf-frame, .apw-gf-close, .apw-demo__img { transition:none !important; }
  .apw-gf-spinner { animation:none; }
}

/* ---- Reveal on scroll ------------------------------------- */
.apw-reveal { opacity:0; transform:translateY(26px); }
.apw-reveal.is-in { opacity:1; transform:none; transition:opacity .8s cubic-bezier(.2,.7,.2,1), transform .8s cubic-bezier(.2,.7,.2,1); }

/* ---- GSAP motion layer (apw-motion.js) -------------------- */
/* When the motion script is active it hides these until it takes
   control, preventing a flash of visible-then-hidden content.
   Header is never hidden. Under prefers-reduced-motion the class is
   never added, so nothing here applies. */
/* First section = hero: its text children are staggered in, so hide
   those specifically. On the photo hero the copy is nested in
   .apw-hero__copy (media/scrim/mark layers must stay visible). */
html.apw-motion main > section:first-of-type:not(.apw-hero) > :is(h1, h2, p, div),
html.apw-motion .apw-hero__copy > :is(h1, h2, p, div) { opacity:0; }
/* Every later section reveals as a whole block. */
html.apw-motion main > section:not(:first-of-type),
html.apw-motion .apw-proj,
html.apw-motion .apw-xcard,
html.apw-motion .apw-demo,
html.apw-motion article.apw-job,
html.apw-motion main figure { opacity:0; }
html.apw-motion .apw-header,
html.apw-motion .apw-header * { opacity:1; }

/* ---- Career accordion (concertina, built by apw-accordion.js) ----
   The article keeps its .apw-job class, so the reveal rule above would
   hide the header; force accordion items (and open-panel figures) visible
   with !important, and let the open/close animation be the motion. */
/* The career articles' own layout. Single-class on purpose: the accordion
   JS adds .apw-acc to these same articles, and its rules below must be
   able to win. This used to be an inline style, which is why it never
   fought the accordion. */
/* Job copy measure. Single-class on purpose: the accordion JS re-classes
   these divs as .apw-acc__body, whose rule sets max-width:none - and it
   can only win that if this rule is not doubled. Was an inline style. */
.apw-job__body { max-width:66ch; }

.apw-job { border-top:1px solid var(--border); padding:38px 0; display:grid; grid-template-columns:260px 1fr; gap:48px; align-items:start; scroll-margin-top:88px; }

/* Expand / collapse all. Injected by apw-accordion.js, so it only exists once
   there is an accordion to control. Right-aligned, like every other button.
   The !important on opacity matches .apw-acc below: GSAP's scroll-reveal would
   otherwise leave the bar invisible until it scrolled into view, and the top
   one sits above the fold. */
.apw-acc-controls { display:flex; justify-content:flex-end; opacity:1 !important;
  transform:none !important; }
.apw-acc-controls--top { margin:0 0 20px; }
.apw-acc-controls--bottom { margin:26px 0 0; }

/* Each item is a self-contained pill, so it is spaced by margin rather than
   separated by a rule - a border would otherwise float in the gap. */
.apw-acc { opacity:1 !important; transform:none !important;
  margin:0 0 18px; scroll-margin-top:78px;
  /* Keep browser scroll-anchoring from nudging the view as a panel expands -
     the accordion's own close→scroll→open choreography owns the scroll. */
  overflow-anchor:none; }
.apw-acc:last-of-type { margin-bottom:0; }

.apw-acc__head {
  width:100%; display:flex; align-items:center; gap:28px;
  padding:30px 28px; background:var(--accent-weak); border:0; cursor:pointer;
  text-align:left; color:var(--ink); font-family:inherit;
  border-radius:12px; transition:background .25s ease;
  scroll-margin-top:74px; /* clear the sticky nav when scrolled to */
  -webkit-tap-highlight-color:transparent;
}
.apw-acc__head:hover { background:var(--accent-weak-2); }
.apw-acc.is-open .apw-acc__head { background:var(--accent-weak-2); }
.apw-acc__head:focus-visible { outline:2px solid var(--accent); outline-offset:2px; }

.apw-acc__lead { display:flex; flex-direction:column; gap:5px; flex:1 1 auto; min-width:0; }
.apw-acc__co { font:700 24px 'Heebo',system-ui,sans-serif; letter-spacing:-.02em;
  color:var(--ink); transition:color .25s ease; }
.apw-acc__role { font:600 13px 'Titillium Web',system-ui,sans-serif;
  text-transform:uppercase; letter-spacing:.1em; color:var(--accent); }

.apw-acc__aside { display:flex; flex-direction:column; align-items:flex-end; gap:9px; flex:0 0 auto; }
.apw-acc__dates { font:500 14px 'Libre Franklin',system-ui,sans-serif;
  color:var(--muted-soft); white-space:nowrap; }
.apw-acc__tags { display:flex; flex-wrap:wrap; gap:7px; justify-content:flex-end; margin:0; }

.apw-acc__chev { flex:0 0 auto; display:flex; color:rgba(28,34,48,.38);
  transition:transform .42s cubic-bezier(.34,1.3,.5,1), color .25s ease; }

/* Open state: company + chevron pick up the accent, chevron flips up. */
.apw-acc.is-open .apw-acc__co { color:var(--accent); }
.apw-acc.is-open .apw-acc__chev { transform:rotate(180deg); color:var(--accent); }

/* Panel: animate to the content's natural height (grid-template-rows trick). */
.apw-acc__panel { display:grid; grid-template-rows:0fr;
  transition:grid-template-rows .5s cubic-bezier(.4,0,.2,1); }
/* Suppress the animation when a section is force-closed during a switch, so
   the layout settles instantly before the scroll target is measured. */
.apw-acc--noanim .apw-acc__panel, .apw-acc--noanim .apw-acc__body { transition:none !important; }
.apw-acc.is-open .apw-acc__panel { grid-template-rows:1fr; }
.apw-acc__inner { overflow:hidden; min-height:0; }
.apw-acc__body { max-width:none; padding:26px 28px 44px;
  opacity:0; transition:opacity .4s ease .06s; }
.apw-acc.is-open .apw-acc__body { opacity:1; }
/* Figures inside an open panel: beat the reveal's opacity:0 hold. */
.apw-acc.is-open .apw-acc__body figure { opacity:1 !important; transform:none !important; }

@media (max-width: 860px) {
  .apw-acc { margin-bottom:14px; }
  /* Extra right padding reserves room for the absolutely-placed chevron. */
  .apw-acc__head { flex-wrap:wrap; gap:10px 16px; padding:22px 48px 22px 20px; position:relative; }
  .apw-acc__lead { flex-basis:100%; }
  .apw-acc__co { font-size:21px; }
  .apw-acc__aside { flex-basis:100%; align-items:flex-start; gap:8px; }
  .apw-acc__tags { justify-content:flex-start; }
  .apw-acc__chev { position:absolute; top:22px; right:18px; }
  .apw-acc__body { padding:20px 20px 32px; }
}

@media (prefers-reduced-motion: reduce) {
  .apw-acc__panel, .apw-acc__body, .apw-acc__chev, .apw-acc__head, .apw-acc__co { transition:none !important; }
}

/* ---- Page entrance (replaces cross-document View Transitions) ---
   Cross-document view transitions were unreliable here: the pages ship
   nearly empty and the DC runtime renders content into #dc-root>.sc-host
   asynchronously (React commit after DOMContentLoaded), so the browser
   frequently snapshotted the new page before its content existed and the
   transition was skipped. Instead, animate .sc-host itself: the browser
   starts this animation the moment the element is inserted, which is
   exactly when the page's content commits, on every navigation, with no
   snapshot-timing race. Opacity only, so the sticky header, transforms
   and per-section scroll reveals are left untouched. */
@media (prefers-reduced-motion: no-preference) {
  #dc-root > .sc-host { animation: apw-page-in .5s cubic-bezier(.2,.7,.2,1) both; }
}
@keyframes apw-page-in { from { opacity: 0; } to { opacity: 1; } }

/* ---- Mobile nav (hamburger, injected by apw-nav.js) ------- */
/* Hidden on desktop; the .apw-nav-wrap links show instead. */
.apw-menu-btn, .apw-mobile-nav { display:none; }

@media (max-width: 860px) {
  /* The CTA now sits beside the brand, where it would crowd the hamburger on a
     phone. Hide it here; apw-nav.js still clones it into the slide-down panel. */
  .apw-header__brand .apw-cta { display:none; }

  .apw-menu-btn {
    display:inline-flex; flex-direction:column; justify-content:center; gap:5px;
    width:44px; height:44px; padding:10px; margin:-6px -8px -6px 0;
    background:transparent; border:0; cursor:pointer; border-radius:10px;
  }
  .apw-menu-btn span {
    display:block; width:100%; height:2px; border-radius:2px; background:var(--ink);
    transition:transform .28s cubic-bezier(.2,.7,.2,1), opacity .18s ease;
  }
  .apw-menu-open .apw-menu-btn span:nth-child(1) { transform:translateY(7px) rotate(45deg); }
  /* !important: beats html.apw-motion .apw-header * { opacity:1 } (the reveal
     rule), which is more specific and would otherwise stop the bar hiding. */
  .apw-menu-open .apw-menu-btn span:nth-child(2) { opacity:0 !important; }
  .apw-menu-open .apw-menu-btn span:nth-child(3) { transform:translateY(-7px) rotate(-45deg); }

  .apw-mobile-nav {
    display:flex; flex-direction:column;
    position:absolute; left:0; right:0; top:100%;
    background:var(--mnav-bg);
    -webkit-backdrop-filter:blur(14px); backdrop-filter:blur(14px);
    border-bottom:1px solid var(--border);
    box-shadow:0 22px 44px -26px rgba(20,30,55,.55);
    padding:6px 24px 14px;
    /* !important on visibility: the panel lives inside .apw-header, so the
       reveal rule html.apw-motion .apw-header * { opacity:1 } (specificity
       0,2,1) outranks this and would pin it permanently visible - i.e. the
       menu looking "always open". These win regardless. */
    transform:translateY(-8px); opacity:0 !important; pointer-events:none !important;
    transition:opacity .22s ease, transform .22s cubic-bezier(.2,.7,.2,1);
  }
  .apw-menu-open .apw-mobile-nav { transform:translateY(0); opacity:1 !important; pointer-events:auto !important; }
  .apw-mnav-link {
    font:600 18px 'Titillium Web',system-ui,sans-serif; color:var(--ink);
    text-decoration:none; padding:14px 6px; border-top:1px solid rgba(28,34,48,.09);
  }
  .apw-mnav-link:first-child { border-top:0; }
  .apw-mnav-link.is-current { color:var(--accent); }
  /* apw-nav.js clones the header links and drops their classes, so the GitHub
     mark arrives here unstyled. Size it, and show the label it hides on desktop. */
  .apw-mnav-link { display:flex; align-items:center; gap:10px; }
  .apw-mnav-link svg { display:block; width:20px; height:20px; fill:currentColor; flex:none; }
  .apw-mnav-link .apw-gh__label { position:static; width:auto; height:auto; margin:0;
    overflow:visible; clip-path:none; }
}

/* ---- Responsive ------------------------------------------- */
@media (max-width: 1080px) { .apw-proj-grid, .apw-demos-grid { grid-template-columns:1fr 1fr !important; } }
@media (max-width: 860px) {
  .apw-nav-wrap { display:none !important; }
  .apw-hero-title { font-size: clamp(40px, 10vw, 78px) !important; }
  .apw-page-title { font-size: clamp(38px, 9vw, 66px) !important; }
  .apw-bio-grid { grid-template-columns: 1fr !important; }
  .apw-job { grid-template-columns: 1fr !important; gap:14px !important; }
  .apw-proj-grid { grid-template-columns:1fr !important; }
  .apw-analysis-grid { grid-template-columns:1fr !important; }
  .apw-demos-grid { grid-template-columns:1fr !important; }
  .apw-xcard-grid { grid-template-columns:1fr 1fr !important; }
  .apw-pad { padding-left:24px !important; padding-right:24px !important; }
}

/* Phones: two of these cards cannot fit. Each carries 30px of horizontal
   padding plus a border, so at 390px the pair overflowed the viewport and the
   page scrolled sideways. Stack them.
   The doubled selector matches the extracted .u-grid-* utility's specificity -
   a single class would lose to it even with !important, since that utility
   hardcodes repeat(3,1fr). */
@media (max-width: 600px) {
  .apw-xcard-grid.apw-xcard-grid { grid-template-columns:1fr !important; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior:auto; }
  .apw-reveal { opacity:1; transform:none; transition:none; }
}

/* ---- Theme plumbing --------------------------------------- */
/* These surfaces are set with inline styles on every page, so the
   token swap needs !important to reach them. */
.apw-header { background:var(--header-bg) !important; }
.apw-proj, .apw-xcard { background:var(--surface) !important; }

/* Panels that use inline background:var(--ink) as a dark surface (the
   .apw-lamp page-title bands and the Contact "needs building" card).
   --ink is a near-white *text* tone, so without this they would paint
   themselves light and put white text on a white panel. Pin them dark. */
.apw-lamp,
[style*="background:var(--ink)"],
[style*="background: var(--ink)"] { background:#0e1420 !important; }

/* ---- Cookie consent banner (built by apw-consent.js) -------- */
/* Floating card, bottom-left, above everything incl. the game frame (2001).
   Solid surface, not glass: the text must read over anything. The two
   choices are styled identically on purpose - declining is not made the
   uglier button. */
.apw-consent { position:fixed; left:20px; bottom:20px; z-index:2100;
  max-width:430px; padding:22px 24px; background:var(--surface);
  border:1px solid var(--border-strong); border-radius:16px;
  box-shadow:0 30px 70px -30px rgba(0,0,0,.7); outline:none; }
.apw-consent__text { font:400 15px/1.6 'Libre Franklin',system-ui,sans-serif;
  color:var(--body-ink); margin:0 0 16px; }
.apw-consent__text a { color:var(--accent); }
.apw-consent__actions { display:flex; justify-content:flex-end; gap:12px; flex-wrap:wrap; }
@media (max-width:520px) {
  .apw-consent { left:12px; right:12px; bottom:12px; max-width:none; }
}

/* ---- Footer legal row: privacy link + cookie choices --------- */
.apw-legal-links { display:inline-flex; align-items:center; gap:14px; }
.apw-legal-links a,
.apw-legal-links button { font:400 14px 'Libre Franklin',system-ui,sans-serif;
  color:rgba(255,255,255,.5); background:none; border:0; padding:0; cursor:pointer;
  text-decoration:underline; text-underline-offset:3px; }
.apw-legal-links a:hover,
.apw-legal-links button:hover { color:#fff; }

/* ---- Privacy page prose -------------------------------------- */
.apw-legal { max-width:72ch; }
.apw-legal h2 { font:700 26px/1.25 'Heebo',system-ui,sans-serif; color:var(--ink);
  letter-spacing:-.02em; margin:44px 0 12px; }
.apw-legal p, .apw-legal li { font:400 17px/1.7 'Libre Franklin',system-ui,sans-serif;
  color:var(--body-ink); }
.apw-legal p { margin:0 0 14px; }
.apw-legal ul { margin:0 0 14px; padding-left:22px; }
.apw-legal li { margin:0 0 6px; }
.apw-legal a { color:var(--accent); }
.apw-legal .apw-legal__updated { font:400 14px 'Libre Franklin',system-ui,sans-serif;
  color:var(--muted); margin:0 0 8px; }
