/* digitalslatt — own site
   Hero: over the top bar's engraved wavy texture (in light blue, with engraved clouds drifting in
   it), the zombie's dirt pile crumbles and he claws out (see-through clips keyed off flat blue,
   tools/blue_key.py); then a loop that starts on the same frame takes over with only his head
   rocking ~1° (tools/idle_sway.py --cut), and gold-tooth sparkles follow the teeth. A shaded SVG
   thought cloud (tools/cloud_svg.py) floats in with the real <h1> inside.
   Below the header: the pastel tint, paper grain and doodle wallpaper; the Services questionnaire
   (api/questionnaire.php sends it). */

:root {
  --paper: #fbfdf5;
  --ink: #111;
  --zombie: #a3e64f;            /* sampled from the zombie's skin and the grass */
  --focus: #111;                /* the keyboard focus ring: ink, 5:1 or more on paper, every pink, the
                                   brain and the sky (blue fell under 3:1 on the pinks); paper on the dirt */
  --display: "Darumadrop One", "Arial Rounded MT Bold", system-ui, sans-serif;
}

*, *::before, *::after { box-sizing: border-box; }
html { background: var(--paper); color: var(--ink); }
body { margin: 0; font: 1rem/1.5 system-ui, sans-serif; }

/* drifting tint behind everything (the "white" of the page) */
body::before {
  content: "";
  position: fixed;
  inset: -35%;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(closest-side at 22% 30%, #ecfad2, transparent),
    radial-gradient(closest-side at 78% 22%, #fff3cf, transparent),
    radial-gradient(closest-side at 65% 75%, #dff6e6, transparent),
    radial-gradient(closest-side at 30% 80%, #fde7ef, transparent),
    var(--paper);
  animation: tint-drift 26s ease-in-out infinite alternate, tint-hue 19s ease-in-out infinite alternate;
}
/* paper grain over the tint (inline SVG noise, works from file://) */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: 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='.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 .2 0 0 0 0 .18 0 0 0 0 .1 0 0 0 .9 -.3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  opacity: .22;
}
@keyframes tint-drift {
  from { transform: translate(-6%, -4%) rotate(0deg) scale(1); }
  to   { transform: translate(6%, 5%) rotate(14deg) scale(1.12); }
}
@keyframes tint-hue {
  from { filter: hue-rotate(-14deg); }
  to   { filter: hue-rotate(22deg); }
}

.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip {
  position: absolute; left: 1rem; top: -4rem; z-index: 10;
  padding: .6rem 1rem; background: var(--ink); color: #fff; text-decoration: none;
}
.skip:focus { top: 1rem; }
:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }

/* ---------- top bar: engraved-pink strip (fine wavy hatching like an old engraving) ---------- */
:root {
  --pink: #f2a6c2;
  --pink-deep: #c8124f;
  --bar-h: 60px;
}
html { scroll-padding-top: calc(var(--bar-h) + 16px); }
.topbar {
  position: sticky; top: 0; z-index: 20;
  height: var(--bar-h);
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='9'%3E%3Cpath d='M-2 5Q4 2 8 5T18 5' fill='none' stroke='%23c8124f' stroke-width='1' stroke-opacity='.42'/%3E%3C/svg%3E"),
    linear-gradient(#f7c4d6, #ee93b5);
  border-bottom: 0;
}
/* hand-drawn wavy ink edge under the bar */
.topbar::after {
  content: "";
  position: absolute; left: 0; right: 0; top: 100%;
  height: 10px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='44' height='10'%3E%3Cpath d='M0 0H44V3Q33 -3 22 3Q11 9 0 3Z' fill='%23ee93b5'/%3E%3Cpath d='M0 3Q11 9 22 3T44 3' fill='none' stroke='%23111' stroke-width='2.6'/%3E%3C/svg%3E") repeat-x;
  pointer-events: none;
}
.topbar-in {
  max-width: 1600px; height: 100%; margin: 0 auto;
  padding: 0 clamp(12px, 2.5vw, 32px);
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
}
.brand {
  display: flex; align-items: center; gap: .55rem;
  color: var(--ink); text-decoration: none;
}
/* the engraved brain turning on its axis: a pre-rendered 3D turntable strip (48 frames,
   tools/brain_render.html -> tools/brain_sprite.py) played like a flipbook */
.brain-spin {
  width: 44px; height: 44px; flex: none;
  background: url("assets/brain-spin.webp") 0 0 / 4800% 100% no-repeat;
  animation: brain-turn 4.8s steps(48, jump-none) infinite;
}
@keyframes brain-turn { to { background-position: 100% 0; } }
.brand-name { font: 400 26px/1 var(--display); letter-spacing: .01em; }
.menu { list-style: none; margin: 0 0 0 auto; padding: 0; display: flex; align-items: center; gap: clamp(.8rem, 2.4vw, 2rem); }
.menu a {
  display: inline-block; white-space: nowrap;
  font: 400 20px/1 var(--display);
  color: var(--ink); text-decoration: none;
  padding: .45em .1em;
}
.menu a:not(.cta):hover { text-decoration: underline wavy var(--pink-deep) 2px; text-underline-offset: 6px; }
.menu .cta {
  padding: .5em 1em .45em;
  background: var(--zombie);
  border: 2.5px solid var(--ink); border-radius: 999px;
  box-shadow: 2px 3px 0 var(--ink);
  transition: transform .15s, box-shadow .15s;
}
.menu .cta:hover { transform: translate(-1px, -1px) rotate(-2deg); box-shadow: 3px 5px 0 var(--ink); }
.menu .cta:active { transform: translate(1px, 2px); box-shadow: 1px 1px 0 var(--ink); }
.topbar :focus-visible { outline-offset: 4px; border-radius: 6px; }
/* FR / EN: the same page in the other language, a small paper pill at the bar's far end */
.lang {
  flex: none; display: inline-grid; place-items: center; min-width: 44px; min-height: 40px; padding: 0 .55em;
  font: 400 18px/1 var(--display); color: var(--ink); text-decoration: none;
  background: var(--paper); border: 2.5px solid var(--ink); border-radius: 999px; box-shadow: 2px 3px 0 var(--ink);
  transition: transform .15s, box-shadow .15s;
}
.lang:hover { transform: translate(-1px, -1px) rotate(2deg); box-shadow: 3px 5px 0 var(--ink); }
.lang:active { transform: translate(1px, 2px); box-shadow: 1px 1px 0 var(--ink); }

.menu-toggle {
  display: none;
  align-items: center; gap: .45em;
  font: 400 18px/1 var(--display); color: var(--ink);
  background: #fff; border: 2.5px solid var(--ink); border-radius: 999px;
  padding: .45em .85em; box-shadow: 2px 3px 0 var(--ink);
  cursor: pointer;
}
.menu-toggle svg { width: 18px; height: 14px; fill: none; stroke: currentColor; stroke-width: 2.6; stroke-linecap: round; }
@media (max-width: 700px) {
  .brand-name { font-size: 22px; }
  .brain-spin { width: 38px; height: 38px; }
}
/* the menu folds into the Menu button below 820px: the French links ("Prendre rendez-vous")
   need about 740px of bar to sit on one line */
@media (max-width: 820px) {
  .menu-toggle { display: inline-flex; margin-left: auto; }
  .menu {
    display: none;
    position: absolute; left: 0; right: 0; top: 100%;
    flex-direction: column; align-items: stretch; gap: 0;
    padding: 18px 20px 22px;
    background: inherit;
    background: linear-gradient(#ee93b5, #f7c4d6);
    border-bottom: 3px solid var(--ink);
  }
  .menu.open { display: flex; }
  .menu a { font-size: 24px; padding: .5em 0; }
  .menu .cta { text-align: center; margin-top: .4em; }
}

/* pause button for the looping motion (WCAG 2.2.2): small, but a 30px target */
.motion-toggle {
  position: absolute; bottom: .6rem; right: .6rem; z-index: 5;
  width: 30px; height: 30px; padding: 0;
  display: grid; place-items: center;
  color: var(--ink); background: rgb(255 255 255 / .8);
  border: 1.5px solid var(--ink); border-radius: 50%;
  cursor: pointer;
  opacity: .7;
  transition: opacity .2s;
}
.motion-toggle:hover, .motion-toggle:focus-visible { opacity: 1; }
.motion-toggle svg { width: 12px; height: 12px; fill: currentColor; }
.motion-toggle .i-play, .paused .motion-toggle .i-pause { display: none; }
.paused .motion-toggle .i-play { display: block; }

/* ---------- hero stage ---------- */
.hero { position: relative; overflow: hidden; }
.stage {
  position: relative;
  width: 100%;
  max-width: 1600px;
  margin: 0 auto;
  aspect-ratio: 16 / 9;
}
.scene { position: absolute; inset: 0; }
/* .frame is the clips' own box: the sparkles share its % */
.frame { position: absolute; inset: 0; }
.frame video, .frame .still {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  display: block;
}
/* intro on top; when it ends the idle loop (same frame) starts underneath and the intro fades
   off it, so the hand-off is video to video and can't shift colour in any browser */
.rise { z-index: 1; transition: opacity .35s ease-out; }
.hero.settled .rise { opacity: 0; }
/* the idle loop shows the risen zombie, so it stays invisible until the hand-off: the see-through
   intro would otherwise show the head behind the mound from the start */
.js .idle { opacity: 0; }
.hero.settled .idle { opacity: 1; }
/* hidden-until-animated states only apply when app.js runs (html.js, set in <head>) */
.js .still { visibility: hidden; }
.hero.novideo .still { visibility: visible; }
.hero.novideo .rise, .hero.novideo .idle { visibility: hidden; }
.fx { position: absolute; inset: 0; z-index: 2; pointer-events: none; will-change: transform; }
/* ---------- the header's backdrop ----------
   the lawn, the dirt and the zombie are keyed off flat blue (tools/blue_key.py); behind them,
   the bar's engraving (the same fine wavy hatching) in a light sky blue that lightens round the
   zombie. Was pale pink: #f8cfde -> #f2b4cb, lines #c8124f */
.hero.textured {
  background:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='9'%3E%3Cpath d='M-2 5Q4 2 8 5T18 5' fill='none' stroke='%231d5f9e' stroke-width='1' stroke-opacity='.26'/%3E%3C/svg%3E"),
    radial-gradient(ellipse 50% 70% at 60% 58%, rgb(246 252 255 / .85), transparent 75%),
    linear-gradient(#e2f2fb, #c6e4f5);
}
/* engraved clouds drifting across behind the scene (tools/sky_clouds.py): each crosses the whole
   header in 2.5-4 minutes, so it's background motion, not a show. Paused with the pause button;
   placed still for people who turn motion off */
.sky { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
.sky-defs { position: absolute; }
.sky-cloud {
  position: absolute; left: 0; top: var(--top);
  width: var(--w); height: auto; overflow: visible;
  opacity: .72;
  animation: sky-drift var(--secs) linear var(--delay) infinite;
  will-change: transform;
}
@keyframes sky-drift {
  from { transform: translateX(-100%); }
  to   { transform: translateX(100vw); }
}
.stage { z-index: 1; }
@media (max-width: 700px) { .sky-cloud { width: calc(var(--w) * .7); } }
/* phones: the texture fills the stage above the lawn */
@media (max-width: 700px) {
  .textured .stage { background: none; }
}
/* wider than the stage: the lawn's ends fade into the texture instead of stopping square */
@media (min-width: 1601px) {
  .textured .frame {
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent);
    mask-image: linear-gradient(90deg, transparent, #000 5%, #000 95%, transparent);
  }
}


/* gold-tooth sparkles */
.sparkle {
  position: absolute;
  aspect-ratio: 1;
  translate: -50% -50%;
  overflow: visible;
  filter: drop-shadow(0 0 .2em #fff6b8);
}
.js .sparkle { opacity: 0; }
.sparkle path { fill: #fffef5; stroke: #5a3d00; stroke-width: 1.4; stroke-linejoin: round; }
.sp-1 { left: 55.2%; top: 49.4%; width: 1.9%; }
.sp-2 { left: 68.6%; top: 48.6%; width: 1.6%; }
.sp-3 { left: 61.8%; top: 56.2%; width: 1.2%; }
.hero.up .sparkle { animation: twinkle 2.8s .3s ease-in-out infinite; }
.hero.up .sp-2 { animation-delay: 1.3s; }
.hero.up .sp-3 { animation-delay: 2.2s; animation-duration: 3.3s; }
@keyframes twinkle {
  0%, 100% { opacity: 0; transform: scale(.2) rotate(0deg); }
  12%      { opacity: 1; transform: scale(1.1) rotate(25deg); }
  24%      { opacity: 1; transform: scale(.9) rotate(45deg); }
  38%      { opacity: 0; transform: scale(.2) rotate(70deg); }
}

/* ---------- thought cloud, in stage % ---------- */
.thought { position: absolute; inset: 0; pointer-events: none; }
.puff, .bubble { position: absolute; }
.puff { aspect-ratio: 1; }
.puff svg { display: block; width: 100%; height: 100%; overflow: visible; }
.puff-1 { left: 42.3%; top: 21.5%; width: 1.6%; }
.puff-2 { left: 39.8%; top: 25.5%; width: 2.2%; }
.puff-3 { left: 36.5%; top: 30%;   width: 3%; }
.bubble { left: 1.5%; top: 1.5%; width: 36%; aspect-ratio: 600 / 390; }
.float { position: absolute; inset: 0; }
.cloud { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; }
/* soft shadow on the "ground" under the floating cloud */
.cloud-shadow {
  position: absolute; left: 16%; right: 12%; bottom: -14%; height: 12%;
  border-radius: 50%;
  background: radial-gradient(closest-side, rgb(60 72 40 / .2), transparent);
}

/* the heading: Darumadrop One outlines as SVG (tools/letters_svg.py), zombie green with the
   drawing's thick black outline; the words themselves are in the <h1> for screen readers */
.ask {
  position: absolute;
  inset: 25.4% 17.1% 23.8% 19.9%;   /* centred on the cloud's body (~309, 198 of 600 x 390) */
  margin: 0;
  transform: rotate(-3deg);
}
.letters { display: block; width: 100%; height: 100%; overflow: visible; }
.gl { transform-box: fill-box; transform-origin: 50% 60%; }
.gl-shadow { fill: var(--ink); }
.js .gl-shadow, .js .gl-fill { opacity: 0; }
.gl-ink {
  fill: none;
  stroke: var(--ink); stroke-width: 110; stroke-linejoin: round; stroke-linecap: round;
}
.js .gl-ink { stroke-dasharray: 1 1; stroke-dashoffset: 1; }
.gl-fill { fill: var(--zombie); }

/* phones: a taller stage, the clip cropped round the zombie, cloud on top */
@media (max-width: 700px) {
  .stage { aspect-ratio: 3 / 5; background: linear-gradient(#e6fdf0, #c9fadc 34%); }   /* sky continues above the painting */
  /* clip at full height along the bottom, cropped at the sides round the zombie */
  .frame { inset: auto auto 0 -66%; width: 196%; aspect-ratio: 16 / 9; }
  /* only x 33.7-84.7% of it is ever on screen, so the clips, posters and still are that strip
     alone (the -m files: x 640-1632 of 1920), placed back where it sits in the frame */
  .frame video, .frame .still { left: calc(100% / 3); width: calc(100% * 31 / 60); }   /* 640 and 992 of 1920 */
  .bubble { left: 3%; top: 4%; width: 74%; }
  .puff-1 { left: 51.5%; top: 40.2%; width: 3%; }
  .puff-2 { left: 53.5%; top: 37.2%; width: 4%; }
  .puff-3 { left: 56%;   top: 33.5%; width: 5.2%; }
  .cloud-shadow { display: none; }   /* would land on the head */
}

/* ---------- entrance: starts on .up (app.js, as the head settles) ---------- */
/* everything fades and eases in (no hard pops); loops start from rest so nothing snaps */
.puff   { transform-origin: 50% 50%; }
.bubble { transform-origin: 88% 92%; }
.js .puff   { opacity: 0; transform: scale(.35); }
.js .bubble { opacity: 0; transform: translateY(6%) scale(.6); }

.hero.up .puff-1 { animation: appear .4s 0s cubic-bezier(.22, 1, .36, 1) forwards; }
.hero.up .puff-2 { animation: appear .4s .12s cubic-bezier(.22, 1, .36, 1) forwards; }
.hero.up .puff-3 { animation: appear .4s .24s cubic-bezier(.22, 1, .36, 1) forwards; }
.hero.up .bubble { animation: appear .6s .32s cubic-bezier(.22, 1.12, .36, 1) forwards; }
@keyframes appear { to { opacity: 1; transform: none; } }

/* letters ink themselves in like a pen, one after another, then flood with green and float */
.hero.up .gl-ink    { animation: ink .26s calc(.55s + var(--i) * 45ms) cubic-bezier(.45, .05, .55, .95) forwards; }
.hero.up .gl-fill   { animation: flood .18s calc(.72s + var(--i) * 45ms) ease-out forwards; }
.hero.up .gl-shadow { animation: flood .16s calc(.84s + var(--i) * 45ms) ease-out forwards; }
.hero.up .gl { animation: letter-float 2.6s calc(1.5s + var(--i) * 120ms) ease-in-out infinite alternate; }
@keyframes ink   { to { stroke-dashoffset: 0; } }
@keyframes flood { to { opacity: 1; } }
@keyframes letter-float { to { translate: 0 -60px; rotate: var(--tilt, 3deg); } }

/* floating: the cloud drifts up and tilts, its shadow shrinks as it rises; circles bob out of step */
.hero.up .float        { animation: drift 4.2s 1s ease-in-out infinite alternate; }
.hero.up .cloud-shadow { animation: shadow 4.2s 1s ease-in-out infinite alternate; }
.hero.up .puff svg     { animation: drift-s 2.4s .45s ease-in-out infinite alternate; }
.hero.up .puff-2 svg   { animation-delay: .6s; }
.hero.up .puff-3 svg   { animation-delay: .75s; }
@keyframes drift   { to { translate: .8% -4%; rotate: .8deg; } }
@keyframes shadow  { to { scale: .8; opacity: .55; } }
@keyframes drift-s { to { translate: 6% -14%; } }

/* pause button: freeze every loop where it is (letters still finish their entrance) */
.paused .brain-spin,
.paused body::before, .paused .gl,
.paused .sparkle,
.paused .float, .paused .cloud-shadow, .paused .puff svg,
.paused .sky-cloud { animation-play-state: paused !important; }

/* no motion: finished drawing, cloud and sparkles already there, nothing drifts */
@media (prefers-reduced-motion: reduce) {
  body::before, .brain-spin { animation: none; }
  .sky-cloud { animation: none; transform: translateX(var(--still)); }
  .motion-toggle { display: none; }
  .rise, .idle { display: none; }
  .still { visibility: visible !important; }
  .float, .cloud-shadow, .puff svg { animation: none !important; }
  .puff, .bubble { opacity: 1 !important; transform: none !important; animation: none !important; }
  .sparkle { opacity: 1 !important; animation: none !important; }
  .gl, .gl-ink, .gl-fill, .gl-shadow { animation: none !important; }
  .gl-ink { stroke-dashoffset: 0 !important; }
  .gl-fill, .gl-shadow { opacity: 1 !important; }
}


/* ---------- below the header: the same doodles as a repeating wallpaper ---------- */
.below { position: relative; }
.wallpaper { position: absolute; inset: 0; width: 100%; height: 100%; opacity: .42; pointer-events: none; }
.below > main, .below > footer { position: relative; }

/* ---------- sections (placeholders for now) ---------- */
.section { max-width: 1100px; margin: 0 auto; padding: 4.5rem clamp(16px, 4vw, 40px); min-height: 40vh; }
.section h2 {
  margin: 0;
  font: 400 clamp(2rem, 5vw, 3.2rem)/1 var(--display);
  color: var(--zombie);
  -webkit-text-stroke: .1em var(--ink);
  paint-order: stroke fill;
  text-shadow: .03em .06em 0 var(--ink);
}

/* ---------- underground: the header's squiggly texture, in browns ----------
   Warm browns shifting from caramel near the grass to chocolate lower down, with soft patches of
   other browns (reddish clay, olive umber) drifting very slowly under the same wavy hatching as
   the bar and the sky. The top edge is a wavy ink line (like the one under the bar) whose crests
   tuck over the bottom of the header's grass. */
.ground {
  position: relative; z-index: 2;
  background: linear-gradient(#b98457, #9c6640 35%, #7c4c2e 75%, #6a3f26);
}
.ground::before {
  content: ""; position: absolute; left: 0; right: 0; bottom: 100%; height: 14px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='60' height='14'%3E%3Cpath d='M0 7Q15 0 30 7T60 7V14H0Z' fill='%23b98457'/%3E%3Cpath d='M0 7Q15 0 30 7T60 7' fill='none' stroke='%23111' stroke-width='2.6'/%3E%3C/svg%3E") repeat-x;
  pointer-events: none;
}
.ground > .section { position: relative; }   /* the content paints over the dirt */
.dirt { position: absolute; inset: 0; z-index: 0; overflow: hidden; pointer-events: none; }   /* its own layer, under the content */
/* the patches of other browns, drifting; they fade out over the last 220px so the section ends on
   exactly #6a3f26, where About me's blend starts */
.dirt-tint {
  position: absolute; inset: 0; overflow: hidden;
  -webkit-mask-image: linear-gradient(#000 calc(100% - 220px), transparent);
  mask-image: linear-gradient(#000 calc(100% - 220px), transparent);
}
.dirt-tint::before {
  content: ""; position: absolute; inset: -25%;
  background:
    radial-gradient(closest-side at 20% 16%, rgb(196 98 54 / .8), transparent),     /* clay red */
    radial-gradient(closest-side at 70% 22%, rgb(206 156 84 / .75), transparent),   /* caramel */
    radial-gradient(closest-side at 46% 48%, rgb(118 102 54 / .7), transparent),    /* olive umber */
    radial-gradient(closest-side at 86% 64%, rgb(160 72 38 / .75), transparent),    /* rust */
    radial-gradient(closest-side at 16% 76%, rgb(78 44 26 / .8), transparent),      /* chocolate */
    radial-gradient(closest-side at 62% 90%, rgb(186 128 72 / .6), transparent);    /* tan */
  animation: dirt-drift 38s ease-in-out infinite alternate;
}
@keyframes dirt-drift {
  from { transform: translate(-4%, -3%) rotate(0deg); }
  to   { transform: translate(4%, 3%) rotate(6deg); }
}
/* the wavy hatching, same tile as the bar's and the sky's */
.dirt::after {
  content: ""; position: absolute; inset: 0;
  animation: sq-flow 6s linear infinite;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='9'%3E%3Cpath d='M-2 5Q4 2 8 5T18 5' fill='none' stroke='%233a1f0e' stroke-width='1' stroke-opacity='.38'/%3E%3C/svg%3E");
}
/* the squiggles drift one wave (16px) sideways every 6s, endlessly */
@keyframes sq-flow { to { background-position: 16px 0; } }
@keyframes sq-flow-pink { from { background-position: 0 calc(var(--sq-phase, 0px) * -1); } to { background-position: 16px calc(var(--sq-phase, 0px) * -1); } }
/* crumbs of soil drop from under the grass edge now and then: two little bursts, each crumb falls
   ~120px over 1.6s with gravity and fades at the end; the bursts repeat every 7-9.5s */
.crumb {
  position: absolute; z-index: 1; top: 4px; width: 7px; height: 7px; border-radius: 45% 55% 50% 50%;
  background: #6b3a1c; box-shadow: 0 0 0 1.5px #2a1306;
  opacity: 0;
  animation: crumb-fall 8s cubic-bezier(.5, 0, .9, .55) infinite;
}
.c1 { left: 18%; animation-delay: 1.2s; }
.c2 { left: 18.6%; width: 5px; height: 5px; animation-delay: 1.35s; }
.c3 { left: 17.7%; width: 4px; height: 4px; animation-delay: 1.5s; }
.c4 { left: 71%; animation-delay: 5.1s; animation-duration: 9.5s; }
.c5 { left: 71.5%; width: 5px; height: 5px; animation-delay: 5.3s; animation-duration: 9.5s; }
.c6 { left: 70.6%; width: 4px; height: 4px; animation-delay: 5.45s; animation-duration: 9.5s; }
.c7 { left: 44%; width: 5px; height: 5px; animation-delay: 3.6s; animation-duration: 7s; }
@keyframes crumb-fall {
  0%       { opacity: 0; transform: translateY(0); }
  2%       { opacity: 1; }
  16%      { opacity: 1; }
  20%, 100% { opacity: 0; transform: translateY(120px); }
}
.paused .dirt::after, .paused .pinkland::before, .paused .crumb, .paused .care-icon { animation-play-state: paused !important; }
@media (prefers-reduced-motion: reduce) {
  .dirt::after, .pinkland::before, .crumb, .care-icon { animation: none !important; }
  .crumb { opacity: 0; }
  .js .ticks li::before { background: var(--zombie); transform: none; animation: none; }
  .js .tick path, .js .care-icon path { stroke-dashoffset: 0; animation: none; }
  .offer .lead, .offer .ticks, .offer .care { transition: none; }
  .offer .lead:hover, .offer .ticks:hover, .offer .care:hover { transform: none; box-shadow: 4px 5px 0 var(--ink); }
}
/* the header's pause button sits above the wavy edge */
.motion-toggle { bottom: calc(.6rem + 10px); }
.paused .dirt-tint::before { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) { .dirt-tint::before { animation: none; } }

/* ---------- About me: the top bar's pink ----------
   Starts on the brown Services ends on (#6a3f26) and blends over 240px, through #a86a72, into the
   bar's pink (#f7c4d6 -> #ee93b5). The squiggles carry on through the join: the brown ones
   (#3a1f0e at 38%) fade out as the bar's magenta ones (#c8124f at 42%) fade in, and app.js
   shifts them by the Services height so they line up with the lines above (--sq-phase).
   It ends on the bar's wavy ink edge, which his cut-off jeans tuck behind. */
.pinkland {
  position: relative;
  margin-top: -1px;   /* tucks under Services by a pixel, so no hairline shows at the join */
  background: linear-gradient(#6a3f26, #a86a72 120px, #f7c4d6 240px, #ee93b5);
  z-index: 2;
  overflow-x: clip;    /* he waits past the right edge without making the page scroll sideways */
}
.pinkland::before, .pinkland::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background-position: 0 calc(var(--sq-phase, 0px) * -1);
}
.pinkland::before {
  animation: sq-flow-pink 6s linear infinite;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='9'%3E%3Cpath d='M-2 5Q4 2 8 5T18 5' fill='none' stroke='%233a1f0e' stroke-width='1' stroke-opacity='.38'/%3E%3C/svg%3E");
  -webkit-mask-image: linear-gradient(#000, transparent 240px);
  mask-image: linear-gradient(#000, transparent 240px);
}
.pinkland::after {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='9'%3E%3Cpath d='M-2 5Q4 2 8 5T18 5' fill='none' stroke='%23c8124f' stroke-width='1' stroke-opacity='.42'/%3E%3C/svg%3E");
  -webkit-mask-image: linear-gradient(transparent, #000 240px);
  mask-image: linear-gradient(transparent, #000 240px);
}
.pinkland > .section {
  position: relative; z-index: 1; padding-top: 6rem;
  min-height: clamp(380px, 58vh, 620px);
  /* text stays clear of him: his width (+2rem), minus whatever margin the 1100px column already
     has on the right on wide screens */
  padding-right: max(clamp(16px, 4vw, 40px), calc(clamp(240px, 46vh, 460px) * .79 + 2rem - max(0px, (100vw - 1100px) / 2)));
}
/* his zombie self peeking in from the right edge, his cut waist on the section's bottom edge.
   Slides in from past the edge the first time a third of About me is in view (app.js adds
   .peeked): translateX(100%) -> 0 over .9s after .1s, cubic-bezier(.22, 1, .36, 1), then stays */
.peeker {
  position: absolute; right: 0; bottom: 0; z-index: 1;
  height: clamp(240px, 46vh, 460px); width: auto;
  pointer-events: none;
}
.peek-spot { position: absolute; right: 0; bottom: 0; width: 40px; height: clamp(240px, 46vh, 460px); pointer-events: none; }
@media (max-width: 700px) { .peek-spot { height: 240px; } }
.js .peeker { transform: translateX(100%); transition: transform .9s cubic-bezier(.22, 1, .36, 1) .1s; }
.js .peeked .peeker { transform: none; }
@media (prefers-reduced-motion: reduce) { .js .peeker { transform: none; transition: none; } }
/* ---------- About me: a timeline, then a card ----------
   "How I work" is drawn straight on the pink: numbered circles joined by a dashed ink line
   (across on wide screens, down on narrow ones). "Why work with me" is the Services tick panel,
   on its own. Entrances play the first time each is well in view (app.js adds .on):
   - steps rise in: 18px below + transparent -> in place, .5s, 150ms apart, each number popping
     (.6 -> 1.15 -> 1) as it lands, its dashed line drawing on to the next (.4s)
   - ticks: the same tick-in as Services */
.about-body { max-width: 900px; margin-top: 1.6rem; }
.steps { list-style: none; margin: .4rem 0 1.4rem; padding: 0; counter-reset: step; display: grid; gap: 1.4rem; }
.steps li { counter-increment: step; position: relative; padding-left: 3.1rem; min-height: 2.4rem; }
.steps li::before {
  content: counter(step); position: absolute; left: 0; top: -.15rem; z-index: 1;
  display: grid; place-items: center; width: 2.3rem; height: 2.3rem;
  font: 400 1.2rem/1 var(--display); background: var(--zombie);
  border: 2.5px solid var(--ink); border-radius: 50%; box-shadow: 2px 3px 0 var(--ink);
}
/* the dashed line to the next step: down on narrow screens */
.steps li:not(:last-child)::after {
  content: ""; position: absolute; left: 1.15rem; top: 2.3rem; bottom: -1.4rem;
  border-left: 2.5px dashed var(--ink); transform-origin: top;
}
.steps h4 { margin: 0 0 .25rem; font: 400 1.25rem/1.2 var(--display); }
.steps p { margin: 0; line-height: 1.5; max-width: 34ch; }
/* across on wide screens: four columns, numbers on top, the line running between them */
@media (min-width: 900px) {
  .steps { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1.2rem; }
  .steps li { padding: 3.2rem 0 0; }
  .steps li:not(:last-child)::after { left: 2.5rem; right: -1.2rem; top: 1.05rem; bottom: auto; border-left: 0; border-top: 2.5px dashed var(--ink); transform-origin: left; }
}
.steps li:nth-child(1) { --n: 0; } .steps li:nth-child(2) { --n: 1; } .steps li:nth-child(3) { --n: 2; } .steps li:nth-child(4) { --n: 3; }
.js .steps li { opacity: 0; translate: 0 18px; }
.js .steps li::before { scale: .6; }
.js .steps li::after { scale: 0; }
.steps.on li { animation: rise-in .5s calc(var(--n) * 150ms) cubic-bezier(.22, 1.3, .36, 1) forwards; }
.steps.on li::before { animation: num-pop .35s calc(var(--n) * 150ms + .3s) cubic-bezier(.3, 1.6, .5, 1) forwards; }
.steps.on li::after { animation: line-draw .4s calc(var(--n) * 150ms + .45s) ease-out forwards; }
@keyframes rise-in { to { opacity: 1; translate: 0 0; } }
@keyframes num-pop { 60% { scale: 1.15; } to { scale: 1; } }
@keyframes line-draw { to { scale: 1; } }
/* "free demo website": a zombie-green highlighter swipe (black text on green: 12:1). It draws
   across the words once the step has landed (.6s after it, .5s); without JS it's simply there */
.mark {
  padding: .05em .3em; margin: 0 -.1em; border-radius: .35em .2em .4em .25em;
  font-weight: 700; color: var(--ink); white-space: nowrap;   /* one piece, never split over two lines */
  background: linear-gradient(var(--zombie), var(--zombie)) left center / 100% 100% no-repeat;
  box-decoration-break: clone; -webkit-box-decoration-break: clone;
}
.js .steps .mark { background-size: 0% 100%; }
.steps.on .mark { animation: mark-swipe .5s calc(var(--n) * 150ms + .6s) cubic-bezier(.4, 0, .2, 1) forwards; }
@keyframes mark-swipe { to { background-size: 100% 100%; } }
@media (prefers-reduced-motion: reduce) { .js .steps .mark { background-size: 100% 100%; animation: none; } }
.why { max-width: 640px; }
.why strong { font-weight: 700; }
@media (prefers-reduced-motion: reduce) {
  .js .steps li, .js .steps li::before, .js .steps li::after { opacity: 1; translate: none; scale: 1; animation: none !important; }
}

/* animated version (app.js adds .peek-video): two see-through clips in his exact box. The still
   stays hidden (it's the fallback); the lean-out shows while it plays, then the wave loop takes
   over (.peek-waving) */
.peeker-vid {
  position: absolute; right: 0; bottom: 0; z-index: 1;
  height: clamp(240px, 46vh, 460px); width: auto; aspect-ratio: 710 / 900;
  pointer-events: none; opacity: 0;
}
.peek-video .peeker { visibility: hidden; transform: none; transition: none; }
/* without a lean-out clip the waving clip slides in like the still (same values) */
.peek-video .peeker-wave { transform: translateX(100%); transition: transform .9s cubic-bezier(.22, 1, .36, 1) .1s; }
.peek-video.peeked .peeker-wave { transform: none; }
.peek-leaning .peeker-in { opacity: 1; }
.peek-waving .peeker-wave { opacity: 1; }
.peek-waving .peeker-in { opacity: 0; }
@media (max-width: 700px) { .peeker-vid { height: 240px; } }

/* rip:start (tools/rip_edge.py) */
:root {
  --rip-w: 560px; --rip-h: 44px;
  --rip-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='560' height='44' viewBox='0 0 560 44'%3E%3Cpath d='M0 18.0 L12.3 18.7 L24.9 18.8 L33.4 19.0 L50.5 14.1 L57.8 17.8 L64.4 23.6 L79.5 25.0 L85.8 14.8 L100.2 18.1 L114.4 14.5 L124.5 17.2 L138.3 30 L147.5 18.9 L165.3 17.5 L183.2 11.5 L201.9 11.2 L213.5 6.0 L230.4 10.3 L242.1 15.4 L258.0 9.5 L270.9 11.7 L284.0 17.5 L295.8 16.1 L315.7 10.2 L335.4 11.3 L341.6 7.5 L356.1 6 L365.7 9.3 L375.8 6 L387.0 7.5 L401.2 11.5 L412.6 13.0 L421.8 14.3 L430.0 15.9 L445.6 14.5 L452.7 17.0 L468.6 14.1 L483.0 11.6 L499.0 6.5 L512.9 10.7 L522.8 15.7 L529.0 12.9 L535.9 9.0 L560 18.0 L560 0 L0 0 Z' fill='black'/%3E%3C/svg%3E");
  --rip-line: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='560' height='44' viewBox='0 0 560 44'%3E%3Cpath d='M0 18.0 L12.3 18.7 L24.9 18.8 L33.4 19.0 L50.5 14.1 L57.8 17.8 L64.4 23.6 L79.5 25.0 L85.8 14.8 L100.2 18.1 L114.4 14.5 L124.5 17.2 L138.3 30 L147.5 18.9 L165.3 17.5 L183.2 11.5 L201.9 11.2 L213.5 6.0 L230.4 10.3 L242.1 15.4 L258.0 9.5 L270.9 11.7 L284.0 17.5 L295.8 16.1 L315.7 10.2 L335.4 11.3 L341.6 7.5 L356.1 6 L365.7 9.3 L375.8 6 L387.0 7.5 L401.2 11.5 L412.6 13.0 L421.8 14.3 L430.0 15.9 L445.6 14.5 L452.7 17.0 L468.6 14.1 L483.0 11.6 L499.0 6.5 L512.9 10.7 L522.8 15.7 L529.0 12.9 L535.9 9.0 L560 18.0' transform='translate%280 -2.6%29' fill='none' stroke='%23c8124f' stroke-opacity='.45' stroke-width='3' stroke-linejoin='round'/%3E%3Cpath d='M0 18.0 L12.3 18.7 L24.9 18.8 L33.4 19.0 L50.5 14.1 L57.8 17.8 L64.4 23.6 L79.5 25.0 L85.8 14.8 L100.2 18.1 L114.4 14.5 L124.5 17.2 L138.3 30 L147.5 18.9 L165.3 17.5 L183.2 11.5 L201.9 11.2 L213.5 6.0 L230.4 10.3 L242.1 15.4 L258.0 9.5 L270.9 11.7 L284.0 17.5 L295.8 16.1 L315.7 10.2 L335.4 11.3 L341.6 7.5 L356.1 6 L365.7 9.3 L375.8 6 L387.0 7.5 L401.2 11.5 L412.6 13.0 L421.8 14.3 L430.0 15.9 L445.6 14.5 L452.7 17.0 L468.6 14.1 L483.0 11.6 L499.0 6.5 L512.9 10.7 L522.8 15.7 L529.0 12.9 L535.9 9.0 L560 18.0' fill='none' stroke='%23111' stroke-width='2.8' stroke-linejoin='round' stroke-linecap='round'/%3E%3C/svg%3E");
  --rip-shade: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='560' height='44' viewBox='0 0 560 44'%3E%3Cpath d='M0 18.0 L12.3 18.7 L24.9 18.8 L33.4 19.0 L50.5 14.1 L57.8 17.8 L64.4 23.6 L79.5 25.0 L85.8 14.8 L100.2 18.1 L114.4 14.5 L124.5 17.2 L138.3 30 L147.5 18.9 L165.3 17.5 L183.2 11.5 L201.9 11.2 L213.5 6.0 L230.4 10.3 L242.1 15.4 L258.0 9.5 L270.9 11.7 L284.0 17.5 L295.8 16.1 L315.7 10.2 L335.4 11.3 L341.6 7.5 L356.1 6 L365.7 9.3 L375.8 6 L387.0 7.5 L401.2 11.5 L412.6 13.0 L421.8 14.3 L430.0 15.9 L445.6 14.5 L452.7 17.0 L468.6 14.1 L483.0 11.6 L499.0 6.5 L512.9 10.7 L522.8 15.7 L529.0 12.9 L535.9 9.0 L560 18.0' transform='translate%280 6%29' fill='none' stroke='%23000' stroke-opacity='.2' stroke-width='10' stroke-linejoin='round'/%3E%3C/svg%3E");
}
/* rip:end */

/* About me's bottom is torn open: the pink is cut along a ragged rip (mask), its ink line drawn
   over the join, and the torn lip casts a soft shadow on the brain below */
.pinkland {
  -webkit-mask: var(--rip-mask) left bottom / var(--rip-w) var(--rip-h) repeat-x,
                linear-gradient(#000, #000) left top / 100% calc(100% - var(--rip-h) + 1px) no-repeat;
          mask: var(--rip-mask) left bottom / var(--rip-w) var(--rip-h) repeat-x,
                linear-gradient(#000, #000) left top / 100% calc(100% - var(--rip-h) + 1px) no-repeat;
}
.rip-line {
  position: relative; z-index: 3; height: var(--rip-h); margin-top: calc(var(--rip-h) * -1);
  background: var(--rip-line) left top / var(--rip-w) var(--rip-h) repeat-x,
              var(--rip-shade) left top / var(--rip-w) var(--rip-h) repeat-x;
  pointer-events: none;
}

/* ---------- Book a meeting and the footer: brain folds ----------
   One layer behind both (app.js sets --brain-top to just above the rip). The tile repeats with no
   seam; its highlights sit on their own layer and pulse softly: 0 -> 1 -> 0 over 4.4s. Without
   JS the sections get the folds directly. */
.brain-bg {
  position: absolute; left: 0; right: 0; top: var(--brain-top, 100%); bottom: 0;
  background: #e8a3a6 url("assets/brain-tile.webp") 0 0 / var(--brain-size, 3600px) repeat;
  pointer-events: none;
}
.brain-hi {
  position: absolute; inset: 0;
  background: url("assets/brain-hi.webp") 0 0 / var(--brain-size, 3600px) repeat;
  opacity: 0; animation: brain-pulse 4.4s ease-in-out infinite;
}
@keyframes brain-pulse { 50% { opacity: 1; } }
@media (max-width: 700px) { .brain-bg { --brain-size: 2200px; } }   /* folds ~75px on phones */
.paused .brain-hi { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) { .brain-hi { animation: none; opacity: .5; } }
html:not(.js) #book, html:not(.js) .foot { background: #e8a3a6 url("assets/brain-tile.webp") 0 0 / 3600px repeat; }
/* the footer's link sits on a paper label over the folds */
.foot-links { padding: .35em .9em; background: var(--paper); border: 2px solid var(--ink); border-radius: 8px; box-shadow: 2px 3px 0 var(--ink); }

/* ---------- Book a meeting: contact ----------
   A paper card on the folds (free quote and demo, the email), and beside it the TikTok and
   Instagram stickers floating: each bobs 10px and tilts +/-4deg over 3.4s, out of step, with its
   shadow on the folds shrinking as it rises. */
.contact {
  max-width: 1000px; margin-top: 1.6rem;
  display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2rem 3.5rem; align-items: center;
}
.contact-card {
  max-width: 620px; padding: 1.3rem 1.4rem 1.4rem;
  background: var(--paper); border: 2.5px solid var(--ink); border-radius: 8px; box-shadow: 4px 5px 0 var(--ink);
}
.contact-card p { margin: 0 0 1rem; line-height: 1.55; }
.contact-lead { font: 400 clamp(1.55rem, 3.6vw, 2.25rem)/1.25 var(--display); }

/* "free demo" in the teeth's gold: the text itself is the thick black outline; a copy laid exactly
   over it (::after) is filled with the gold, top-lit like the teeth, and a white glint sweeps
   across it every 3.6s; a warm glow breathes behind it (::before) and sparkles twinkle round it */
.gold {
  position: relative; isolation: isolate; display: inline-block; white-space: nowrap;
  font-size: 1.3em; color: var(--ink); -webkit-text-stroke: .15em var(--ink);
  text-shadow: .04em .07em 0 var(--ink);
}
.gold::after {
  content: attr(data-text); content: attr(data-text) / "";
  position: absolute; left: 0; top: 0;
  -webkit-text-stroke: 0; color: transparent; text-shadow: none;
  background:
    linear-gradient(105deg, transparent 42%, rgb(255 255 255 / .95) 50%, transparent 58%) 100% 0 / 250% 100% no-repeat,
    linear-gradient(#fffbe2, #ffe55e 26%, #fccb1d 50%, #f0a70f 80%, #ffd84a);
  -webkit-background-clip: text; background-clip: text;
  animation: gold-glint 3.6s 1s ease-in-out infinite;
}
.gold::before {
  content: ""; position: absolute; inset: -.45em -.5em; z-index: -1;
  background: radial-gradient(closest-side, rgb(255 214 60 / .8), rgb(255 230 120 / .35) 55%, rgb(255 230 120 / 0));
  animation: gold-glow 2.4s ease-in-out infinite alternate;
}
@keyframes gold-glint { 0% { background-position: 100% 0, 0 0; } 30%, 100% { background-position: 0% 0, 0 0; } }
@keyframes gold-glow { from { opacity: .45; scale: .94; } to { opacity: 1; scale: 1.04; } }
.gold .sparkle { -webkit-text-stroke: 0; text-shadow: none; filter: drop-shadow(0 0 .12em #ffd23a); }
.gold .sparkle path { fill: #fffbe2; stroke: #6b4300; }
.gs-1 { left: 2%;  top: 8%;  width: .5em; }
.gs-2 { left: 97%; top: 18%; width: .4em; }
.gs-3 { left: 60%; top: 96%; width: .32em; }
.gs   { animation: twinkle 2.8s .4s ease-in-out infinite; }
.gs-2 { animation-delay: 1.4s; }
.gs-3 { animation-delay: 2.2s; animation-duration: 3.3s; }
.paused .gold::before, .paused .gold::after { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) {
  .gold::after { animation: none; }
  .gold::before { animation: none; opacity: .8; }
}
@media (forced-colors: active) {
  .gold { -webkit-text-stroke: 0; }
  .gold::before, .gold::after, .gold .sparkle { display: none; }
}

/* the email: a big link with an envelope, and a Copy button (app.js shows it) for people
   without a mail app set up */
.email-row { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem .8rem; }
.email {
  display: inline-flex; align-items: center; gap: .6em; min-height: 48px; padding: .45em .9em;
  font-weight: 700; font-size: 1.08rem; color: var(--ink); text-decoration: none; overflow-wrap: anywhere;
  background: var(--zombie); border: 2.5px solid var(--ink); border-radius: 8px; box-shadow: 3px 4px 0 var(--ink);
  transition: transform .15s ease-out, box-shadow .15s ease-out;
}
.email span { text-decoration: underline; text-underline-offset: .18em; text-decoration-thickness: 1.5px; }
.email-icon { width: 1.5em; height: 1.5em; flex: none; overflow: visible; }
.email-icon path { fill: #fff; stroke: var(--ink); stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; }
.copy {
  min-height: 44px; padding: .35em .9em; font: 700 .95rem system-ui, sans-serif; color: var(--ink); cursor: pointer;
  background: #fff; border: 2px solid var(--ink); border-radius: 8px; box-shadow: 2px 3px 0 var(--ink);
  transition: transform .15s ease-out, box-shadow .15s ease-out;
}
.email:hover, .copy:hover { transform: translate(-1px, -2px) rotate(-1deg); box-shadow: 4px 6px 0 var(--ink); }
.email:active, .copy:active { transform: translate(1px, 2px); box-shadow: 1px 1px 0 var(--ink); }
.copied { font-weight: 700; }

/* the floating stickers */
.find-me { display: grid; justify-items: center; gap: 1.4rem; }
.find-h {
  margin: 0; max-width: 16em; padding: .35em .8em; text-align: center;
  background: var(--paper); border: 2px solid var(--ink); border-radius: 8px; box-shadow: 2px 3px 0 var(--ink);
}
.find-h strong { white-space: nowrap; }
.socials { list-style: none; margin: 0; padding: 0 0 1.2rem; display: flex; gap: 1.8rem; }
.socials li { position: relative; }
/* the shadow each sticker casts on the folds below it */
.socials li::after {
  content: ""; position: absolute; left: 12%; right: 12%; top: calc(100% + 10px); height: 12px;
  border-radius: 50%; background: radial-gradient(closest-side, rgb(60 10 20 / .35), transparent);
  animation: social-shadow 3.4s ease-in-out infinite alternate;
}
.social {
  display: grid; place-items: center; width: 84px; height: 84px; padding: 18px;
  border: 2.5px solid var(--ink); border-radius: 24px; box-shadow: 4px 5px 0 var(--ink);
  animation: social-bob 3.4s ease-in-out infinite alternate;
  transition: scale .15s ease-out;
}
.social svg { display: block; width: 100%; height: 100%; overflow: visible; }
.socials li:nth-child(2) .social, .socials li:nth-child(2)::after { animation-delay: -1.7s; }
.social:hover { scale: 1.1; }
.social:focus-visible { outline: 3px solid var(--focus); outline-offset: 5px; }
@keyframes social-bob { from { translate: 0 0; rotate: -4deg; } to { translate: 0 -10px; rotate: 4deg; } }
@keyframes social-shadow { to { scale: .75; opacity: .55; } }
.tiktok { background: var(--ink); }
.tt-c { fill: #25f4ee; } .tt-r { fill: #fe2c55; } .tt-w { fill: #fff; }
.insta { background: radial-gradient(circle at 30% 107%, #fdf497 0%, #fdf497 5%, #fd5949 45%, #d6249f 60%, #285aeb 90%); }
.insta path { fill: #fff; }
.paused .social, .paused .socials li::after { animation-play-state: paused; }
@media (prefers-reduced-motion: reduce) { .social, .socials li::after { animation: none; } }
@media (max-width: 760px) {
  .contact { grid-template-columns: minmax(0, 1fr); }
  .contact-card { padding: 1.1rem 1.1rem 1.2rem; }
}

.pinkland { overflow-y: visible; }
@media (max-width: 700px) {
  .pinkland > .section { padding-right: clamp(16px, 4vw, 40px); padding-bottom: 250px; min-height: 0; }
  .peeker { height: 240px; }
}

/* small text over the dirt goes on paper labels so it stays readable */
.ground .fine, .ground .step-count, .ground .status {
  width: fit-content; max-width: 100%;
  padding: .4em .8em;
  background: var(--paper); border: 2px solid var(--ink); border-radius: 14px;
}
.ground .step-count { margin-bottom: -.4rem; }
.ground .status:empty { display: none; }

/* ---------- services: what I offer ----------
   Paper panels with the questionnaire's square-ish ink border, on the brown squiggles. The intro and
   sub-headings sit on paper labels so they read over the dirt. */
.offer { max-width: 1000px; margin-top: 1.6rem; display: grid; gap: 1.1rem; }
.offer .lead {
  margin: 0; max-width: 62ch; padding: 1rem 1.2rem;
  font-size: 1.15rem; line-height: 1.55;
  background: var(--paper); border: 2.5px solid var(--ink); border-radius: 8px; box-shadow: 4px 5px 0 var(--ink);
}
.offer .sub {
  width: fit-content; margin: 1.2rem 0 0; padding: .3em .7em .25em;
  font: 400 clamp(1.3rem, 3vw, 1.7rem)/1.15 var(--display);
  background: var(--zombie); border: 2.5px solid var(--ink); border-radius: 8px; box-shadow: 3px 4px 0 var(--ink);
}
.ticks {
  list-style: none; margin: 0; padding: 1rem 1.2rem; display: grid; gap: .5rem;
  background: var(--paper); border: 2.5px solid var(--ink); border-radius: 8px; box-shadow: 4px 5px 0 var(--ink);
}
.ticks li { position: relative; padding-left: 1.7em; line-height: 1.45; }
.ticks li::before {
  content: ""; position: absolute; left: 0; top: .2em; width: 1.05em; height: 1.05em; box-sizing: border-box;
  background: var(--zombie); border: 2px solid var(--ink); border-radius: 4px;
}
.tick { position: absolute; left: 0; top: .2em; width: 1.05em; height: 1.05em; overflow: visible; }
.tick path { fill: none; stroke: var(--ink); stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; }
/* each box pops and gets ticked in turn when the list scrolls into view (app.js adds .on):
   box scale .6 -> 1.1 -> 1 over .3s, then the tick draws over .35s; 150ms between rows */
.js .ticks li::before { background: #fff; transform: scale(.6); }
.js .tick path { stroke-dasharray: 1 1; stroke-dashoffset: 1; }
.ticks.on li::before { animation: box-pop .3s calc(var(--n) * 150ms) cubic-bezier(.3, 1.6, .5, 1) forwards; }
.ticks.on .tick path { animation: ink .35s calc(var(--n) * 150ms + .22s) ease-out forwards; }
.ticks li:nth-child(1) { --n: 0; } .ticks li:nth-child(2) { --n: 1; } .ticks li:nth-child(3) { --n: 2; }
.ticks li:nth-child(4) { --n: 3; } .ticks li:nth-child(5) { --n: 4; }
@keyframes box-pop { 60% { transform: scale(1.1); background: var(--zombie); } to { transform: none; background: var(--zombie); } }
/* the worm (app.js): sticks up through the intro panel's top border, near its right end.
   viewBox y=0 is where he comes out; it sits on the border's centre line (1.25px above the
   padding box), so the svg's bottom is 10 - 1.25 = 8.75px below that */
.offer .lead { position: relative; }
.worm-svg {
  position: absolute; right: 14%; bottom: calc(100% - 8.75px); z-index: 2;
  width: 90px; height: 110px; translate: 50% 0; overflow: visible; pointer-events: none;
}
.w-hole { fill: #2a1306; stroke: var(--ink); stroke-width: 1.2; }
.w-ink, .w-body, .w-bands, .w-shine { fill: none; stroke-linecap: round; stroke-linejoin: round; }
.w-ink   { stroke: var(--ink); stroke-width: 12; }
.w-body  { stroke: #f59bb4; stroke-width: 8; }
.w-bands { stroke: #d9708f; stroke-width: 8; stroke-linecap: butt; stroke-dasharray: 1.2 4.4; opacity: .65; }
.w-shine { stroke: #ffd3de; stroke-width: 1.8; translate: -1.7px 0; opacity: .8; }
.w-crumbs circle { fill: #6b3a1c; stroke: #2a1306; stroke-width: 1; opacity: 0; }

/* maintenance: the bar's pink, so it stands out from the paper panels */
.care {
  margin-top: .6rem; padding: 1.2rem 1.3rem 1.3rem;
  background: linear-gradient(#f7c4d6, #ee93b5); border: 2.5px solid var(--ink); border-radius: 8px; box-shadow: 4px 5px 0 var(--ink);
}
.care h3 { margin: 0 0 .5rem; font: 400 clamp(1.35rem, 3.2vw, 1.75rem)/1.15 var(--display); }
.care p { margin: 0; max-width: 70ch; line-height: 1.55; }
.care-list { list-style: none; margin: 1rem 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: .7rem 1.6rem; }
.care-list li { display: flex; align-items: center; gap: .5em; font-weight: 700; }
.care-icon {
  width: 2.1em; height: 2.1em; flex: none; padding: .3em; box-sizing: border-box; overflow: visible;
  background: #fff; border: 2px solid var(--ink); border-radius: 8px; box-shadow: 2px 3px 0 var(--ink);
}
.care-icon path { fill: none; stroke: var(--ink); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
/* they draw themselves in when the card scrolls into view (.on: .6s each, 200ms apart), then
   wobble now and then: +/-6deg over the last .5s of a 5s cycle, out of step with each other */
.js .care-icon path { stroke-dasharray: 1 1; stroke-dashoffset: 1; }
.care.on .care-icon path { animation: ink .6s calc(var(--n) * 200ms) ease-out forwards; }
.care.on .care-icon { animation: icon-wobble 5s calc(var(--n) * 1.3s + 1.4s) ease-in-out infinite; }
.care-list li:nth-child(1) { --n: 0; } .care-list li:nth-child(2) { --n: 1; } .care-list li:nth-child(3) { --n: 2; }
@keyframes icon-wobble { 0%, 90%, 100% { rotate: 0deg; } 92.5% { rotate: -6deg; } 95% { rotate: 6deg; } 97.5% { rotate: -3deg; } }
/* panels lift and tilt a little under the pointer, like the Book a meeting button */
@media (hover: hover) {
  .offer .lead, .offer .ticks, .offer .care { transition: transform .18s ease-out, box-shadow .18s ease-out; }
  .offer .lead:hover, .offer .ticks:hover, .offer .care:hover { transform: translate(-2px, -3px) rotate(-1deg); box-shadow: 6px 8px 0 var(--ink); }
}
.offer + .quiz { margin-top: .9rem; }

/* ---------- services: the questionnaire ---------- */
.quiz { max-width: 760px; margin-top: 1.6rem; display: grid; gap: 1.5rem; }
.fine { margin: 0; font-size: .95rem; color: #333; max-width: 62ch; }
.q {
  margin: 0; padding: 1.3rem 1.4rem 1.5rem; min-width: 0;
  background: #fff; border: 2.5px solid var(--ink); border-radius: 8px;
  box-shadow: 4px 5px 0 var(--ink);
}
/* floated so the question sits inside the card instead of on its border */
.q legend {
  float: left; width: 100%; margin: 0 0 1rem; padding: 0;
  font: 400 clamp(1.25rem, 3vw, 1.6rem)/1.2 var(--display);
}
.q legend + * { clear: both; }
.q-n {
  display: inline-grid; place-items: center;
  width: 1.6em; height: 1.6em; margin-right: .5em; vertical-align: .05em;
  font-size: .8em; background: var(--zombie); border: 2px solid var(--ink); border-radius: 50%;
}
.opt { font: 400 .95rem system-ui, sans-serif; color: #444; }
/* the options run down the card, one full-width row each */
.chips { display: flex; flex-direction: column; gap: .55rem; }
.chip {
  display: flex; align-items: center; gap: .7em;
  min-height: 48px; padding: .55em 1em;
  font-weight: 600; background: var(--paper);
  border: 2px solid var(--ink); border-radius: 6px;
  cursor: pointer; transition: background .15s, transform .15s;
}
.chip:hover { transform: translateX(3px); }
.chip input {
  appearance: none; -webkit-appearance: none; flex: none;
  width: 1.15em; height: 1.15em; margin: 0;
  background: #fff; border: 2px solid var(--ink); border-radius: 50%;
}
.chip input:checked { background: radial-gradient(circle, var(--ink) 0 36%, #fff 42%); }
.chip:has(input:checked) { background: var(--zombie); }
.chip input:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }
/* "Other" asks what the business is, only once it's picked */
.field.other-box { display: none; }
.q:has(input[value="other"]:checked) .field.other-box { display: grid; }
.field { display: grid; gap: .35rem; margin-top: .9rem; }
.field:first-of-type { margin-top: 0; }
.chips + .field { margin-top: 1rem; }
.field label { font-weight: 600; }
.quiz input[type="text"], .quiz input[type="email"], .quiz input[type="tel"], .quiz textarea {
  width: 100%; padding: .7em .85em;
  font: inherit; color: var(--ink); background: #fff;
  border: 2px solid var(--ink); border-radius: 14px;
}
.quiz textarea { min-height: 8.5em; resize: vertical; }
.quiz [aria-invalid="true"] { border-color: #b3261e; box-shadow: 0 0 0 2px rgb(179 38 30 / .25); }
.q[aria-invalid="true"] { border-color: #b3261e; }
.err { margin: .55rem 0 0; color: #b3261e; font-weight: 600; }   /* 6:1 on white */
/* bot trap: off-screen, out of the tab order, hidden from screen readers */
.hp { position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden; }
.send-row { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem 1.4rem; }
.send {
  font: 400 22px/1 var(--display); color: var(--ink);
  padding: .6em 1.5em .55em;
  background: var(--zombie); border: 2.5px solid var(--ink); border-radius: 999px;
  box-shadow: 3px 4px 0 var(--ink);
  cursor: pointer; transition: transform .15s, box-shadow .15s;
}
.send:hover { transform: translate(-1px, -1px) rotate(-2deg); box-shadow: 4px 6px 0 var(--ink); }
.send:active { transform: translate(1px, 2px); box-shadow: 1px 1px 0 var(--ink); }
.send:disabled { opacity: .6; cursor: progress; transform: none; }
.send[hidden] { display: none; }
.status { margin: 0; font-weight: 600; }
.status:empty { display: none; }
.status.bad { color: #b3261e; }
.finale { display: grid; gap: 1.2rem; }
.finale[hidden] { display: none; }
.fine a { color: var(--ink); font-weight: 600; }
/* one question at a time (app.js adds .stepping) */
.step-count { margin: 0 0 -.6rem; font-weight: 600; color: #333; }
.stepping .q { scroll-margin-top: calc(var(--bar-h) + 24px); }
.q.enter { animation: step-in .35s cubic-bezier(.22, 1, .36, 1); }
@keyframes step-in { from { opacity: 0; transform: translateX(24px); } }
.step-nav { display: flex; flex-wrap: wrap; gap: .8rem; }
.step-btn {
  min-height: 44px; padding: .5em 1.3em .45em;
  font: 400 20px/1 var(--display); color: var(--ink);
  background: #fff; border: 2.5px solid var(--ink); border-radius: 999px;
  box-shadow: 2px 3px 0 var(--ink);
  cursor: pointer; transition: transform .15s, box-shadow .15s;
}
.step-btn.next { background: var(--zombie); }
.step-btn:hover { transform: translate(-1px, -1px); box-shadow: 3px 5px 0 var(--ink); }
.step-btn:active { transform: translate(1px, 2px); box-shadow: 1px 1px 0 var(--ink); }
.step-btn[hidden] { display: none; }
/* these sit on bare dirt, where an ink ring would vanish: a paper one instead (7:1 on the dark brown) */
.ground .step-btn:focus-visible, .ground .send:focus-visible, .ground .quiz-done:focus-visible { outline-color: var(--paper); }
@media (prefers-reduced-motion: reduce) { .q.enter { animation: none; } }
/* after it's sent, the form gives way to a thank-you card */
.quiz-done {
  max-width: 760px; margin-top: 1.6rem; padding: 1.4rem 1.5rem;
  font: 400 clamp(1.3rem, 3vw, 1.7rem)/1.3 var(--display);
  background: var(--zombie); border: 2.5px solid var(--ink); border-radius: 8px;
  box-shadow: 4px 5px 0 var(--ink);
}
.quiz-done:focus { outline: none; }
.quiz-done:focus-visible { outline: 3px solid var(--focus); outline-offset: 4px; }

/* ---------- privacy policy (privacy.html) ---------- */
.legal { max-width: 760px; margin: 0 auto; padding: 3.5rem clamp(16px, 4vw, 40px) 1rem; line-height: 1.6; }
.legal h1 {
  margin: 0 0 .3rem;
  font: 400 clamp(2.2rem, 6vw, 3.4rem)/1.05 var(--display);
  color: var(--zombie); -webkit-text-stroke: .1em var(--ink); paint-order: stroke fill;
  text-shadow: .03em .06em 0 var(--ink);
}
.legal h2 { margin: 2.2rem 0 .5rem; font: 400 clamp(1.35rem, 3.4vw, 1.75rem)/1.2 var(--display); }
.legal .updated { margin-top: 0; color: #333; }
.legal a { color: var(--ink); font-weight: 600; }
.legal ul { padding-left: 1.3rem; }
.legal li + li { margin-top: .45rem; }

/* ---------- page not found (404.html): the stamp, a line, and the way home ---------- */
.lost { min-height: 60vh; padding-bottom: 3rem; text-align: center; }
.lost-stamp { display: block; width: 216px; height: auto; margin: 0 auto 1.2rem; transform: rotate(-8deg); }
.lost p { font-size: 1.1rem; }
.lost-fr { margin-top: 2.6rem; padding-top: 2rem; border-top: 2.5px dashed var(--ink); }
.lost-fr h2 { margin: 0 0 .3rem; font: 400 clamp(1.6rem, 4.5vw, 2.4rem)/1.1 var(--display); }
.legal .lost-home {
  display: inline-block; margin-top: .6rem; padding: .55em 1.2em .5em;
  font: 400 1.25rem/1.1 var(--display); text-decoration: none;
  background: var(--zombie); border: 2.5px solid var(--ink); border-radius: 999px; box-shadow: 2px 3px 0 var(--ink);
  transition: transform .15s, box-shadow .15s;
}
.lost-home:hover { transform: translate(-1px, -1px) rotate(-2deg); box-shadow: 3px 5px 0 var(--ink); }
.lost-home:active { transform: translate(1px, 2px); box-shadow: 1px 1px 0 var(--ink); }

/* ---------- footer ---------- */
.foot { display: flex; flex-direction: column; align-items: center; gap: .9rem; padding: 3rem 1rem; }
.foot-links { margin: 0; font-size: .95rem; }
.foot-links a { color: var(--ink); }
.stamp { width: 72px; height: auto; transform: rotate(-8deg); }
