/* ==========================================================================
   Magfolio v2 — "Painted Maximalism" theme layer
   --------------------------------------------------------------------------
   Load this AFTER /assets/styles.css. It never removes structure; it repaints
   every surface in Maggie's own visual language: painterly brushstroke bands,
   deep-purple (#221442) cards floating on paint, cream (#fffaee) ink, lavender
   pills, cut-out artwork over rotated colour slabs, and hand-made motion.

   ACCESSIBILITY: every text/background pair below meets WCAG 2.1 AA
   (>=4.5:1 body, >=3:1 large text). See v2/README.md for the contrast table.
   The original site's cream-on-white headings are corrected to cream-on-paint.

   All painted photography is referenced through custom properties so the
   standalone preview (file://) can repoint them at ../dist/media/.
   ========================================================================== */

:root {
  /* --- Maggie's palette, sampled from mag-folio.com + her gouache --------- */
  --deep: #221442;            /* header bar / title cards */
  --deep-2: #2d1b56;
  --cream: #fffaee;           /* her ink on purple */
  --cream-soft: #edd3ec;
  --paper: #fbf6ec;           /* warm gallery paper, not sterile white */
  --lav: #c9a0dc;
  --lav-pale: #e9d6f2;
  --rose: #c0628f;
  --teal: #2f8fa8;
  --ochre: #c89b6a;           /* from the gouache self-portrait ground */
  --crimson: #b3202c;         /* ZBrush */
  --maya: #37a5cc;            /* Autodesk Maya */
  --blender: #e87d0d;         /* Blender */
  --gp: #8a5fbf;              /* Grease Pencil */

  /* --- ink tokens -------------------------------------------------------
     Two families, and every text colour in this sheet comes from one of them.
     Each is measured against the *hardest* surface in its family rather than
     the obvious one, so a token that passes here passes everywhere in the
     theme:

       light family — verified on #d0c3d7, the darkest of the lavender washes
                      painted behind body copy (not --paper). Passing there
                      means it also passes on --paper, --cream, --lav-pale and
                      the #f6e7fb chip tint.
       dark family  — verified on #442c6e, the lightest purple in the header
                      bar and hero wash. Passing there means it also passes on
                      --deep and --deep-2.

     Ratios are WCAG 2.1 contrast: body copy needs 4.5:1, text 24px+ (or 18.66px
     at 700) needs 3:1. Reproduce them with `python v2/preview.py` and
     `window.__audit()` in the console.

     The raw palette hues above (--teal, --rose, --ochre, --crimson, --lav) are
     paint, not ink: at 2.3–3.7:1 on a light ground they fail as text. Use them
     for swashes, slabs, borders and icons, where 3:1 is the bar. Words on a
     light ground take --ink / --ink-soft / --muted / --accent. */
  --ink: #3a2a5e;             /* 7.5:1 on the wash, 11.7:1 on paper */
  --ink-soft: #55417f;        /* 5.1:1 / 8.0:1 */
  --muted: #564a6c;           /* 4.8:1 / 7.5:1 — was #6b5c86, only 3.6:1 on the wash */
  --line: #c9b6da;
  --accent: #682792;          /* 5.4:1 / 8.5:1 */
  --on-deep: var(--cream);          /* 11.0:1 on #442c6e, 16.2:1 on --deep */
  --on-deep-soft: var(--cream-soft);/*  8.3:1 / 12.1:1 */
  --on-deep-muted: var(--lav);      /*  5.2:1 /  7.7:1 — the quiet tier on purple */

  --bg: var(--paper);
  --surface: #fffdf7;
  --tint: var(--lav-pale);

  /* maximalist geometry: irregular, hand-cut */
  --radius: 34px 22px 30px 24px / 24px 30px 22px 34px;
  --radius-sm: 16px 10px 14px 11px / 11px 14px 10px 16px;
  --slab-1: var(--lav);
  --slab-2: var(--teal);
  --slab-3: var(--rose);
  --slab-4: var(--ochre);
  --shadow-paint: 0 2px 0 rgb(34 20 66 / .10), 0 14px 34px -12px rgb(34 20 66 / .38);

  /* --- corner scale: Apple's rounded rectangle, never a stadium ----------
     Two families of surface, deliberately different:

       art surfaces keep the hand-cut --radius / --radius-sm above. Torn paper
       is the collage identity and it is not a control, so Apple's geometry
       does not apply to it.
       controls take the fixed scale below. HIG (Buttons) gives a text button a
       rounded rectangle and requires a flexible-height button to "maintain
       consistent corner radius and padding" as its content grows — so the
       radius is a fixed length per size class, never 999px.

     999px is what made the buttons smush: a pill radius is half the element's
     *shortest* side, so the arc eats the flat edge until a wide CTA on a
     narrow window is nothing but two semicircles. Each value here is ~0.22 of
     the control height it belongs to — the proportion Apple's own iOS app
     icon uses (185 of 1024) — so the corner stays a corner at any width.

     --corner makes them continuous: SwiftUI's RoundedCornerStyle.continuous,
     shipped to CSS as `corner-shape: squircle` (superellipse(2)). Browsers
     without it drop the declaration and render the same border-radius as a
     plain quarter-circle — correct, just less soft. */
  --r-xs: 8px;                /* 36-40px tags and small chips */
  --r-sm: 10px;               /* nav links, form fields, the objective anchor */
  --r-md: 12px;               /* the 44-50px control height: buttons */
  --r-lg: 18px;               /* popovers, panels, menus */
  --r-xl: 26px;               /* full-bleed art frames */
  --r-circle: 50%;            /* icon-only controls stay circular per HIG */
  --corner: squircle;

  /* HIG's minimum hit region is 44x44pt on iOS/iPadOS/macOS/tvOS/watchOS, and
     its iOS type floor is 11pt — nothing on the site is allowed below either.
     These are rem-where-possible so both still scale with the reader's own
     text-size preference, which Apple calls out as essential: an interface
     that ignores it "can be difficult or impossible to use". */
  --hit: 2.75rem;             /* 44px at the default root size */
  --hit-lg: 3.125rem;         /* 50px: primary CTAs */
  --type-floor: 0.6875rem;    /* 11px */

  /* --- hero backdrop: painted in CSS, never in photographs -------------
     The build already ships the art as real <img> tags (.project-hero-media)
     and as JS stage scenes (orbit / gallery), so the theme only
     supplies the gouache wash behind them. Hashed /media/ filenames must not
     appear here: they change every time the site is rebuilt.               */
  --hero-paint:
    radial-gradient(120% 130% at 8% -20%, rgb(104 39 146 / .95), transparent 58%),
    radial-gradient(90% 110% at 92% 4%, rgb(47 143 168 / .50), transparent 60%),
    radial-gradient(70% 90% at 62% 112%, rgb(192 98 143 / .55), transparent 62%),
    linear-gradient(168deg, var(--deep-2) 4%, var(--deep) 58%, #1a0f36 100%);

  /* --- hand-made SVG paint: a tapered brush swash + paper grain ---------- */
  --swash: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 26' preserveAspectRatio='none'%3E%3Cpath d='M5 15C42 6 74 20 112 13c42-8 74 8 118 1 34-5 56 2 65-2-14 11-46 8-82 13-38 5-74-8-112-2-36 6-66 2-96-8z' fill='currentColor'/%3E%3C/svg%3E");
  --swash-thick: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 40' preserveAspectRatio='none'%3E%3Cpath d='M4 22C40 8 78 30 118 20c44-11 78 12 122 2 32-7 50 3 56-2-8 16-44 14-84 20-40 6-78-10-116-3-36 7-64 4-92-17z' fill='currentColor'/%3E%3C/svg%3E");
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
  --brush-edge: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1200 60' preserveAspectRatio='none'%3E%3Cpath d='M0 34c60-14 120 12 190 6s110-24 180-18 120 26 190 20 110-26 180-20 120 24 190 18 110-22 170-14l100 12v22H0z' fill='currentColor'/%3E%3C/svg%3E");
  /* one 35mm sprocket hole, tiled down the edges of an empty film window */
  --sprockets: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='30'%3E%3Crect x='3' y='8' width='14' height='14' rx='4.5' fill='%23fffaee'/%3E%3C/svg%3E");
}

/* ==========================================================================
   1 · The whole page sits on painted paper
   ========================================================================== */
body {
  background:
    radial-gradient(120% 60% at 12% -8%, rgb(201 160 220 / .30), transparent 60%),
    radial-gradient(90% 50% at 96% 4%, rgb(47 143 168 / .16), transparent 62%),
    radial-gradient(70% 46% at 50% 108%, rgb(192 98 143 / .16), transparent 60%),
    var(--paper);
  color: var(--ink);
}
/* a faint canvas tooth over everything, like gouache on cold-press paper.
   Kept at .12 so it never drops text contrast below AA. */
body::before {
  content: "";
  position: fixed;
  /* starts below the bar: a see-through fixed layer touching the top edge
     keeps iOS Safari from colouring the status strip from the bar */
  inset: var(--header-h, 76px) 0 0 0;
  z-index: 60;
  pointer-events: none;
  background: var(--grain) repeat 0 0 / 220px 220px;
  opacity: .12;
  mix-blend-mode: multiply;
}

h1, h2, h3, h4 { letter-spacing: -0.015em; }
a { text-decoration: underline; text-underline-offset: .18em; }   /* always visible links */

/* Painted swash under every major heading, drawn on as it enters view.
   Swash colours are chosen so heading ink stays >=4.5:1 on top of them. */
h1, h2 { position: relative; display: inline-block; }
h1::after, h2::after {
  content: "";
  position: absolute;
  left: -0.06em;
  right: -0.10em;
  bottom: -0.28em;
  height: 0.34em;
  /* the SVGs are used as MASKS, not backgrounds: a data-URI SVG in
     background-image cannot inherit currentColor and always paints black.
     Masking lets every swash keep its per-heading colour. */
  background-color: currentColor;
  -webkit-mask: var(--swash-thick) no-repeat center / 100% 100%;
  mask: var(--swash-thick) no-repeat center / 100% 100%;
  color: var(--lav);
  z-index: -1;
  opacity: .95;
}
html.js-paint h1::after, html.js-paint h2::after {
  -webkit-mask-size: 0% 100%;
  mask-size: 0% 100%;
  transition: -webkit-mask-size 1.1s cubic-bezier(.22,.9,.24,1) .15s,
              mask-size 1.1s cubic-bezier(.22,.9,.24,1) .15s;
}
html.js-paint h1.reveal-in::after, html.js-paint h2.reveal-in::after,
html.js-paint .reveal-in h1::after, html.js-paint .reveal-in h2::after {
  -webkit-mask-size: 100% 100%;
  mask-size: 100% 100%;
}
.section:nth-of-type(odd) h2::after, .showcase h2::after { color: var(--teal); }
.section:nth-of-type(even) h2::after { color: var(--rose); }
.site-footer h2::after { color: var(--lav); }

/* ==========================================================================
   2 · Header: her deep-purple bar with the lavender pill, made richer
   ========================================================================== */
.site-header {
  background:
    linear-gradient(rgb(255 255 255 / .05), rgb(255 255 255 / 0)),
    linear-gradient(100deg, var(--deep) 0%, var(--deep-2) 55%, #3a2166 100%),
    var(--deep);   /* a solid colour under the gradient: iOS Safari tints the status bar from it,
                      so the page no longer shows through above the bar */
  border-bottom: 0;
  box-shadow: 0 10px 26px -14px rgb(34 20 66 / .7);
  /* the bar is opaque, so the base sheet's blur does nothing but stop Safari reading its colour */
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.brand { color: var(--cream); }
/* the base sheet writes the lockup's tagline in --muted, a light-surface ink;
   on the purple bar it measured 1.9:1 — the worst text on the whole site */
.brand-tag { color: var(--on-deep-muted); }
.nav-link {
  color: var(--cream-soft);
  border-radius: 999px;
  padding: .42em 1.05em;
  text-decoration: none;
  white-space: nowrap;
  transition: background .25s, color .25s, transform .25s;
}
.nav-link:hover { background: rgb(255 250 238 / .16); color: var(--cream); transform: translateY(-1px); }
/* the base sheet paints the "Work" dropdown toggle in ink — invisible on purple */
.nav-link.is-section { color: var(--cream-soft); }
.nav-link.is-section:hover { color: var(--cream); }
/* the current-page pill has to come last: .nav-link.is-section is the same
   specificity, so an earlier pill rule lost and rendered cream-soft on
   lav-pale — a measured 1.02:1, i.e. invisible */
.nav-link[aria-current="page"],
.nav-link.is-section[aria-current="page"] {
  background: var(--lav-pale);
  color: var(--deep);
  font-weight: 700;
  box-shadow: 0 3px 0 rgb(34 20 66 / .35);
}
.nav-link[aria-current="page"]:hover,
.nav-link.is-section[aria-current="page"]:hover { background: var(--cream); color: var(--deep); }
/* the click-to-call link lives in the bar on desktop */
.header-call {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: .45em;
  background: var(--lav-pale);
  color: var(--deep);
  font-weight: 700;
  border-radius: 999px;
  padding: .42em 1em;
  text-decoration: none;
  box-shadow: 0 3px 0 rgb(34 20 66 / .4);
  white-space: nowrap;
}
.header-call:hover { background: var(--cream); transform: translateY(-1px); }
.header-call svg { width: 1em; height: 1em; fill: currentColor; }
/* The bar is a tight fit between its own 860px collapse point and ~1330px:
   six links, a brand lockup and the phone pill. This tier tightens spacing
   only — never type below the floor. Under 1000px the phone pill drops its
   number and keeps the icon (aria-label still names the call; the fixed
   call-bar owns small screens). */
@media (min-width: 861px) and (max-width: 1330px) {
  .nav-list { gap: clamp(2px, .7vw, 12px); }
  .nav-link { font-size: .97rem; padding: .4em .72em; }
  .header-call { padding: .35em .75em; }
  .brand-logo { height: clamp(26px, 3.4vw, 36px); }
}
@media (min-width: 861px) and (max-width: 1000px) {
  .header-call span { display: none; }
  /* the tagline is the first thing to go — otherwise the row runs long and
     flex-shrink quietly squeezes the phone pill below its 44pt floor */
  .brand-tag { display: none; }
  /* icon-only still owes the 44pt floor in both axes: a fixed square with a
     centered glyph, not a squeezed pill */
  .header-call {
    padding: 0;
    width: var(--hit);
    justify-content: center;
  }
}
.subnav {
  background: var(--deep-2);
  border: 1px solid rgb(255 250 238 / .16);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-paint);
  overflow: hidden;
}
.subnav a { color: var(--cream-soft); }
.subnav a:hover { background: rgb(255 250 238 / .14); color: var(--cream); }
.subnav a[aria-current] { background: var(--lav-pale); color: var(--deep); font-weight: 700; }
/* Small screens: the menu is one purple sheet under the bar. Sub-links sit
   two-up so every item fits on a phone without scrolling the sheet. */
@media (max-width: 860px) {
  .site-nav {
    background: var(--deep);
    border-bottom: 0;
    box-shadow: 0 18px 30px rgb(34 20 66 / .55);
    padding: 6px var(--gutter) 16px;
  }
  .nav-item { border-bottom-color: rgb(255 250 238 / .16); }
  /* section names read as headings, their sub-links as a smaller indented list */
  .nav-link, .nav-link.is-section { color: var(--cream); font-size: 1.3rem; font-weight: 700; padding: .3em .7em; margin-block: 3px; }
  .subnav,
  .nav-item:hover > .subnav,
  .nav-item:focus-within > .subnav {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0 8px;
    margin: 0 0 8px 1.1rem;
    padding: 0 0 0 4px;
    border: 0;
    border-left: 2px solid rgb(233 214 242 / .4);
    border-radius: 0;
    background: none;
    box-shadow: none;
    overflow: visible;
  }
  .subnav a { display: flex; align-items: center; padding: 4px 10px; border-radius: var(--r-sm); color: var(--cream-soft); font-size: .95rem; font-weight: 400; line-height: 1.25; }
  .subnav a[aria-current] { font-weight: 700; }
  .nav-commissions { margin: 8px 0 0; }
}

/* ==========================================================================
   3 · Hero: a full-bleed painted band, collage layers, floating paint
   ========================================================================== */
.hero-3d, .page-hero {
  position: relative;
  isolation: isolate;
  overflow: visible;
  /* every stop in --hero-paint keeps cream text at >=7:1 (worst case 7.3:1
     over the rose wash), so the artwork stages can sit on top unharmed */
  background: var(--hero-paint);
  color: var(--cream);
}
/* canvas tooth + one broad dry-brush drag, so the wash reads as gouache laid
   on cold-press paper rather than a CSS gradient */
/* canvas tooth only. The old drag screen-blended mid-gray noise at .55, which
   fogged the whole band to lavender and sank the cream copy to ~4:1; soft-light
   keeps the gouache grain without the fog, so cream measures >=10:1 on paint. */
.hero-3d::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--grain) repeat;
  opacity: .35;
  mix-blend-mode: soft-light;
  z-index: -1;
  pointer-events: none;
}
/* the copy column arrives with a near-opaque white spotlight from the base
   sheet (rgba(248,251,252,.96) radial) — invisible-ink territory for cream
   text. The painted band is the seat now. */
.hero-3d-copy::before { content: none; }
/* torn brush edge tearing the painted band into the paper below */
.hero-3d::after, .page-hero::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -24px;
  height: 26px;
  background-color: currentColor;
  -webkit-mask: var(--brush-edge) no-repeat center / 100% 100%;
  mask: var(--brush-edge) no-repeat center / 100% 100%;
  color: var(--deep);
  z-index: 3;
  pointer-events: none;
}

/* project heroes get their art from a real <img> in the build; treat that
   plate like a painting instead of a flat banner */
.project-hero-media img {
  filter: saturate(1.06) contrast(1.04);
  transform: scale(1.015);
}
.project-hero-david .project-hero-media { background: #9ed8d3; }
.project-hero-david .project-hero-media img {
  object-fit: contain;
  object-position: center;
  transform: none;
}
.project-hero-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--grain) repeat;
  opacity: .2;
  mix-blend-mode: overlay;
  pointer-events: none;
}
/* :not(.objective *) keeps this cream hero ink out of the Objective card, which
   brings its own light ground. Unguarded, `.page-hero p` (0,1,1) outranked the
   card's own `.objective-scope` (0,1,0) and repainted the whole card cream on
   cream — a measured 1.0:1 wherever the brief sits inside a page hero. */
.hero-3d h1, .hero-3d .lead,
.page-hero h1, .page-hero p:not(.objective, .objective *) {
  color: var(--on-deep);
  text-shadow: 0 1px 2px rgb(23 13 48 / .6);
}
.hero-3d .eyebrow, .page-hero .eyebrow {
  color: var(--lav-pale);
  letter-spacing: .22em;
  text-transform: uppercase;
  font-weight: 700;
}
.hero-3d h1 strong, .page-hero h1 strong { color: #ffe9a8; }

/* her pale-lavender pills, sitting on a painted stroke like the original */
.pill-list { position: relative; }
.pill-list::before {
  content: "";
  position: absolute;
  inset: -14px -26px;
  background-color: currentColor;
  -webkit-mask: var(--swash-thick) no-repeat center / 100% 100%;
  mask: var(--swash-thick) no-repeat center / 100% 100%;
  color: rgb(201 160 220 / .5);
  z-index: -1;
  transform: rotate(-1deg);
}
.pill-list li {
  background: var(--lav-pale);
  color: var(--deep);
  border: 0;
  box-shadow: 0 3px 0 rgb(34 20 66 / .4);
  font-weight: 700;
  transform: rotate(-.6deg);
}
.pill-list li:nth-child(2n) { transform: rotate(.8deg); background: #f6e7fb; }

/* Apple's geometry for a text button: a fixed rounded rectangle, never a
   stadium. 999px is half the element's *shortest* side, so the arc ate the flat
   edge and a long CTA on a narrow window smushed into two semicircles; --r-md
   is ~0.22 of the 44px control height — the proportion of an iOS app icon — so
   the corner stays a corner at any width and the label grows by wrapping.
   min-height is the HIG 44pt hit region in rem, so it scales with the reader's
   own text size. §12e carries the rest of the control geometry. */
.btn {
  min-height: var(--hit);
  padding: 0 1.25rem;
  border-radius: var(--r-md);
  line-height: 1.25;
  white-space: normal;
  text-wrap: balance;
  background: var(--lav-pale);
  color: var(--deep);
  font-weight: 700;
  text-decoration: none;
  box-shadow: 0 4px 0 rgb(34 20 66 / .45);
  transition: transform .2s, box-shadow .2s, background .2s;
}
.btn:hover { transform: translateY(-2px) rotate(-.5deg); box-shadow: 0 6px 0 rgb(34 20 66 / .45); }
/* Outlined button. On a light ground it takes --accent ink and hairline (8.5:1
   on paper, 5.4:1 on the lavender wash); on paint it flips to cream. Scoping the
   flip to the dark bands is what keeps it legible — an unscoped cream outline
   measured 1.6:1 against the about page's light section. */
.btn-outline {
  background: transparent;
  color: var(--accent);
  border: 2px solid var(--accent);
  box-shadow: none;
}
.btn-outline:hover { background: rgb(104 39 146 / .1); }

:is(.hero-3d, .page-hero, .project-head, .info-card, .callout, .commission,
    .site-footer) .btn-outline {
  color: var(--on-deep);
  border-color: var(--lav);
}
:is(.hero-3d, .page-hero, .project-head, .info-card, .callout, .commission,
    .site-footer) .btn-outline:hover { background: rgb(255 250 238 / .14); }

/* the 3D stage / portrait becomes a torn painting pinned to the band */
.stage3d, .portrait { position: relative; isolation: isolate; }
.stage-fallback > img, .portrait img {
  border-radius: 6px;
  box-shadow: 0 0 0 10px var(--cream), 0 0 0 12px rgb(34 20 66 / .5), var(--shadow-paint);
  transform: rotate(-1.4deg);
}
.stage-fallback::before, .portrait::before {   /* rotated colour slab behind */
  content: "";
  position: absolute;
  inset: 6% -5% -5% 7%;
  background: var(--rose);
  border-radius: var(--radius);
  transform: rotate(3.4deg);
  z-index: -1;
  opacity: .9;
}
/* painter's tape at the top of a framed portrait */
.portrait::after {
  content: "";
  position: absolute;
  top: -14px; left: 50%;
  width: 92px; height: 26px;
  background: rgb(255 250 238 / .8);
  transform: translateX(-50%) rotate(-3deg);
  box-shadow: 0 2px 6px rgb(34 20 66 / .25);
  z-index: 2;
}

.avatar-greet a {
  background: var(--cream);
  color: var(--deep);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-paint);
  padding: .5em .9em;
  display: inline-block;
  transform: rotate(-2deg);
}

/* floating paint specks injected by maximal.js */
.speck-field { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 1; }
.speck-field i {
  position: absolute;
  border-radius: 50%;
  background: currentColor;
  opacity: .45;
  animation: speck-float 9s ease-in-out infinite alternate;
}
@keyframes speck-float {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(var(--dx, 12px), var(--dy, -26px), 0) scale(1.25); }
}

/* ==========================================================================
   4 · Cards & figures: cut-out artwork over rotated colour slabs
   ========================================================================== */
.card, .tile, .figure, .gallery > li, .zoom {
  position: relative;
  isolation: isolate;
  background: transparent;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}
.card::before, .tile::before, .figure::before, .gallery > li::before {
  content: "";
  position: absolute;
  inset: 5% -4% -4% 6%;
  background: var(--slab, var(--slab-1));
  border-radius: var(--radius);
  transform: rotate(-2.6deg);
  z-index: -1;
  transition: transform .35s cubic-bezier(.22,.9,.24,1);
}
/* the base sheet clips the card to its box, which hid the stroke entirely */
.card { overflow: visible; }
.card::before { inset: 4% -5% -4% -5%; }   /* shows on both sides of the picture */
li:nth-child(odd) > .card { --slab: var(--rose); }
li:nth-child(even) > .card { --slab: var(--teal); }
.card:hover::before, .tile:hover::before, .figure:hover::before { transform: rotate(-4.4deg) scale(1.02); }

.card-media, .tile img, .figure img, .zoom img {
  border-radius: 4px;
  box-shadow: 0 0 0 8px var(--cream), 0 0 0 9px rgb(34 20 66 / .35), var(--shadow-paint);
  transform: rotate(.7deg);
  transition: transform .35s cubic-bezier(.22,.9,.24,1);
  background: var(--surface);
}
.card:nth-child(2n) .card-media, .gallery > li:nth-child(2n) img { transform: rotate(-.9deg); }
.card:hover .card-media, .tile:hover img, .figure:hover img { transform: rotate(0deg) scale(1.025); }

/* in a card the picture fills its box edge to edge: no cream mat, no tilt
   leaving a sliver of it showing before the hover */
.card .card-media { transform: none; box-shadow: none; background: var(--deep-2); }
.card .card-media.bg-light { background: var(--surface); }   /* cut-outs keep their light panel */
.card .card-media--contain { background: #000; }             /* the Flower Alien's own black ground */
.card-media--top img { object-position: top; }
.card-media--left img { object-position: left; }
.card-media--contain img,
.card:hover .card-media--contain img {
  object-fit: contain;
  transform: none;
}
.card-body { padding: 1.2rem 1.15rem 1.35rem; background: transparent; }
.card-body h3 { color: var(--deep); }
.card-body p { color: var(--ink-soft); }
.card-more { color: var(--accent); font-weight: 700; }

/* denser, more collage-like grids */
.card-grid { gap: clamp(2.2rem, 4.5vw, 4rem) clamp(1.6rem, 3vw, 2.8rem); }
/* the nine projects stay three across however wide the window gets */
@media (min-width: 900px) { .card-grid-3 { grid-template-columns: repeat(3, 1fr); } }
.track { gap: clamp(1.8rem, 3.4vw, 3rem); }

.icon-btn {
  background: var(--deep);
  color: var(--cream);
  border: 0;
  box-shadow: 0 4px 0 rgb(34 20 66 / .35);
  border-radius: 50%;
}
.icon-btn:hover { background: var(--accent); transform: translateY(-2px); }

/* ==========================================================================
   5 · A painted marquee ribbon between hero and work
   ========================================================================== */
.paint-marquee {
  position: relative;
  z-index: 3;
  margin: -1.2rem 0 2.6rem;
  padding: .8rem 0;
  background: linear-gradient(95deg, var(--deep), #3a2166 60%, var(--deep-2));
  color: var(--cream);
  transform: rotate(-1.3deg) scaleX(1.04);
  box-shadow: var(--shadow-paint);
  overflow: hidden;
}
.paint-marquee .mq-track {
  display: flex;
  gap: 2.6rem;
  width: max-content;
  animation: mq-scroll 34s linear infinite;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  font-size: .92rem;
}
.paint-marquee .mq-track span { display: inline-flex; align-items: center; gap: 2.6rem; }
.paint-marquee .mq-track span::after { content: "✦"; color: var(--lav); }
.paint-marquee:hover .mq-track, .paint-marquee:focus-within .mq-track { animation-play-state: paused; }
@keyframes mq-scroll { to { transform: translateX(-50%); } }

/* ==========================================================================
   6 · Project / about / paintings pages
   ========================================================================== */
.page-hero { padding-block: clamp(3.5rem, 9vw, 7rem); }
.project-head, .info-card, .callout {
  background: linear-gradient(120deg, var(--deep), var(--deep-2));
  color: var(--cream);
  border: 0;
  border-radius: var(--radius);
  box-shadow: var(--shadow-paint);
  transform: rotate(-.5deg);
}
.project-head { padding: clamp(1.25rem, 3vw, 2rem); }
.project-head h1, .info-card h2, .info-card h3, .callout h2, .callout h3 { color: var(--cream); }
.project-head p, .info-card p, .callout p { color: var(--cream-soft); }

/* the original put cream headings on white (illegible). Cream now only ever
   sits on paint; on paper we use deep purple. */
.project h2, .project h3 { color: var(--deep); }
.project h2::after { color: var(--ochre); }
/* …but the painted title cards keep cream ink. This must come AFTER the rule
   above: same specificity, and .project-head h2 lives inside .project. */
.project-head h2, .project-head h3,
.info-card h2, .info-card h3,
.callout h2, .callout h3 { color: var(--cream); }

.embed, .sprite-row > li {
  border-radius: var(--radius-sm);
  box-shadow: 0 0 0 8px var(--cream), var(--shadow-paint);
}
.pager a {
  background: var(--lav-pale);
  color: var(--deep);
  border-radius: 999px;
  font-weight: 700;
  box-shadow: 0 4px 0 rgb(34 20 66 / .35);
}

.gallery.project-gallery {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: clamp(.8rem, 1.8vw, 1.25rem);
  margin-top: clamp(2rem, 5vw, 4rem);
}
.project-gallery > li {
  grid-column: span 3;
  min-width: 0;
  margin: 0;
  padding: .65rem;
  background: var(--cream);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-paint);
}
.project-gallery > li::before { content: none; }
.project-gallery > li:only-child { grid-column: 1 / -1; }
.project-gallery-5 > li:nth-last-child(-n + 3) { grid-column: span 2; }
/* wide renders: the first across the full width, then pairs, in 16:9 frames;
   with an even count the last has no partner, so it runs the full width too */
.project-gallery-wide > li:first-child,
.project-gallery-wide > li:last-child:nth-child(even) { grid-column: 1 / -1; }
.gallery.project-gallery-wide .zoom { aspect-ratio: 16 / 9; }
.project-gallery figure {
  display: grid;
  grid-template-rows: 1fr auto;
  height: 100%;
}
.project-gallery .zoom {
  display: grid;
  place-items: center;
  aspect-ratio: 4 / 3;
  background: #120d1c;
  /* the button's own ground is dark, so its text (the sr-only label) must be cream */
  color: var(--cream);
  border-radius: var(--r-sm);
}
.project-gallery .zoom img,
.project-gallery > li:nth-child(2n) img,
.project-gallery .zoom:hover img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  transform: none;
  box-shadow: none;
}
.project-gallery figcaption {
  min-height: 3.2rem;
  padding: .8rem .45rem .25rem;
  color: var(--ink);
  font-weight: 600;
}
@media (max-width: 760px) {
  .project-gallery > li,
  .project-gallery-5 > li:nth-last-child(-n + 3) { grid-column: 1 / -1; }
}

/* About: the avatar becomes a painted portrait in a taped, gilded frame */
.about-grid .stage3d, .about-grid .stage-avatar { isolation: isolate; }
.about-grid .stage-fallback > img {
  box-shadow: 0 0 0 12px var(--cream), 0 0 0 14px var(--ochre), var(--shadow-paint);
  border-radius: 4px;
  transform: rotate(-1deg);
}

/* ==========================================================================
   6b · The Objective brief card — one recognisable object on every project
   --------------------------------------------------------------------------
   Markup comes from tools/objectives.py (v2/objectives.py). It reads as a
   cream index card pinned to the painting with tape: same card, same target
   mark, same lavender-swashed "Objective" heading on every page, so a
   recruiter scanning seven projects always knows where to look.

   `isolation: isolate` is load-bearing: the global h2::after swash uses
   z-index:-1, which would otherwise paint *behind* the card's own background.
   ========================================================================== */
/* --- placement: the brief docks BELOW the hero, in normal flow ------------
   The card used to be injected inside .project-hero-copy. Measured in a
   1581x1145 window that stretched the hero to ~1100px purely to hold a
   593x606 opaque cream card, covering 22% of the full-bleed artwork. Nothing
   is positioned over the painting now: objectives.py emits
   <aside class="objective objective-dock"> immediately after the hero's closing
   </section>, in the document flow, on paper — where its cream ground reads as
   an index card on the desk instead of a placard over the artwork.

   With the card gone the hero falls back to its min-height, so raise it: the
   point of the move is to show more artwork, not less. */
.project-hero { min-height: clamp(480px, 76vh, 800px); }
.project-hero-copy { padding-bottom: clamp(30px, 6vh, 66px); }

/* The hero keeps a one-line chip pointing at the card. It sits in flow with the
   headline, in the same family as the nav's current-page pill, so it reads as
   part of the painted bar rather than as new chrome — never absolutely
   positioned over the artwork. --deep on --lav-pale is 12.4:1. The arrow says
   "the brief is below". */
.objective-anchor {
  display: inline-flex;
  align-items: center;
  gap: .5em;
  min-height: var(--hit);
  margin-top: 1.2rem;
  padding: .5em max(1.15em, 18px);
  background: var(--lav-pale);
  color: var(--deep);
  border-radius: var(--r-sm);
  corner-shape: var(--corner);
  font-size: .95rem;
  font-weight: 700;
  letter-spacing: .04em;
  line-height: 1.25;
  text-decoration: none;
  box-shadow: 0 3px 0 rgb(34 20 66 / .4);
  transition: transform .2s, box-shadow .2s, background .2s;
}
.objective-anchor:hover {
  background: var(--cream);
  transform: translateY(-1px);
  box-shadow: 0 4px 0 rgb(34 20 66 / .4);
}
.objective-anchor:focus-visible { outline: 3px solid var(--cream); outline-offset: 3px; }
.objective-anchor-mark { flex: none; width: 1.35em; height: 1.35em; color: var(--accent); }
.objective-anchor-arrow { flex: none; width: .95em; height: .95em; opacity: .72; }

/* The docked card. It lands as a direct child of <main>, outside .container, so
   it brings its own gutter and its own measure: a painted band across the page,
   capped at 62rem and centred, so the cream card still reads as one object
   rather than a wall of cream. The negative top margin tucks its taped edge
   under the hero's torn brush stroke, which is what makes hero + brief look
   like a single thing laid on the desk.

   Scoped `.objective.objective-dock` because both classes sit on the same
   element here; an unscoped `.objective-dock` would also catch a wrapper-style
   dock (the card inside an .objective-dock <div>), and the two need different
   jobs — the wrapper gets breathing room, the card gets a measure. */
.objective.objective-dock {
  max-width: min(62rem, calc(100% - 2 * var(--gutter, 1.5rem)));
  margin: -1.2rem auto 2.4rem;
  padding: 1.6rem clamp(1.5rem, 4vw, 2.6rem) 1.5rem 2.4rem;
  transform: rotate(-.35deg);
}
/* a band is wide, so cap the reading measure inside it: the brief and each spec
   line stay at a comfortable line length while the card itself stays full */
.objective.objective-dock .objective-text,
.objective.objective-dock .objective-spec dd { max-width: 68ch; }
/* wrapper-style dock (older markup / native port): room below the hero, and the
   card inside it keeps its own 58ch measure, left-aligned in its .container */
.objective-dock:not(.objective) { padding-block: clamp(34px, 7vh, 76px) clamp(6px, 2vh, 24px); }
.objective-dock:not(.objective) .objective { margin: 0; }
.objective-dock:not(.objective) + .objective-dock:not(.objective) { padding-top: 0; }

/* the stock veil fades to transparent too early: the lead washed out over
   light artwork. A heavier middle stop keeps cream-soft text >=4.5:1. */
.project-hero::after {
  background: linear-gradient(180deg,
    rgb(23 13 48 / .40) 0%,
    rgb(23 13 48 / .68) 52%,
    rgb(23 13 48 / .88) 100%);
}

.objective {
  position: relative;
  isolation: isolate;
  max-width: 58ch;
  margin: 1.7rem 0 0;
  padding: 1.35rem 1.5rem 1.3rem 2rem;
  background: linear-gradient(168deg, #fffdf6 12%, var(--cream));
  color: var(--ink);
  border-radius: 6px 14px 8px 12px / 12px 6px 14px 8px;
  box-shadow: 0 0 0 2px var(--lav), var(--shadow-paint);
  transform: rotate(-.6deg);
  scroll-margin-top: 96px;
}
.objective:target { box-shadow: 0 0 0 3px var(--crimson), var(--shadow-paint); }

/* the painted purple spine, like a file tab */
.objective::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 13px;
  background: linear-gradient(180deg, var(--deep) 8%, var(--accent));
  border-radius: 6px 0 0 12px;
}
/* painter's tape pinning it to the page */
.objective::after {
  content: "";
  position: absolute;
  top: -13px; right: 30px;
  width: 108px; height: 27px;
  background: rgb(201 160 220 / .82);
  box-shadow: 0 2px 6px rgb(34 20 66 / .28);
  transform: rotate(2.6deg);
  z-index: 3;
}

.objective-head { display: flex; align-items: flex-start; gap: .85rem; }
.objective-mark {
  flex: none;
  width: 40px; height: 40px;
  padding: 7px;
  color: var(--crimson);              /* 6.4:1 on the pale disc */
  background: var(--lav-pale);
  border-radius: 52% 48% 46% 54% / 48% 52% 50% 46%;
  box-shadow: 0 0 0 2px rgb(179 32 44 / .3);
  transform: rotate(-7deg);
}
.objective-head-text { min-width: 0; }
/* id selector so the section-level teal/rose swash rules can't out-rank it */
.objective h2#objective-title {
  margin: 0;
  color: var(--deep);                 /* 15.4:1 on cream */
  font-size: clamp(1.1rem, 2.3vw, 1.42rem);
  letter-spacing: .14em;
  text-transform: uppercase;
}
.objective h2#objective-title::after { color: var(--lav); }
.objective-scope {
  margin: .7em 0 0;
  color: var(--ink-soft);             /* 8.1:1 on cream */
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .09em;
  text-transform: uppercase;
}

.objective-text {
  margin: 1.05rem 0 0;
  max-width: 56ch;
  color: var(--ink);                  /* 12.9:1 on cream */
  font-size: clamp(1.02rem, 1.5vw, 1.13rem);
  font-weight: 600;
  line-height: 1.6;
}

.objective-spec {
  display: grid;
  gap: .72rem;
  margin: 1.15rem 0 0;
  padding-top: 1rem;
  border-top: 2px dashed rgb(104 39 146 / .3);
}
.objective-spec > div {
  display: grid;
  grid-template-columns: minmax(7rem, 9rem) 1fr;
  gap: .25rem .95rem;
  align-items: start;
}
.objective-spec dt {
  margin: 0;
  padding-top: .4em;
  color: var(--muted);                /* 5.8:1 on cream */
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.objective-spec dd { margin: 0; color: var(--ink); font-weight: 600; }

.objective-chips, .objective-deliverables {
  display: flex;
  flex-wrap: wrap;
  gap: .4rem;
  margin: 0;
  padding: 0;
  list-style: none;
}
.objective-chips li {
  padding: .26em .8em;
  background: var(--lav-pale);
  color: var(--deep);                 /* 12.4:1 */
  border-radius: 999px;
  box-shadow: 0 2px 0 rgb(34 20 66 / .22);
  font-size: .82rem;
  font-weight: 700;
  transform: rotate(-.7deg);
}
.objective-chips li { display: inline-flex; align-items: center; gap: .4em; }
.objective-chips svg { flex: none; width: 1.15em; height: 1.15em; fill: currentColor; }
.objective-chips li:nth-child(2n) { background: #f6e7fb; transform: rotate(.9deg); }
.objective-deliverables { gap: .3rem 1.1rem; }
.objective-deliverables li {
  position: relative;
  padding-left: 1.4em;
  font-size: .93rem;
}
/* a small teal brush dash as the bullet */
.objective-deliverables li::before {
  content: "";
  position: absolute;
  left: 0; top: .58em;
  width: .78em; height: .4em;
  background-color: currentColor;
  -webkit-mask: var(--swash) no-repeat center / 100% 100%;
  mask: var(--swash) no-repeat center / 100% 100%;
  color: var(--teal);                 /* 3.6:1 non-text marker */
}

.objective-context {
  margin: 1.15rem 0 0;
  padding: .8rem 1rem;
  background: var(--lav-pale);
  border-left: 5px solid var(--accent);
  border-radius: var(--radius-sm);
  color: var(--ink);                  /* 9.2:1 on lavender-pale */
  font-size: .95rem;
  line-height: 1.55;
}
.objective-context strong {
  color: var(--accent);               /* 6.9:1 on lavender-pale */
  font-size: .76rem;
  letter-spacing: .1em;
  text-transform: uppercase;
}

@media (max-width: 720px) {
  .objective { margin-top: 1.3rem; padding: 1.2rem 1.1rem 1.15rem 1.5rem; transform: rotate(-.35deg); }
  /* the dock keeps its tuck under the hero but gives back the wide-band padding,
     so the spec rows still have room on a phone */
  .objective.objective-dock { margin: -.8rem auto 1.8rem; padding: 1.25rem 1.1rem 1.15rem 1.5rem; }
  .objective::before { width: 10px; }
  .objective::after { right: 16px; width: 88px; }
  .objective-spec > div { grid-template-columns: 1fr; gap: .3rem; }
  .objective-spec dt { padding-top: 0; }
}
@media print {
  .objective { transform: none; box-shadow: none; break-inside: avoid; }
  .objective::after { display: none; }
}

/* ==========================================================================
   7 · Real application icons orbiting the avatar
   ========================================================================== */
/* above a hovered collage card (z 9), so a mark is never covered by one */
.app-orbit { position: absolute; inset: 0; pointer-events: none; z-index: 10; }
.app-icon {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: var(--s, 62px);
  height: var(--s, 62px);
  margin: calc(var(--s, 62px) / -2);
  pointer-events: auto;
  display: grid;
  place-items: center;
  background: linear-gradient(150deg, #fffdf7, #f3e9da);
  color: var(--brand, var(--deep));
  border: 0;
  border-radius: 38% 62% 55% 45% / 50% 44% 56% 50%;
  box-shadow: 0 0 0 3px var(--cream), 0 0 0 5px rgb(34 20 66 / .35), 0 10px 22px -8px rgb(34 20 66 / .5);
  transform: rotate(var(--r, 0deg));
  animation: icon-bob 6.5s ease-in-out infinite;
  animation-delay: var(--d, 0s);
  transition: transform .3s cubic-bezier(.22,.9,.24,1), box-shadow .3s;
  cursor: pointer;
}
.app-icon svg { width: 62%; height: 62%; fill: currentColor; }
.app-icon:hover, .app-icon:focus-visible {
  transform: rotate(0deg) scale(1.16);
  box-shadow: 0 0 0 3px var(--cream), 0 0 0 6px var(--brand, var(--deep)), 0 14px 26px -8px rgb(34 20 66 / .55);
  z-index: 6;
}
.app-icon .app-icon-label {
  position: absolute;
  top: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%) rotate(-2deg);
  background: var(--deep);
  color: var(--cream);
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .06em;
  padding: .28em .7em;
  border-radius: 999px;
  white-space: nowrap;
  opacity: 0;
  transition: opacity .25s;
  pointer-events: none;
}
.app-icon:hover .app-icon-label, .app-icon:focus-visible .app-icon-label { opacity: 1; }
@keyframes icon-bob {
  0%, 100% { translate: 0 0; }
  50%      { translate: 0 -12px; }
}

/* restyle the existing skill chips as painted tiles too */
.avatar-tool, .tool-icon {
  border-radius: 38% 62% 55% 45% / 50% 44% 56% 50%;
  box-shadow: 0 0 0 3px var(--cream), 0 6px 16px -6px rgb(34 20 66 / .5);
}

/* ==========================================================================
   8 · Marginalia & stickers — the handmade clutter that says "artist"
   ========================================================================== */
.sticker {
  display: inline-block;
  background: var(--cream);
  color: var(--deep);
  border-radius: 999px;
  padding: .3em .9em;
  font-weight: 700;
  font-size: .8rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  box-shadow: 0 3px 0 rgb(34 20 66 / .35);
  transform: rotate(-2deg);
}
.sticker.alt { background: var(--lav); color: var(--deep); transform: rotate(1.6deg); }
.marginalia { font-style: italic; color: var(--ink-soft); transform: rotate(-1.2deg); }

/* ==========================================================================
   9 · Start a project + pay now (commissions)
   ========================================================================== */
.commission {
  position: relative;
  isolation: isolate;
  background: linear-gradient(140deg, var(--deep), var(--deep-2) 70%, #170d30);
  color: var(--cream-soft);
  border-radius: var(--radius);
  box-shadow: var(--shadow-paint);
  padding: clamp(1.6rem, 4vw, 3rem);
  transform: rotate(-.4deg);
}
.commission h2, .commission h3 { color: var(--cream); }
.commission h2::after { color: var(--lav); }
.commission p, .commission li { color: var(--cream-soft); }
.commission a:not(.btn) { color: var(--lav-pale); }

.commission-grid { display: grid; gap: clamp(1.4rem, 3vw, 2.4rem); grid-template-columns: 1.1fr .9fr; }
@media (max-width: 900px) { .commission-grid { grid-template-columns: 1fr; } }

/* service picker: painted tiles, one per kind of work */
.svc-list { list-style: none; margin: 0; padding: 0; display: grid; gap: .8rem; }
.svc-list li { position: relative; }
.svc-list input[type="radio"], .svc-list input[type="checkbox"] {
  position: absolute;
  opacity: 0;
  width: 100%; height: 100%;
  margin: 0;
  cursor: pointer;
}
.svc-list label {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  background: rgb(255 250 238 / .08);
  border: 2px solid rgb(255 250 238 / .22);
  border-radius: var(--radius-sm);
  padding: .8rem 1rem;
  cursor: pointer;
  color: var(--cream);
  font-weight: 700;
  transition: background .2s, border-color .2s, transform .2s;
}
.svc-list label small { display: block; font-weight: 400; color: var(--cream-soft); }
.svc-list .svc-price { color: #ffe9a8; font-weight: 700; white-space: nowrap; }
.svc-list input:checked + label {
  background: var(--lav-pale);
  color: var(--deep);
  border-color: var(--cream);
  transform: rotate(-.6deg);
}
.svc-list input:checked + label small { color: var(--ink-soft); }
.svc-list input:checked + label .svc-price { color: var(--accent); }
.svc-list input:focus-visible + label { outline: 3px solid var(--lav); outline-offset: 3px; }

/* the running quote card */
.quote-card {
  background: var(--cream);
  color: var(--ink);
  border-radius: var(--radius);
  padding: 1.3rem 1.4rem;
  box-shadow: 0 0 0 4px rgb(34 20 66 / .35), var(--shadow-paint);
  transform: rotate(.6deg);
  position: sticky;
  top: calc(var(--header-h, 76px) + 16px);
}
.quote-card h3 { color: var(--deep); }
.quote-card .quote-total {
  font-size: clamp(1.8rem, 1.2rem + 2vw, 2.6rem);
  font-weight: 700;
  color: var(--deep);
  line-height: 1.1;
}
.quote-card .quote-note { color: var(--ink-soft); font-size: .9rem; }
.quote-card dl { display: grid; grid-template-columns: 1fr auto; gap: .3rem 1rem; margin: 0 0 1rem; }
.quote-card dt { color: var(--ink-soft); }
.quote-card dd { margin: 0; font-weight: 700; color: var(--deep); }

/* pay + call actions */
.pay-row { display: flex; flex-wrap: wrap; gap: .8rem; align-items: center; margin-top: 1rem; }
.btn-pay {
  background: var(--blender);
  color: #241300;                 /* 7.1:1 on the orange */
  font-weight: 700;
  min-height: var(--hit-lg);
  border-radius: var(--r-md);
  padding: .8em 1.5em;
  text-decoration: none;
  box-shadow: 0 5px 0 rgb(34 20 66 / .5);
  display: inline-flex;
  align-items: center;
  gap: .5em;
}
.btn-pay:hover { transform: translateY(-2px); box-shadow: 0 7px 0 rgb(34 20 66 / .5); }
.btn-pay svg { width: 1.1em; height: 1.1em; fill: currentColor; }
.btn-call {
  background: var(--cream);
  color: var(--deep);
  min-height: var(--hit-lg);
  border-radius: var(--r-md);
  padding: .8em 1.4em;
  font-weight: 700;
  text-decoration: none;
  box-shadow: 0 5px 0 rgb(34 20 66 / .5);
  display: inline-flex;
  align-items: center;
  gap: .5em;
}
.btn-call:hover { transform: translateY(-2px); }
.btn-call svg { width: 1.05em; height: 1.05em; fill: currentColor; }

/* FAQ (also emitted as FAQPage schema) */
.faq details {
  background: rgb(255 250 238 / .07);
  border: 1px solid rgb(255 250 238 / .2);
  border-radius: var(--radius-sm);
  padding: .8rem 1rem;
  margin-bottom: .7rem;
}
.faq summary { cursor: pointer; font-weight: 700; color: var(--cream); }
.faq details[open] { background: rgb(255 250 238 / .12); }
.faq p { margin: .6rem 0 0; }

/* ==========================================================================
   10 · Click-to-call: always within reach
   ========================================================================== */
.call-bar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 70;
  display: none;
  background: linear-gradient(95deg, var(--deep), var(--deep-2));
  color: var(--cream);
  padding: .7rem 1rem calc(.7rem + env(safe-area-inset-bottom, 0px));
  box-shadow: 0 -8px 24px -10px rgb(34 20 66 / .6);
}
.call-bar a {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .6em;
  color: var(--cream);
  font-weight: 700;
  text-decoration: none;
  font-size: 1.05rem;
}
.call-bar svg { width: 1.2em; height: 1.2em; fill: currentColor; }
@media (max-width: 860px) {
  .call-bar { display: block; }
  body.nav-open .call-bar { display: none; }   /* never let the bar cover the open menu */
  body { padding-bottom: 64px; }      /* keep footer text clear of the bar */
  .header-call { display: none; }
}

/* ==========================================================================
   11 · Footer: her dark-purple Get In Touch world, expanded
   ========================================================================== */
.site-footer {
  position: relative;
  background: linear-gradient(160deg, var(--deep) 0%, var(--deep-2) 60%, #170d30 100%);
  color: var(--cream-soft);
  margin-top: 3rem;
}
.site-footer::before {
  content: "";
  position: absolute;
  left: 0; right: 0; top: -24px;
  height: 26px;
  background-color: currentColor;
  -webkit-mask: var(--brush-edge) no-repeat center / 100% 100%;
  mask: var(--brush-edge) no-repeat center / 100% 100%;
  color: var(--deep);
  pointer-events: none;
}
.site-footer h2, .site-footer h3 { color: var(--cream); }
.site-footer p, .site-footer li, .site-footer a, .site-footer .muted { color: var(--cream-soft); }
.site-footer a:hover { color: var(--cream); }
.footer-cta {
  background: rgb(255 250 238 / .06);
  border: 1px solid rgb(255 250 238 / .16);
  border-radius: var(--radius);
  transform: rotate(-.4deg);
}
.form input, .form textarea {
  background: var(--cream);
  color: var(--deep);
  border: 0;
  border-radius: var(--radius-sm);
}
.form label { color: var(--cream); font-weight: 700; }
.form-status { color: var(--lav-pale); }
.btn-light { background: var(--lav-pale); color: var(--deep); box-shadow: 0 4px 0 rgb(0 0 0 / .35); }
.footer-bottom { border-top: 1px solid rgb(255 250 238 / .16); }
/* The footer carries no notes about how the page was made — no build-process
   commentary anywhere on the site, ever. maximal.js deletes the node outright;
   this hides it first (no flash) and covers the case where the line is still
   emitted. Swap-time patch: delete the <p class="small">Hand-built…</p> line
   in tools/build.py so it never ships again. */
.footer-bottom > p.small { display: none; }

/* ==========================================================================
   12 · Motion: reveals that feel painted on, not slid in
   ========================================================================== */
html.js-paint .scroll-3d {
  opacity: calc(.15 + .85 * var(--enter, 1));
  transform:
    perspective(900px)
    rotateX(calc((1 - var(--enter, 1)) * 10deg))
    rotateZ(calc((1 - var(--enter, 1)) * var(--rz, -1.4deg)))
    translateY(calc((1 - var(--enter, 1)) * 34px));
}
html.js-paint .scroll-3d.from-right { --rz: 1.4deg; }

/* ==========================================================================
   12b · Pieces that maximal.js injects into the page
   ========================================================================== */
.app-icon, .app-icon .app-icon-label, .header-call, .call-bar a,
.btn-pay, .btn-call, .nav-link-cta, .quote-mail { text-decoration: none; }

/* the rebranded chips swap stroke line-art for solid brand marks */
.tool-icon svg { fill: currentColor; stroke: none; }
.avatar-tool-label { line-height: 1.25; text-align: center; }

/* "Start a project" is the one nav item that should shout */
.nav-link-cta {
  background: var(--lav-pale);
  color: var(--deep);
  font-weight: 700;
  box-shadow: 0 3px 0 rgb(34 20 66 / .4);
}
.nav-link-cta:hover {
  background: var(--cream);
  color: var(--deep);
  transform: translateY(-1px);
}
.nav-commissions { margin-left: .4rem; }

/* quote card: email fallback + deposit line */
.quote-mail {
  display: inline-flex;
  align-items: center;
  gap: .5em;
  margin-top: .7rem;
  color: var(--accent);
  font-weight: 700;
}
.quote-mail:hover { color: var(--deep); }
.quote-deposit-line { color: var(--ink-soft); font-size: .95rem; margin: .2rem 0 0; }
.quote-deposit-line b { color: var(--deep); }

/* ==========================================================================
   12c · The story reel — film windows, and the GoomVille act
   --------------------------------------------------------------------------
   maximal.js regroups /story/ into two animatic acts followed by one GoomVille
   act, and drops a painted film window under each animatic that has no cut
   yet. Every video window on the site gets the same treatment: a cream-framed
   screen at the full width of its column, so a turntable or an animatic reads
   as a cinema window instead of a thumbnail.

   The base sheet caps .video-row figures at 420px — a 1920x1080 turntable in a
   postage stamp. A row holds one film on this site, so let it fill the row;
   if a second film ever joins one, the flex row halves them and both still
   get the frame.
   ========================================================================== */
.video-row .figure { max-width: 100%; }
.video-row .figure video,
.video-row .figure canvas,
.story-act .figure video,
.film-slot .film-screen {
  width: 100%;
  border-radius: var(--r-md);
  corner-shape: var(--corner);
  box-shadow: 0 0 0 8px var(--cream), var(--shadow-paint);
}
.video-row .figure video,
.story-act .figure video { aspect-ratio: 16 / 9; object-fit: contain; background: #000; }

/* the turntable: the model takes the film's place in the same frame */
.video-row .figure canvas { display: block; aspect-ratio: 16 / 9; touch-action: none; cursor: grab; }
.video-row .figure canvas:active { cursor: grabbing; }
.turntable { flex-direction: column; align-items: center; }
/* wireframe next to painted: side by side, stacked on a phone; a still waits in the same frame */
.compare .figure { flex-basis: 320px; }
.compare .figure img { width: 100%; border-radius: var(--r-md); corner-shape: var(--corner);
  box-shadow: 0 0 0 8px var(--cream), var(--shadow-paint); }
/* one model in two worlds: two equal frames that stay next to each other at every width */
.worlds { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(24px, 3vw, 36px); margin-top: 1.4rem; }
.worlds .figure { margin: 0; }
.worlds img, .worlds .film-screen { width: 100%; aspect-ratio: 3 / 4; object-fit: cover; }
.worlds img { border-radius: var(--r-md); corner-shape: var(--corner);
  box-shadow: 0 0 0 8px var(--cream), var(--shadow-paint); }
.worlds .film-screen { padding: 8px; transform: none; }
.worlds .film-screen::before, .worlds .film-screen::after { content: none; }
.turntable .figure { flex: none; width: 100%; }
/* a model with no backdrop of its own stands on deep paint; the look in use wears the ring */
.turntable .figure canvas { background: var(--deep-2); }
.turntable-looks { justify-content: center; }
.turntable-looks .btn[aria-pressed="true"] { outline: 3px solid var(--ochre); outline-offset: 3px; }
.turntable-hint { margin: 0; color: var(--cream); font-weight: 600; }

/* an embedded film (YouTube, Canva) gets the same cream cinema frame; the
   iframe fills it edge to edge */
.film-frame {
  position: relative;
  aspect-ratio: 16 / 9;
  background: #000;
  border-radius: var(--r-md);
  corner-shape: var(--corner);
  box-shadow: 0 0 0 8px var(--cream), var(--shadow-paint);
  overflow: hidden;
}
.film-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* an empty slot is still a window: deep paint, sprocket holes, a hand-drawn
   play ring. Nothing about it says "broken player". */
.film-slot { margin: 1.15rem 0 0; }
.film-screen {
  position: relative;
  aspect-ratio: 16 / 9;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: .5rem;
  padding: clamp(20px, 5vw, 52px) clamp(36px, 8vw, 84px);
  text-align: center;
  background:
    radial-gradient(120% 95% at 50% 0%, rgb(104 39 146 / .5) 0%, transparent 62%),
    linear-gradient(168deg, var(--deep-2) 0%, #170d30 82%);
  border-radius: var(--r-md);
  corner-shape: var(--corner);
  box-shadow: 0 0 0 8px var(--cream), var(--shadow-paint);
  overflow: hidden;
  transform: rotate(-.35deg);
}
.film-screen::before, .film-screen::after {
  content: "";
  position: absolute;
  top: 12px; bottom: 12px;
  width: 20px;
  background: var(--sprockets) repeat-y center top / 20px 30px;
  opacity: .5;
}
.film-screen::before { left: 10px; }
.film-screen::after { right: 10px; }
.film-screen-play {
  width: clamp(46px, 7vw, 66px);
  height: auto;
  color: var(--ochre);
  filter: drop-shadow(0 3px 0 rgb(0 0 0 / .35));
}
.film-screen-title {
  margin: 0;
  color: var(--cream);
  font-size: clamp(1.05rem, 2.2vw, 1.35rem);
  font-weight: 800;
  letter-spacing: .02em;
}
.film-screen-note {
  margin: 0;
  max-width: 36ch;
  color: var(--lav);
  font-size: .92rem;
  font-weight: 600;
  line-height: 1.5;
}

/* the GoomVille act: one heading, the film, then everything painted for it */
.story-act-intro { max-width: 62ch; }
.story-act h3 { margin: 2.4rem 0 1rem; }
.story-act .gallery { margin: 0; }

/* ==========================================================================
   12d · The troupe & the painted portrait
   --------------------------------------------------------------------------
   maximal.js §0 retires the rendered avatar: the home hero is now five of
   Maggie's own characters pinned to the painted band like studio postcards, and
   the About hero is her real gouache self-portrait, in the taped frame the
   stage already had. The model stays on disk — this is a change of composition,
   not a deletion, so a better sculpt can be lit again.

   Each card is placed by custom properties written inline per card: --x/--y
   against the stage box, --w as a share of its width, --rot the angle of the
   pin, --z the stack, --dz its parallax depth, --d the offset of its breathing,
   --pos where its face sits inside the 4:3 crop. Percentages of the stage
   rather than of the viewport, so the whole collage scales with the hero.

   rotate / scale / translate are separate properties on purpose. They compose
   with each other, and none of them overwrites the Ken Burns animation running
   on the <img> inside — a shorthand `transform` would have killed it.
   ========================================================================== */
.troupe { position: absolute; inset: 0; }
.troupe-card {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: var(--w);
  z-index: var(--z);
  margin: 0;
  padding: clamp(6px, 1.1vw, 12px);
  background: var(--cream);
  border-radius: var(--r-sm);
  corner-shape: var(--corner);
  box-shadow: var(--shadow-paint);
  rotate: var(--rot);
  translate: calc(var(--mx, 0) * var(--dz) * 1px) calc(var(--my, 0) * var(--dz) * 1px);
  transition: rotate .45s cubic-bezier(.22,.9,.24,1),
              scale .45s cubic-bezier(.22,.9,.24,1),
              translate .45s cubic-bezier(.22,.9,.24,1),
              box-shadow .45s;
}
.troupe-card img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: var(--ar, 4 / 3);
  object-fit: cover;
  object-position: var(--pos, 50% 38%);
  border-radius: var(--r-xs);
  box-shadow: none;
  transform: none;
  /* the crop drifts a hair, so a still frame still feels like a room with air
     in it; --d staggers the five so they never breathe in step */
  animation: troupe-breathe 16s ease-in-out var(--d, 0s) infinite alternate;
}
.troupe-card--contain { background: #9ed8d3; }
.troupe-card--contain img {
  object-fit: contain;
  animation: none;
}
@keyframes troupe-breathe {
  from { transform: scale(1.05); }
  to   { transform: scale(1.13) translate3d(-1.5%, -1.2%, 0); }
}
/* straightening a postcard you put your finger on */
.troupe-card:hover, .troupe-card:focus-within {
  rotate: 0deg;
  scale: 1.035;
  z-index: 9;
  box-shadow: 0 3px 0 rgb(34 20 66 / .12), 0 22px 44px -14px rgb(34 20 66 / .5);
}

/* The greeting used to hang beside a rendered head, at 70% across and a third
   down. That spot is now the zombie postcard's forehead. The one part of the
   stage that no card reaches and the copy column never grows into is the bottom
   right — the copy is capped at min(46%, 600px) on the left, and every card
   ends above 86% of the box — so the pill sits there, pinned to the collage
   like the label on the back of a frame. */
.home-hero .avatar-greet {
  left: auto;
  right: 6%;
  top: auto;
  bottom: 3.5%;
  max-width: min(34ch, 42%);
  transform: none;
}
.home-hero .avatar-greet.is-on { transform: none; }

/* -- About: the gouache self-portrait in the stage -------------------------
   The button keeps the lightbox main.js already bound to it, so all this does
   is mat it: a cream border, a slight lean, and her face seated high in the
   crop because the gouache is a half-length portrait. */
.about-grid .stage-avatar .stage-fallback { padding: 3%; }
.stage-portrait {
  display: block;
  width: 100%;
  height: 100%;
  padding: clamp(8px, 1.4vw, 14px);
  border: 0;
  background: var(--cream);
  border-radius: var(--r-md);
  corner-shape: var(--corner);
  box-shadow: var(--shadow-paint);
  rotate: -1deg;
  overflow: hidden;
  cursor: zoom-in;
}
.stage-portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 24%;
  border-radius: var(--r-sm);
  box-shadow: none;
  transform: none;
  animation: troupe-breathe 22s ease-in-out infinite alternate;
}
/* the shelf caption, kept with the portrait when the figure moved up into the
   hero — a painting without its line is just a crop */
.stage-caption {
  margin: .8rem 0 0;
  text-align: center;
  color: var(--ink-soft);
  font-size: .92rem;
  font-weight: 600;
}

/* a phone holds three postcards, not five: the two at the back would be
   slivers, so they go, and the remaining three are re-seated to fill the
   shorter, wider box without hanging off it */
@media (max-width: 860px) {
  .troupe-card:nth-child(1), .troupe-card:nth-child(2) { display: none; }
  .troupe-card:nth-child(3) { --x: 56%; --y: 46%; --w: 43%; rotate: 4deg; }
  .troupe-card:nth-child(5) { --x: 27%; --y: 0%; --w: 46%; rotate: -2deg; }
  .troupe-card:nth-child(4) { --x: 1%; --y: 42%; --w: 43%; rotate: -5deg; }
  /* under the collage, in flow, as its caption: at this width the copy column
     is the full hero, so there is no clear corner left to pin the pill into */
  .home-hero .avatar-greet {
    position: relative;
    z-index: 3;
    right: auto;
    bottom: auto;
    max-width: min(34ch, 92%);
    margin: 1.1rem auto 1.9rem;
    text-align: center;
  }
}
@media (prefers-reduced-motion: reduce) {
  .troupe-card, .troupe-card img, .stage-portrait img { animation: none !important; }
  .troupe-card { translate: 0 0; transition: none; }
  .troupe-card img, .stage-portrait img { transform: none; }
}

/* ==========================================================================
   12e · Apple HIG control geometry
   --------------------------------------------------------------------------
   One rule for every control on the site: a fixed corner per size class, a
   44pt hit region, an 11pt type floor, and content that grows by wrapping
   rather than by shrinking or truncating. The radius scale and the reasoning
   behind it live in :root (§ corner scale); this is where it gets applied.
   ========================================================================== */
/* Rows of controls wrap. A row that cannot wrap turns two CTAs into one
   truncated CTA and one off-screen one — the failure HIG's flexible-height
   guidance exists to prevent. */
.btn-row, .avatar-bubble-links, .pay-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .875rem;
}
.btn-row { row-gap: 1rem; }

/* The 44pt floor for the small controls that are not .btn: the nav's chips, the
   call link, the dropdown's rows, the pause and close buttons. Icon-only
   controls get the full square, since a 40px circle is 40px of target in both
   axes and a finger is not. */
.nav-link, .nav-link-cta, .header-call, .subnav a, .pager a,
.avatar-self, .objective-anchor { min-height: var(--hit); }
.avatar-pause { min-height: var(--hit); padding-inline: 1.1rem; }
.avatar-bubble-close {
  width: var(--hit);
  height: var(--hit);
  min-height: var(--hit);
  border-radius: var(--r-circle);
}
.icon-btn { min-width: var(--hit); min-height: var(--hit); }

/* 11pt floor: the base sheet writes the lockup's tagline at 10.5px, the only
   type on the site under the floor. It is a rem token, so it now scales with
   the reader's text-size preference instead of being pinned in pixels. */
.brand-tag { font-size: var(--type-floor); letter-spacing: .14em; }

/* Display type tightens as it grows — the same optical correction Apple applies
   to its large titles — and a headline is balanced so its last line is not a
   single orphaned word. Body copy keeps the ragged-right measure it needs;
   balancing a paragraph makes it harder to read, not easier. */
h1, h2 { letter-spacing: -.022em; }
h1 { text-wrap: balance; }

/* Continuous corners, where the browser has them. `corner-shape: squircle` is
   SwiftUI's RoundedCornerStyle.continuous: the corner curve keeps going past
   the arc's end instead of stopping dead at a tangent, which is most of why
   Apple's rectangles look soft and everyone else's look round. Everywhere else
   the declaration is dropped and the same border-radius renders as a plain
   quarter-circle — correct, just less soft. Only surfaces with a radius of
   their own are listed; the torn-paper --radius slabs are collage, not
   controls, and stay hand-cut. */
@supports (corner-shape: squircle) {
  .btn, .btn-light, .btn-pay, .btn-call, .icon-btn,
  .avatar-self, .avatar-pause, .objective-anchor, .pager a,
  .svc-list label, .faq details,
  .avatar-bubble, .avatar-tool, .tool-icon,
  .project-head, .info-card, .callout, .commission, .quote-card,
  .objective-context, .subnav, .embed, .lightbox-card,
  .nav-link, .nav-link-cta, .header-call, .pill-list li, .sticker,
  .objective-chips li, .app-icon .app-icon-label {
    corner-shape: var(--corner);
  }
}

/* ==========================================================================
   12f · Touch: a label that needs a hover is a label nobody reads
   --------------------------------------------------------------------------
   The base sheet only reveals the tool labels on :hover / :focus-visible, which
   is fine for a pointer and useless on glass — there is no hover on a phone,
   and a tap fires the tool instead of showing its name. Under (hover: none) the
   label becomes part of the chip: static, always painted, on a deep-plum pill
   that keeps cream text at 16.2:1 whether the chip is lit or not.
   ========================================================================== */
@media (hover: none) {
  .avatar-tool { width: auto; min-width: 78px; }
  .avatar-ui .avatar-tool .avatar-tool-label,
  .stage-avatar.is-3d + .avatar-ui .avatar-tool .avatar-tool-label {
    position: static;
    top: auto;
    left: auto;
    display: block;
    max-width: 100%;
    padding: 3px 8px;
    background: var(--deep);
    color: var(--cream);
    border-radius: var(--r-xs);
    corner-shape: var(--corner);
    font-size: .78rem;
    font-weight: 700;
    letter-spacing: .02em;
    line-height: 1.3;
    text-align: center;
    white-space: normal;
    opacity: 1;
    transform: none;
    pointer-events: none;
  }
  /* the orbiting application marks have the same problem: their names only
     appear on hover, so on a phone four icons are four unlabelled blobs */
  .app-icon .app-icon-label { opacity: 1; }
}

/* ==========================================================================
   13 · Responsive + reduced motion
   ========================================================================== */
/* The collage is painted to overshoot the canvas — the marquee's tilted
   corners, drifting specks, orbiting labels all break the edge on purpose.
   The page itself must not grow a scrollbar because of them. clip, not
   hidden: clip doesn't turn the page into a scroll container, so the sticky
   header keeps sticking (and older browsers that lack it simply scroll). */
html, body { overflow-x: clip; }
@media (max-width: 860px) {
  .app-icon { --s: 46px; }
  .app-icon:nth-child(1) { left: 13%; top: 24%; }
  .app-icon:nth-child(2) { left: 88%; top: 28%; }
  .app-icon:nth-child(3) { left: 52%; top: 88%; }
  .stage-fallback::before, .portrait::before { inset: 4% -3% -3% 5%; }
  .paint-marquee { transform: rotate(-1deg) scaleX(1.02); }
  .quote-card { position: static; }
}
@media (prefers-reduced-motion: reduce) {
  .app-icon, .paint-marquee .mq-track, .speck-field i { animation: none !important; }
  html.js-paint h1::after, html.js-paint h2::after {
    -webkit-mask-size: 100% 100% !important;
    mask-size: 100% 100% !important;
    transition: none;
  }
  html.js-paint .scroll-3d { transform: none; opacity: 1; }
  .card-media, .tile img, .figure img { transition: none; }
}

/* ==========================================================================
   13 · Her own paint
   --------------------------------------------------------------------------
   Everything above draws its strokes in CSS. This section swaps them for
   Maggie's real ones: the swooshes and painted bands from her first site, cut
   into /assets/paint/ by tools/paint.py. URLs are relative to this sheet.
   ========================================================================== */
:root { --swash-thick: url("paint/band-lavender.webp"); }

/* The painted purple ground. Measured against ground.webp's single brightest
   pixel (#ab71cf) with the veil and both glows stacked at full strength:
   cream 7.3:1, gold 6.3:1, cream-soft 5.5:1. Lavender, the old quiet tier,
   only reaches 3.4:1 there, so on paint the quiet tier is cream-soft. */
.hero-3d, .page-hero, .site-footer, .project-head {
  --on-deep-muted: var(--cream-soft);
  background:
    radial-gradient(90% 110% at 92% 4%, rgb(47 143 168 / .14), transparent 60%),
    radial-gradient(70% 90% at 62% 112%, rgb(192 98 143 / .14), transparent 62%),
    linear-gradient(rgb(34 20 66 / .62), rgb(34 20 66 / .62)),
    url("paint/ground.webp") center / cover var(--deep);
}

/* heading swashes are a real dry-brush drag now, so they need more height */
h1::after, h2::after { height: .62em; bottom: -.42em; left: -.2em; right: -.3em; }

/* every flat colour slab becomes a brushed block of that colour */
.stage-fallback::before, .portrait::before,
.card::before, .tile::before, .figure::before, .gallery > li::before {
  border-radius: 0;
  -webkit-mask: url("paint/band-purple.webp") center / 100% 100% no-repeat;
  mask: url("paint/band-purple.webp") center / 100% 100% no-repeat;
}

/* her two swooshes cross the hero art, the way they cross her self-portrait */
.troupe::before, .troupe::after, .paint-frame::before, .paint-frame::after {
  content: "";
  position: absolute;
  z-index: 8;
  pointer-events: none;
  background: center / contain no-repeat;
}
.troupe::before, .paint-frame::before {
  background-image: url("paint/swoosh-cream.webp");
  width: 24%;
  aspect-ratio: 318 / 700;
  right: 14%;
  top: -7%;
  rotate: 8deg;
}
.troupe::after, .paint-frame::after {
  background-image: url("paint/swoosh-coral.webp");
  width: 26%;
  aspect-ratio: 435 / 700;
  left: 14%;
  bottom: -6%;
  rotate: -6deg;
}
/* far enough out that it only crosses the corner, not the figure on the right of the picture */
.paint-frame::before { right: -17%; top: -27%; width: 26%; }
.paint-frame::after { left: -12%; bottom: -18%; width: 28%; }

/* -- the painted-in canvas (Work, Story) -----------------------------------
   Five horizontal strokes, each a mask band with its own wet edge, alternate
   left and right. The custom properties are registered so they animate; a
   browser without @property leaves the mask invalid and just swaps pictures. */
@property --p1 { syntax: "<percentage>"; inherits: false; initial-value: 120%; }
@property --p2 { syntax: "<percentage>"; inherits: false; initial-value: 120%; }
@property --p3 { syntax: "<percentage>"; inherits: false; initial-value: 120%; }
@property --p4 { syntax: "<percentage>"; inherits: false; initial-value: 120%; }
@property --p5 { syntax: "<percentage>"; inherits: false; initial-value: 120%; }
.paint-stage .stage-fallback { padding: 9% 6% 9% 4%; }
.paint-frame {
  --mat: clamp(8px, 1.2vw, 14px);
  position: relative;
  width: 100%;
  max-width: 720px;
  aspect-ratio: 4 / 3;
  background: var(--cream);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-paint);
  rotate: -1.6deg;
}
.paint-frame img {
  position: absolute;
  inset: var(--mat);
  width: calc(100% - 2 * var(--mat));
  height: calc(100% - 2 * var(--mat));
  object-fit: cover;
  border-radius: var(--r-xs);
  box-shadow: none;
  transform: none;
  opacity: 0;
}
.paint-frame img.is-under { opacity: 1; z-index: 1; }
.paint-frame img.is-on {
  --strokes:
    linear-gradient(90deg, #000 calc(var(--p1) - 16%), transparent var(--p1)) 0 0 / 100% 21% no-repeat,
    linear-gradient(270deg, #000 calc(var(--p2) - 16%), transparent var(--p2)) 0 25% / 100% 21% no-repeat,
    linear-gradient(90deg, #000 calc(var(--p3) - 16%), transparent var(--p3)) 0 50% / 100% 21% no-repeat,
    linear-gradient(270deg, #000 calc(var(--p4) - 16%), transparent var(--p4)) 0 75% / 100% 21% no-repeat,
    linear-gradient(90deg, #000 calc(var(--p5) - 16%), transparent var(--p5)) 0 100% / 100% 21% no-repeat;
  opacity: 1;
  z-index: 2;
  -webkit-mask: var(--strokes);
  mask: var(--strokes);
  animation: paint-in 1.7s cubic-bezier(.45, 0, .3, 1) both;
}
@keyframes paint-in {
  0%   { --p1: 0%; --p2: 0%; --p3: 0%; --p4: 0%; --p5: 0%; }
  14%  { --p2: 0%; }
  28%  { --p3: 0%; }
  42%  { --p4: 0%; }
  56%  { --p5: 0%; }
  40%  { --p1: 120%; }
  54%  { --p2: 120%; }
  68%  { --p3: 120%; }
  82%  { --p4: 120%; }
  100% { --p1: 120%; --p2: 120%; --p3: 120%; --p4: 120%; --p5: 120%; }
}
.paint-caption {
  position: absolute;
  z-index: 9;
  left: -3%;
  bottom: 7%;
  margin: 0;
  padding: .35em .9em;
  background: var(--lav-pale);
  color: var(--deep) !important;   /* 12.4:1; has to beat the cream hero-ink rule */
  text-shadow: none !important;
  font-weight: 700;
  border-radius: var(--r-xs);
  box-shadow: 0 3px 0 rgb(34 20 66 / .4);
  rotate: -3deg;
}
.paint-next {
  position: absolute;
  z-index: 9;
  right: -3%;
  bottom: 7%;
  width: var(--hit);
  height: var(--hit);
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--r-circle);
  background: var(--lav-pale);
  color: var(--deep);
  box-shadow: 0 3px 0 rgb(34 20 66 / .4);
  cursor: pointer;
}
.paint-next svg { width: 45%; height: 45%; }
.paint-next:hover { background: var(--cream); }
@media (prefers-reduced-motion: reduce) {
  .paint-frame img.is-on { animation: none; -webkit-mask: none; mask: none; }
}
.hero-3d .pill-list { margin-top: 1.1rem; }

/* ==========================================================================
   14 · The painted ground runs under every page
   --------------------------------------------------------------------------
   No paper sections any more: the whole site sits on her purple paint, as her
   first site did. The ground is a fixed layer behind the page (a fixed
   background-attachment is ignored on phones), under the same veil as §13.
   Copy that sits straight on the ground takes the cream family; anything that
   keeps a light surface of its own gets the dark inks back.
   ========================================================================== */
html { background: var(--deep); }
html::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    linear-gradient(rgb(34 20 66 / .62), rgb(34 20 66 / .62)),
    url("paint/ground.webp") center / cover var(--deep);
}
body {
  --ink: var(--cream);
  --ink-soft: var(--cream-soft);
  --muted: var(--cream-soft);
  --accent: var(--lav-pale);
  --line: rgb(233 214 242 / .3);
  --tint: rgb(255 250 238 / .08);
  background: none;
  color: var(--cream);
}
/* light surfaces keep the dark inks they were measured with (see :root) */
.card-body, .objective, .project-gallery > li, .quote-card, .sticker, .app-icon, .pager a,
.form input, .form textarea, .how > li, .pick, .quote-field, .avatar-bubble {
  --ink: #3a2a5e;
  --ink-soft: #55417f;
  --muted: #564a6c;
  --accent: #682792;
  --line: #c9b6da;
}
/* a card's label is one of her pale-lavender tags, not a sheet of paper */
.card-body { background: var(--lav-pale); }
/* headings that used to sit on paper now sit on paint */
.project h2, .project h3, main > .section h2, main > .section h3 { color: var(--cream); }
main :is(.card-body, .objective, .how > li, .quote-card, .pick) :is(h2, h3, h4) { color: var(--deep); }
/* captions under free-standing figures are lavender tags too, so the brushed
   slab behind a figure can never run under bare caption text */
.figure > figcaption {
  --ink: #3a2a5e;
  --ink-soft: #55417f;
  --muted: #564a6c;
  --accent: #682792;
  display: table;
  margin-top: .7rem;
  padding: .3em .8em;
  background: var(--lav-pale);
  color: var(--ink);
  border-radius: var(--r-xs);
  position: relative;
  z-index: 1;
}
.figure > figcaption a { color: var(--accent); }
/* the big film windows go without a slab: it ran under the next heading */
.film-slot::before, .scrolly-steps .figure::before { content: none; }

/* ==========================================================================
   15 · Captions and labels that read at a glance
   --------------------------------------------------------------------------
   The tags passed AA on paper but in a thin, small, muted face. Captions and
   card labels now use the darkest ink at a heavier weight (--deep on
   --lav-pale is 12.4:1; on the cream gallery cards 16:1).
   ========================================================================== */
.figure > figcaption, .gallery figcaption, .project-gallery figcaption {
  color: var(--deep);
  font-size: .95rem;
  font-weight: 600;
  line-height: 1.4;
  opacity: 1;
}
.figure > figcaption a { color: var(--accent); font-weight: 700; }
.card-body p { color: var(--ink); font-weight: 500; }
.stage-caption { color: var(--cream); }
/* on a phone the stage sits straight under the hero copy */
@media (max-width: 860px) {
  .hero-3d > .stage3d { margin-top: 1.25rem; }
}

/* ==========================================================================
   16 · Picture stories
   --------------------------------------------------------------------------
   A project reads left to right without its copy: where it started, a painted
   arrow, what Maggie made. Each step carries a number, an icon and a tag whose
   colour says what it is: paper cream = something drawn (the source), gold =
   her 3D work, lavender = a texture or map. All three take --deep ink
   (cream 16:1, gold 13:1, lavender 12:1).
   ========================================================================== */
:root {
  --arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 60'%3E%3Cpath d='M3 33c20-13 50-17 80-10l-4-15 38 22-36 24 3-16C56 31 30 33 7 43c-3 1-6-8-4-10z'/%3E%3C/svg%3E");
}
.flow {
  --g: clamp(3.4rem, 6vw, 5.5rem);
  list-style: none;
  margin: clamp(1.75rem, 4vw, 3rem) 0 0;
  padding: 0;
  display: grid;
  gap: var(--g);
  align-items: center;
}
.flow + .flow { margin-top: clamp(2.5rem, 6vw, 4.5rem); }
.flow-step { position: relative; margin: 0; padding: 0; }
.flow-tag {
  position: relative;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: .55em;
  margin: 0 0 -.55rem .6rem;
  padding: .4em .95em .4em .45em;
  background: var(--cream);
  color: var(--deep);
  font-weight: 700;
  line-height: 1.2;
  border-radius: var(--r-md);
  box-shadow: 0 3px 0 rgb(34 20 66 / .45);
  rotate: -1.6deg;
}
.flow-3d .flow-tag { background: #ffe9a8; rotate: 1.2deg; }
.flow-map .flow-tag { background: var(--lav-pale); }
.flow-n {
  display: grid;
  place-items: center;
  flex: none;
  width: 1.9em;
  height: 1.9em;
  border-radius: var(--r-circle);
  background: var(--deep);
  color: var(--cream);
}
.flow-tag svg { flex: none; width: 1.35em; height: 1.35em; }
.flow-label small { display: block; font-size: .8em; font-weight: 600; color: #3a2a5e; }
.flow-pics { display: grid; gap: .9rem; }
.flow-pics-2 { grid-template-columns: var(--pc, 1fr); }   /* --pc: each picture's share, so both end level */
.flow-pics .zoom {
  display: block;
  width: 100%;
  padding: clamp(6px, .9vw, 12px);
  border: 0;
  background: var(--cream);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-paint);
  rotate: -1.4deg;
}
.flow-3d .flow-pics .zoom { rotate: .9deg; }
.flow-pics .zoom:nth-child(2) { rotate: 1.6deg; }
.flow-pics .zoom img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--r-xs);
  box-shadow: none;
  transform: none;
}
/* the painted arrow sits in the gap before every step but the first */
.flow-step + .flow-step::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: calc(var(--g) * .78);
  aspect-ratio: 2 / 1;
  translate: calc(-50% - var(--g) / 2) -50%;
  background: #ffe9a8;
  -webkit-mask: var(--arrow) center / contain no-repeat;
  mask: var(--arrow) center / contain no-repeat;
  filter: drop-shadow(0 3px 0 rgb(34 20 66 / .5));
  pointer-events: none;
}
@media (max-width: 860px) {
  .flow { --g: 4.2rem; grid-template-columns: minmax(0, 1fr); }
  .flow-step + .flow-step::before {
    left: 50%;
    top: 0;
    width: 3.2rem;
    translate: -50% calc(-50% - var(--g) / 2);
    rotate: 90deg;
  }
}
.flow-notes { margin-top: clamp(2.5rem, 6vw, 4rem); }
.flow-notes .checks { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 0 2rem; }
.flow-more { margin-top: clamp(2.5rem, 6vw, 4rem); }
.flow-link {
  display: inline-flex;
  align-items: center;
  gap: .4em;
  margin-top: clamp(2rem, 5vw, 3.5rem);
  color: var(--cream);
  font-size: 1.25rem;
  font-weight: 700;
}
.flow-link svg { width: 1em; height: 1em; }
.flow-link + .flow { margin-top: 1.1rem; }

/* the colours a piece is made of, as dabs on the title bar */
.project-head { align-items: center; }
.project-head > .palette {
  flex: 0 0 auto;
  display: flex;
  margin: 0;
  padding: 0 .5rem 0 0;
  list-style: none;
}
.palette li {
  width: clamp(34px, 4.2vw, 56px);
  aspect-ratio: 1;
  margin-left: -10px;
  background: var(--c);
  border: 2px solid rgb(255 250 238 / .9);
  border-radius: 58% 42% 55% 45% / 48% 56% 44% 52%;
  box-shadow: inset 0 -6px 10px rgb(0 0 0 / .2), 0 3px 0 rgb(34 20 66 / .45);
}
.palette li:nth-child(odd) { translate: 0 -6px; rotate: -8deg; }
.palette li:nth-child(even) { translate: 0 5px; rotate: 6deg; }
.flow-tag { font-size: 1.05rem; }
.flow-notes { max-width: none; }
/* a brushed block of colour behind each step's pictures, like the cards have */
.flow-pics { position: relative; isolation: isolate; }
.flow-pics::before {
  content: "";
  position: absolute;
  inset: 6% -4% -6% 6%;
  z-index: -1;
  background: var(--rose);
  -webkit-mask: url("paint/band-purple.webp") center / 100% 100% no-repeat;
  mask: url("paint/band-purple.webp") center / 100% 100% no-repeat;
  rotate: 3deg;
  opacity: .9;
  pointer-events: none;
}
/* the way on to the related pages: big cream buttons on a rose brush band */
.project-links { position: relative; isolation: isolate; width: fit-content; max-width: 100%;
  padding: clamp(1.1rem, 3vw, 1.75rem) clamp(1.25rem, 4vw, 2.5rem); }
.project-links::before {
  content: "";
  position: absolute;
  inset: -8% -2%;
  z-index: -1;
  background: var(--rose);
  -webkit-mask: url("paint/band-purple.webp") center / 100% 100% no-repeat;
  mask: url("paint/band-purple.webp") center / 100% 100% no-repeat;
  rotate: -1.5deg;
  pointer-events: none;
}
.project-links .btn {
  display: inline-flex; align-items: center; gap: .55em;
  min-height: var(--hit-lg); padding: 0 1.6rem;
  background: var(--cream); color: var(--deep);
  font-size: 1.125rem; font-weight: 800;
  box-shadow: 0 5px 0 rgb(34 20 66 / .55);
}
.project-links .btn svg { width: 1em; height: 1em; flex: none; transition: translate .2s; }
.project-links .btn:hover svg { translate: 4px 0; }
@media (max-width: 520px) {   /* a phone: one button per line, text left, arrow right, each on one line */
  .project-links { width: 100%; }
  .project-links .btn { width: 100%; justify-content: space-between; padding: 0 1.1rem; font-size: 1.0625rem; }
}
/* a tall phone hero only shows a slice of the desk: keep the trophy in it */
.project-hero-cubicle .project-hero-media img { object-position: 27% 50%; }
.flow-2d .flow-pics::before { background: var(--teal); inset: 5% 5% -6% -5%; rotate: -3deg; }
.flow-map .flow-pics::before { background: var(--lav); rotate: -2deg; }
.flow-link { display: flex; width: fit-content; }

/* ==========================================================================
   17 · Each project page strokes its headings in its own colours
   --------------------------------------------------------------------------
   Two stops; a page with one colour repeats it. The swash is a mask, so the
   gradient fills the brush drag from left to right.
   ========================================================================== */
main:has(.project-hero-troll)  { --stroke-a: #a56be0; --stroke-b: #a56be0; }
main:has(.project-hero-zombie) { --stroke-a: #2bb8d6; --stroke-b: #2bb8d6; }
main:has(.project-hero-alien)  { --stroke-a: #e56fa8; --stroke-b: #6cbf5a; }
main:has(.project-hero-david)  { --stroke-a: #ef7fb0; --stroke-b: #35bcd8; }
main:has(.project-hero-mira)   { --stroke-a: #f08a2c; --stroke-b: #f08a2c; }
main:has(.project-hero-tiger)  { --stroke-a: #4b92ae; --stroke-b: #4b92ae; }   /* sampled from its eye */
main:has(.project-hero-church) { --stroke-a: #5fae6a; --stroke-b: #5fae6a; }
main:has(.project-hero) :is(h1, h2)::after {
  background: linear-gradient(90deg, var(--stroke-a), var(--stroke-b));
}
/* on the title bar the stroke runs under the small tag line below the title,
   so it is laid down darker there (measured with tools/qa/pixel_audit.py) */
main:has(.project-hero) .project-head h2::after { filter: brightness(.5); }

/* ==========================================================================
   18 · Project heroes keep their subject in frame on very large screens
   --------------------------------------------------------------------------
   The hero used to stop at 800px tall, so a 2560 or 3840 wide window cut the
   art down to a thin strip. It now grows with the window, and each picture
   is anchored on what matters in it.
   ========================================================================== */
@media (min-width: 1700px) {
  .project-hero { min-height: min(84vh, 46vw); }
}
.project-hero-mira .project-hero-media img { object-position: center top; }    /* her hair */
.project-hero-zombie .project-hero-media img { object-position: center 12%; }
/* the two square pictures are shown whole beside the copy instead of cropped */
@media (min-width: 900px) {
  .project-hero-alien .project-hero-media { background: #000; }
  .project-hero-church .project-hero-media {
    background:
      linear-gradient(rgb(34 20 66 / .62), rgb(34 20 66 / .62)),
      url("paint/ground.webp") center / cover;
  }
  .project-hero-alien .project-hero-media img,
  .project-hero-church .project-hero-media img {
    object-fit: contain;
    object-position: 96% center;
    transform: none;
  }
}

/* ==========================================================================
   19 · Drawings and oil paintings: rows that fill the width
   --------------------------------------------------------------------------
   build.py (justified) works out the rows from the real image sizes for three
   widths and gives every picture its share of its row (--a/--b/--c) and the
   number of pictures in that row (--an/--bn/--cn).
   ========================================================================== */
.gallery.justified {
  --gap: clamp(22px, 2.2vw, 44px);
  display: flex;
  flex-wrap: wrap;
  gap: var(--gap);
}
.justified > li {
  --f: var(--a);
  --n: var(--an);
  width: calc((100% - (var(--n) - 1) * var(--gap)) * var(--f) - .5px);
}
.justified .zoom img { display: block; width: 100%; height: auto; }
@media (min-width: 700px) {
  .justified > li { --f: var(--b); --n: var(--bn); }
}
@media (min-width: 1700px) {
  .justified > li { --f: var(--c); --n: var(--cn); }
  #drawings, #paintings { max-width: min(92vw, 3200px); }
}

/* ==========================================================================
   20 · Picture stories, large
   --------------------------------------------------------------------------
   build.py (flow) picks the layout from the picture shapes and passes each
   step its width (--w), a cap at its own pixel size (--mw) and the row's
   column shares (--cols). The story breaks out of the text column so the
   pictures get nearly the whole window.
   ========================================================================== */
@media (min-width: 861px) {
  .flow {
    --wide: min(100vw - 3rem, 1680px);
    width: var(--wide);
    margin-inline: calc((100% - var(--wide)) / 2);
  }
  /* two steps that fit in one row, the second set a little lower */
  .flow-side { grid-template-columns: var(--cols); align-items: start; }
  .flow-side > li:nth-child(2) { margin-top: clamp(1.5rem, 4vw, 4rem); }
  /* a zigzag down the page: odd steps left, even steps right */
  .flow-zig { display: flex; flex-direction: column; gap: clamp(1.5rem, 3vw, 3rem); }
  .flow-zig > li { width: var(--w); max-width: var(--mw); }
  .flow-zig > li:nth-child(even) { align-self: flex-end; }
  /* small steps in a row, then the last one across the page */
  .flow-lead { grid-template-columns: repeat(var(--n), minmax(0, 1fr)); justify-items: center; align-items: start; }
  .flow-lead > li { width: 100%; max-width: var(--mw); }
  .flow-lead > li:last-child { grid-column: 1 / -1; justify-self: end; width: var(--w); }
  /* the arrow drops into the space left of a step that starts a new line */
  .flow-zig > .flow-step + .flow-step::before, .flow-lead > .flow-step:last-child::before {
    left: auto;
    right: calc(100% + 1.25rem);
    top: 10%;
    width: clamp(5rem, 9vw, 9rem);
    translate: none;
    rotate: 24deg;
  }
  /* two wide pictures in one step overlap on the diagonal */
  .flow-pics-fan { display: block; }
  .flow-pics-fan .zoom { width: 68%; }
  .flow-pics-fan .zoom:nth-child(2) { margin: -2% 0 0 32%; }
}

/* Phones: the hero is taller than it is wide, so a wide render shows only a
   third of its width and the copy covers its lower half. Each one is slid
   sideways to put its subject in the clear top part. */
@media (max-width: 860px) {
  .project-hero-tiger .project-hero-media img { object-position: 38% center; }    /* the eye */
  .project-hero-zombie .project-hero-media img { object-position: 10% center; }   /* his head and chest */
  .project-hero-troll .project-hero-media img {                                   /* the left bust, lifted clear of the title */
    object-position: 8% center;
    transform: translateY(-8%);
  }
  .project-hero-david .project-hero-media img { object-fit: cover; object-position: 44% center; }
}

/* -- Home: the demo reel is the hero ---------------------------------------
   Desktop plays the wide cut whole, in its own 16:9, with nothing laid over it:
   the greeting is a tall painted card standing beside the film, the same height
   as it. The film is as big as the window allows while the ribbon and the top
   of Recent Projects stay on screen with it when the page opens (20rem is kept
   for them), and the pair is centred on the painted ground.
   The card's type is sized from its own box, so it fills the strip at any
   window size without spilling: its copy has to fit its height.
   Phones play the upright cut under the greeting, at its own 9:16. */
.hero-reel { display: block; width: 100%; height: auto; aspect-ratio: 9 / 16; margin-top: 1.2rem; }
@media (min-width: 861px) {
  .home-hero {
    --cw: clamp(250px, 22vw, 400px);
    --gap: clamp(3rem, 5vw, 5rem);   /* wide enough that the coral swoosh on the card's right side stays off the film */
    display: grid;
    grid-template-columns: var(--cw) min(100% - var(--cw) - var(--gap), (100svh - var(--header-h) - 20rem) * 16 / 9);
    justify-content: center;
    gap: var(--gap);
    /* room for the swooshes that wrap the card's corners (top, left, bottom); the tilted ribbon
       also rides up over the hero's foot, and must stay off the film and the coral swoosh */
    padding: 3.2rem 3.2rem 4.2rem 4rem;
  }
  .hero-reel {
    grid-area: 1 / 2;
    aspect-ratio: 16 / 9;
    margin: 0;
    border-radius: var(--r-xs);
    box-shadow: 0 0 0 5px var(--cream), var(--shadow-paint);
  }
  .home-hero .hero-3d-inner {
    grid-area: 1 / 1;
    container-type: size;
    display: block;
    width: auto;
    max-width: none;
    min-height: 0;
    margin: 0;
    padding: 0;
  }
  .home-hero .hero-3d-copy {
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    justify-content: center;
    width: auto;
    height: 100%;
    padding: 3em 1.4em;   /* the top and bottom 3em are the brushstrokes' ground: no text goes there */
    font-size: max(10px, min(5cqw, 2.9cqh));   /* 28em of copy when the buttons wrap, and the 6em of padding */
    background:
      radial-gradient(90% 110% at 92% 4%, rgb(47 143 168 / .14), transparent 60%),
      radial-gradient(70% 90% at 30% 112%, rgb(192 98 143 / .14), transparent 62%),
      linear-gradient(rgb(34 20 66 / .62), rgb(34 20 66 / .62)),
      url("paint/ground.webp") center / cover var(--deep);
    border-radius: var(--r-sm);
    box-shadow: 0 0 0 5px var(--cream), var(--shadow-paint);
  }
  .home-hero .hero-3d-copy::before {   /* the brushed slab behind, as the cards have; it stops short of the film */
    content: "";
    inset: -2.5% -4% -2.5% -8%;
    z-index: -1;
    background: var(--rose);
    -webkit-mask: url("paint/band-purple.webp") center / 100% 100% no-repeat;
    mask: url("paint/band-purple.webp") center / 100% 100% no-repeat;
    rotate: -2deg;
  }
  /* Her two swooshes wrap the card's corners from outside, cream round the top left and coral
     round the bottom right. Each picture is a quarter turn of a brush: the cream one is turned
     so its thick end hangs down the card's left side and its tail runs along the top, the coral
     one so its thick end climbs the right side and its tail runs under the bottom. They sit
     outside the card apart from a sliver: the cream reaches 2.6em below the top edge and the
     coral 8em above the bottom one, but both keep to the outer 1em at the sides, clear of
     the text (padding 1.4em). Outside they need 2.6em above and left, 2.8em right and 2.3em
     below, which the hero's padding and the gap to the film leave free, so nothing is cut off.
     The coral one hangs off the button row only because the card's two pseudo-elements are
     taken; it is placed against the card. */
  .home-hero .hero-3d-copy::after, .home-hero .btn-row::after {
    content: "";
    position: absolute;
    z-index: 2;
    background: center / contain no-repeat;
    pointer-events: none;
  }
  .home-hero .hero-3d-copy::after {
    left: 1.15em;
    top: -6.75em;
    width: 6.2em;
    aspect-ratio: 318 / 700;
    background-image: url("paint/swoosh-cream.webp");
    rotate: -90deg;
  }
  .home-hero .btn-row::after {
    right: -2.8em;
    bottom: -2.3em;
    width: 6.4em;
    aspect-ratio: 435 / 700;
    background-image: url("paint/swoosh-coral.webp");
    rotate: 180deg;
  }
  /* the lavender stroke behind the skill pills: it sits behind the pills but in front of the card */
  .home-hero .pill-list { isolation: isolate; }
  .home-hero .eyebrow { font-size: 1em; }
  .home-hero h1 { font-size: 2.45em; }
  .home-hero .lead { margin: 1.9em 0 1.2em; font-size: .95em; }
  .home-hero .btn-row { gap: .6em; }
  .home-hero .btn { min-height: 2.7em; padding: 0 1em; font-size: .9em; }
  .home-hero .pill-list { margin: 1.3em 0 0; gap: .4em; }
  .home-hero .pill-list li { padding: .25em .7em; font-size: .78em; }
}

/* A window that is tall for its width (half a big monitor) has height to spare
   and no width: there the card would squeeze the film, so it lies down as a
   strip under it instead and the film takes the full width. 12rem more is kept
   below the film for the strip. Side by side wins again from about 6:5, and in
   any window under 900px tall. */
@media (min-width: 861px) and (min-height: 900px) and (max-aspect-ratio: 6 / 5) {
  .home-hero { grid-template-columns: min(100%, (100svh - var(--header-h) - 34rem) * 16 / 9); row-gap: 4rem; }
  .hero-reel { grid-area: 1 / 1; }
  .home-hero .hero-3d-inner { grid-area: 2 / 1; container-type: inline-size; }
  .home-hero .hero-3d-copy {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    column-gap: 1.6em;
    height: auto;
    padding: 1em 1.6em 1.6em;
    font-size: max(10px, 1.3cqw);
  }
  .home-hero .eyebrow { grid-area: 1 / 1; align-self: end; margin: 0; }
  .home-hero h1 { grid-area: 2 / 1; align-self: start; margin: 0; }
  .home-hero .lead { grid-area: 1 / 2 / span 2; }
  .home-hero .btn-row { grid-area: 1 / 3; align-self: end; margin: 0 0 .5em; }
  .home-hero .pill-list { grid-area: 2 / 3; align-self: start; max-width: 19em; margin: 0; }
  .home-hero .hero-3d-copy::before { inset: -7% -1.5% -8% -1.5%; rotate: -.8deg; }
  .home-hero .lead { margin: 0; }
}

/* Home: a blue brushstroke under the greeting, at every width. It lies just below the last
   line, not behind it, so no text is read against it. */
.home-hero h1 { isolation: isolate; }
.home-hero h1::after { top: calc(100% + .04em); bottom: auto; height: .48em; left: -.1em; right: 8%; color: var(--teal); }

/* the reel's sound button: a speaker and two words, filled in while the sound is on */
.reel-sound { display: inline-flex; align-items: center; gap: .4em; cursor: pointer; }
.reel-sound svg { width: 1.25em; height: 1.25em; flex: none; }
.reel-sound[aria-pressed="true"] { background: var(--lav-pale); color: var(--deep); }
.reel-sound[hidden] { display: none; }
/* Phones: the film is a screen tall, so the button in the greeting scrolls away while it plays.
   There maximal.js moves it into the top bar beside the menu button, speaker only: it covers no film. */
@media (max-width: 860px) {
  .site-header .reel-sound {
    position: absolute;
    top: calc((var(--header-h) - 2.75rem) / 2);
    right: calc(var(--gutter) + 46px);
    justify-content: center;
    color: var(--on-deep);
    border-color: var(--lav);
    width: 2.75rem;
    height: 2.75rem;
    min-height: 0;
    padding: 0;
    border-radius: 50%;
  }
  .site-header .reel-sound[aria-pressed="true"] { color: var(--deep); }
  .site-header .reel-sound span { display: none; }
}
@media (max-width: 349px) {   /* too narrow for the lockup's tagline and the speaker side by side */
  .site-header:has(.reel-sound) .brand-tag { display: none; }
}

/* The Recent Projects row scrolls sideways only. Its cards' brushed slabs used to overflow it
   downwards, which made the row a vertical scroller too and caught the wheel on the way down
   the page; the padding gives the slabs their room instead. */
.track { overflow-y: hidden; padding-block: 1.4rem 2.6rem; }

/* Story: the heading under the GoomVille film sits clear of the film's brushed slab */
.story-act .figure + h3 { margin-top: 4.8rem; }

/* the drifting specks stay behind every picture and film in a hero */
.page-hero { isolation: isolate; }
.speck-field { z-index: -1; }

/* -- About: the troupe of postcards is the hero. The collage column is the wider one, and her
   two swooshes are big and stand off the pictures: cream out past the top right, coral out
   past the bottom left. ---------------------------------------------------- */
@media (min-width: 861px) {
  .about-grid { grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: clamp(40px, 6vw, 96px); }
  .about-grid .avatar-hero { margin-left: 6%; }
}
.about-grid .troupe::before { right: -21%; top: -21%; width: 30%; rotate: 8deg; }
.about-grid .troupe::after { left: -18%; bottom: -21%; width: 34%; rotate: -6deg; }
@media (max-width: 860px) {
  .about-grid .troupe::before { right: -4%; top: -13%; width: 26%; }
  .about-grid .troupe::after { left: -5%; bottom: -12%; width: 28%; }
  .about-grid .app-icon:nth-child(1) { left: 34%; top: 9%; }
}

/* -- the next project, as a button at the foot of every project page ------ */
.pager .next { background: var(--cream); box-shadow: 0 0 0 2px var(--ochre), 0 4px 0 rgb(34 20 66 / .35); }
.pager .next strong { font-size: 1.2rem; }

/* -- the image viewer: every picture fits the screen with room left for its
   label below and the arrows at its sides --------------------------------- */
.lightbox-stage { padding: 64px 76px 8px; overflow: hidden; }
.lightbox-stage img { width: auto; height: auto; max-width: calc(100vw - 152px); max-height: calc(100dvh - 160px); }
.lightbox-caption { min-height: 88px; padding: 12px 24px 20px; color: var(--cream); font-size: 1.15rem; font-weight: 600; }
@media (max-width: 700px) {
  /* no room at the sides of a phone: the arrows drop to the corners beside the label */
  .lightbox-stage { padding-inline: 12px; }
  .lightbox-stage img { max-width: calc(100vw - 24px); }
  .lightbox-caption { padding-inline: 72px; font-size: 1rem; }
  .lightbox-nav { top: auto; bottom: 20px; transform: none; }
}

/* three notes in a row: the middle one sits in the middle of its column */
@media (min-width: 861px) {
  .flow-notes .checks li:nth-child(2):nth-last-child(2) { justify-self: center; }
}
