/* ===================================================================
   campoli.me — the public marketing page.
   Hand-written CSS, mobile first. See DESIGN.md for the rules.
   =================================================================== */

/* Typefaces and the three type tokens live in tokens.css — every page needs
   them, not just this one. */

::selection { background: var(--green); color: var(--on-green) }

* { box-sizing: border-box; margin: 0; padding: 0 }
html { -webkit-text-size-adjust: 100% }
body {
  font-family: var(--body);
  background: var(--bg); color: var(--txt);
  font-size: 16.5px; line-height: 1.65; overflow-x: hidden;
  transition: background-color .9s linear;
}
.wrap { width: min(1240px, 100% - 2.4rem); margin-inline: auto }
a { color: inherit }

/* Shared display + label recipes, so a change lands once. */
.t-display { font-family: var(--display); font-weight: 600; letter-spacing: -.018em;
             color: var(--ink); line-height: 1.05 }
.t-label { font-family: var(--label); font-weight: 700; text-transform: uppercase;
           letter-spacing: .18em; font-size: .74rem }

/* ===================================================================
   MOBILE FIRST. Everything below is the phone layout; the desktop
   two-column arrangement is added only at the 940px breakpoint.
   =================================================================== */

/* ---------- topbar ---------- */
/* The counter strip sticks directly beneath this bar, so the bar's height is a
   shared number rather than one each of them guesses. It was written as a bare
   45px on .tally and went silently wrong the moment the CTA inside the bar grew
   to a 44px touch target: the strip slid 19px under the header.
   44px control + 2 x .6rem padding + 1px border = 64px. */
:root { --topbar-h: 64px }

.topbar { position: sticky; top: 0; z-index: 60; background: var(--scrim);
          backdrop-filter: blur(10px); border-bottom: 1px solid var(--hair) }
.topbar-in { display: flex; align-items: center; justify-content: space-between;
             gap: .8rem; padding: .6rem 0 }
.mark { display: flex; align-items: center; gap: .55rem; text-decoration: none;
        color: var(--ink); min-width: 0 }
.mark svg { width: 26px; height: 26px; flex: none }
.mark span { font-family: var(--display); font-weight: 600; font-size: 1.06rem;
             letter-spacing: -.01em; white-space: nowrap }
.mark span b { font-weight: 600; color: var(--green) }
/* The page's only persistent conversion control, so it meets the 44px target
   on a phone. Height comes from min-height and centring rather than from extra
   padding, so the pill keeps its proportions instead of growing into a slab. */
.top-cta { display: inline-flex; align-items: center; min-height: 44px;
           font-family: var(--label); font-weight: 700; text-transform: uppercase;
           letter-spacing: .05em; font-size: .8rem; color: var(--on-green);
           background: var(--green); padding: .44rem .9rem; border-radius: 3px;
           text-decoration: none; white-space: nowrap; flex: none }
.top-cta:hover { background: #0c6248 }
.top-cta:focus-visible { outline: 3px solid var(--green-ink); outline-offset: 3px }

/* ---------- opening ---------- */
.open { padding: 2.6rem 0 2rem }
h1 { font-family: var(--display); font-weight: 600; letter-spacing: -.025em;
     font-size: clamp(2.15rem, 8.4vw, 3rem); line-height: 1.04; color: var(--ink);
     margin-bottom: 1.1rem }
h1 .q { color: var(--green-ink); display: block }
.open .lead { font-size: 1.06rem; max-width: 44ch; line-height: 1.6 }
.open .lead strong { color: var(--ink); font-weight: 600 }

/* ---------- the count strip: how many pieces so far ---------- */
/* The counter is only meaningful while pieces are still being collected. Once
   the system is whole it has nothing left to count, so it retires rather than
   riding along to the footer. */
.tally { position: sticky; top: var(--topbar-h); z-index: 40; background: var(--bg-2);
         border-block: 1px solid var(--hair); padding: .5rem 0;
         transition: opacity .5s ease, visibility .5s }
.tally.retired { opacity: 0; visibility: hidden }
.tally-in { display: flex; align-items: center; gap: .7rem }
.tally-mini { width: 34px; height: 34px; flex: none }
.tally-mini svg { width: 100%; height: 100%; display: block }
.tm-cell { fill: none; stroke: var(--hair); stroke-width: 5 }
.tm-cell.on { stroke: var(--green) }
.tm-cell.me { stroke: var(--green); fill: var(--green) }
.tally-txt { font-family: var(--label); font-weight: 600; font-size: .76rem;
             letter-spacing: .16em; text-transform: uppercase; color: var(--txt-dim);
             transition: color .4s ease }
.tally-txt b { color: var(--ink); font-weight: 700 }
.tally.full .tally-txt { color: var(--green-ink) }

/* ---------- one problem per screen ---------- */
.probs { padding-top: .4rem }
/* The sticky board belongs to the desktop layout only. On a phone the pieces
   are carried by each problem's own thumbnail, and the board reappears as the
   completion moment inside .done-sec. */
.stage { display: none }
.prob { padding: 2.6rem 0 2.2rem; border-bottom: 1px solid var(--hair) }
.prob-fig { display: flex; align-items: center; gap: 1rem; margin-bottom: 1.2rem }
.prob-pz { width: 96px; height: 96px; flex: none;
           transform: translateY(6px) scale(.96); opacity: .55;
           transition: transform .6s cubic-bezier(.22,1,.28,1), opacity .5s ease }
.prob-pz svg { width: 100%; height: 100%; display: block; overflow: visible }
.prob.on .prob-pz { transform: none; opacity: 1 }
.pp-body { fill: var(--bg-lift); stroke: var(--txt-dim); stroke-width: 2.6;
           transition: fill .55s ease, stroke .55s ease }
.pp-gl { fill: none; stroke: var(--txt-dim); stroke-width: 4.5;
         stroke-linecap: round; stroke-linejoin: round; transition: stroke .55s ease }
.prob.on .pp-body { fill: rgba(15, 122, 90, .12); stroke: var(--green-ink) }
.prob.on .pp-gl { stroke: var(--green-ink) }
.prob-k { font-family: var(--label); font-weight: 700; font-size: .74rem;
          letter-spacing: .22em; text-transform: uppercase; color: var(--green-ink);
          margin-bottom: .3rem }
.prob-t { font-family: var(--display); font-weight: 600; letter-spacing: -.02em;
          font-size: 1.6rem; line-height: 1.1; color: var(--ink); margin-bottom: .6rem }
.prob p { margin-bottom: .7rem; max-width: 46ch }
.prob p strong { color: var(--ink); font-weight: 600 }
.said { position: relative; padding-left: 1.5rem; color: var(--txt-dim);
        margin-top: .9rem; max-width: 44ch }
.said::before { content: "\201C"; position: absolute; left: 0; top: -.02em;
                font-family: var(--display); font-weight: 700; font-size: 2.2rem;
                line-height: 1; color: var(--green) }

/* ---------- the completion: full puzzle, its own moment ---------- */
.done-sec { padding: 3rem 0 3.4rem; border-bottom: 1px solid var(--hair);
            background: var(--paper-0); transition: background-color .9s linear }
.board { width: min(340px, 86vw); margin: 0 auto 1.6rem }
.board svg { width: 100%; height: auto; display: block; overflow: visible;
             filter: drop-shadow(0 6px 14px rgba(32, 30, 29, .16)) }
.bd-slot { fill: none; stroke: var(--hair); stroke-width: 1.5; stroke-dasharray: 6 6 }
.bd-pz { transform-origin: center;
         transform: translate(var(--tx), var(--ty)) rotate(var(--tr)); opacity: 0;
         transition: transform .9s cubic-bezier(.22,1,.28,1), opacity .5s ease;
         transition-delay: var(--d) }
.bd-pz .b { fill: rgba(15, 122, 90, .13); stroke: var(--green-ink); stroke-width: 2.2 }
.bd-pz .g { fill: none; stroke: var(--green-ink); stroke-width: 2.4;
            stroke-linecap: round; stroke-linejoin: round }
.board.go .bd-pz { transform: translate(0, 0) rotate(0); opacity: 1 }

/* The centre piece. It currently carries the mark as a signature; when a
   portrait is ready it goes back in and the mark steps aside — see the PHOTO
   constant in the page script, which is the only switch. */
.bd-me { transform-origin: center; transform: translateY(-46px) scale(.86); opacity: 0;
         transition: transform 1s cubic-bezier(.22,1,.28,1) .55s, opacity .6s ease .55s }
.bd-me .b { fill: var(--green); stroke: var(--paper-0); stroke-width: 2.6 }
.bd-me .me-rim { fill: none; stroke: var(--paper-0); stroke-width: 2.6 }
/* The mark on the centre plate: paper on green, the package's mono-white cut.
   Geometry lives in puzzle.js (MARK); only its colour is set here. */
.bd-me .mk circle { stroke: var(--paper-0) }
.bd-me .mk { opacity: .96 }
.board.go .bd-me { transform: translateY(0) scale(1); opacity: 1 }
.bd-me-slot { fill: none; stroke: var(--green-ink); stroke-width: 1.8;
              stroke-dasharray: 5 5; opacity: .55; transition: opacity .4s ease .5s }
.board.go .bd-me-slot { opacity: 0 }

.done-k { font-family: var(--label); font-weight: 700; font-size: .74rem;
          letter-spacing: .22em; text-transform: uppercase; color: var(--green-ink);
          margin-bottom: .5rem }
.done-h { font-family: var(--display); font-weight: 600; letter-spacing: -.022em;
          font-size: clamp(1.8rem, 6.8vw, 2.4rem); line-height: 1.08; color: var(--ink);
          margin-bottom: .9rem; max-width: 20ch }
.done-sec p { margin-bottom: .8rem; max-width: 48ch }
.done-sec p strong { color: var(--ink); font-weight: 600 }

/* ---------- roads ---------- */
.roads-sec { padding: 3rem 0; background: var(--paper-0);
             transition: background-color .9s linear }
.sec-h { font-family: var(--display); font-weight: 600; letter-spacing: -.022em;
         font-size: clamp(1.8rem, 6.6vw, 2.5rem); line-height: 1.08; color: var(--ink);
         margin-bottom: .85rem; max-width: 22ch }
.sec-h em { font-style: normal; color: var(--green-ink) }
.sec-l { max-width: 52ch; margin-bottom: 2rem }
.road { padding: 1.5rem 0; border-top: 1px solid var(--hair) }
.road:last-of-type { border-bottom: 1px solid var(--hair) }
.road-hd { display: flex; align-items: baseline; gap: .7rem; margin-bottom: .5rem }
.road-k { font-family: var(--display); font-weight: 700; font-size: 1.6rem;
          line-height: 1; color: var(--green); flex: none; font-variant-numeric: tabular-nums }
.road h3 { font-family: var(--display); font-weight: 600; letter-spacing: -.018em;
           font-size: 1.42rem; line-height: 1.1; color: var(--ink) }
.road p { max-width: 52ch; margin-bottom: .6rem }
.road-when { font-size: .94rem; color: var(--txt-dim) }
.road-when b { color: var(--green-ink); font-weight: 600 }
.roads-note { margin-top: 1.6rem; font-size: 1.06rem; max-width: 52ch;
              color: var(--ink); font-weight: 500 }

/* ---------- who ---------- */
.who-sec { padding: 3rem 0; border-top: 1px solid var(--hair) }
/* The portrait is a square source framed to a portrait crop, biased to the
   face so it is not lost in the landscape behind him. */
.face { aspect-ratio: 4/5; max-width: 230px; border-radius: 4px; overflow: hidden;
        background: var(--bg-lift); margin: 0 0 1.6rem;
        box-shadow: 0 6px 16px rgba(32, 30, 29, .14) }
.face img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 22%;
            display: block }
.who p { margin-bottom: .85rem; max-width: 48ch }
.who p strong { color: var(--ink); font-weight: 600 }
.who-claim { font-family: var(--display); font-weight: 600; letter-spacing: -.02em;
             font-size: clamp(1.35rem, 5.2vw, 1.9rem); line-height: 1.15; color: var(--ink);
             margin: 1.7rem 0 1.4rem; padding-top: 1rem; border-top: 2px solid var(--green);
             display: inline-block; max-width: 24ch }
.facts { display: flex; flex-wrap: wrap; gap: .45rem; list-style: none; margin-top: 1.2rem }
.facts li { font-family: var(--label); font-weight: 600; font-size: .8rem;
            letter-spacing: .08em; text-transform: uppercase; border: 1.4px solid var(--hair);
            border-radius: 999px; padding: .28rem .8rem }

/* ---------- close: the one inverted surface ---------- */
.close { padding: 3.4rem 0 3.8rem; background: var(--dark); color: var(--on-dark-dim) }
.close-h { font-family: var(--display); font-weight: 600; letter-spacing: -.026em;
           font-size: clamp(1.95rem, 7.6vw, 3.2rem); line-height: 1.04;
           color: var(--on-dark); margin-bottom: .9rem; max-width: 16ch }
.close-h em { font-style: normal; color: var(--green-lift) }
.close p { max-width: 48ch; margin-bottom: 1.6rem }
.btn { display: inline-flex; align-items: center; justify-content: center; gap: .6rem;
       font-family: var(--label); font-weight: 700; text-transform: uppercase;
       letter-spacing: .05em; font-size: 1.15rem; color: var(--on-green);
       background: var(--green); padding: .9rem 1.8rem; border-radius: 3px;
       text-decoration: none;
       box-shadow: 0 0 0 1.5px var(--green-lift), 0 8px 20px rgba(0, 0, 0, .45);
       transition: transform .2s cubic-bezier(.16,1,.3,1), box-shadow .2s, background .2s }
.btn:hover { background: var(--green-lift); transform: translateY(-2px);
             box-shadow: 0 0 0 1.5px var(--green-glow), 0 12px 28px rgba(0, 0, 0, .55) }
.btn:active { transform: translateY(1px) }
.btn:focus-visible { outline: 3px solid var(--green-glow); outline-offset: 4px }
.close-alt { margin-top: 1.1rem; font-size: .95rem; color: var(--on-dark-dim) }
.close-alt a { border-bottom: 1px dashed var(--on-dark-dim); text-decoration: none }
.close-alt a:hover { color: var(--green-glow); border-color: var(--green-glow) }

footer { padding: 1.5rem 0; background: var(--dark-2);
         border-top: 1px solid rgba(243, 242, 242, .12); font-size: .88rem;
         color: var(--on-dark-dim) }
footer a:hover { color: var(--green-glow) }
.foot-in { display: flex; justify-content: space-between; flex-wrap: wrap; gap: .8rem }

/* ===================================================================
   DESKTOP: 940px and up. The eight problems become a scrolling column
   beside a puzzle that assembles as you read. The mobile per-problem
   pieces and the sticky tally are retired here.
   =================================================================== */
@media (min-width: 940px) {
  body { font-size: 17px }
  .wrap { width: min(1240px, 100% - 3rem) }
  .topbar-in { padding: .8rem 0 }
  .mark svg { width: 29px; height: 29px }
  .mark span { font-size: 1.18rem }
  .top-cta { font-size: .9rem; padding: .48rem 1.1rem }

  .open { padding: clamp(3.4rem, 9vh, 6rem) 0 clamp(2rem, 4vh, 3rem) }
  h1 { font-size: clamp(2.6rem, 5.2vw, 4.4rem); max-width: 17ch; margin-bottom: 1.4rem }
  .open .lead { font-size: 1.2rem; max-width: 47ch }

  .tally { display: none }

  /* the spine: sticky board on the left, problems scrolling right */
  .probs { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.04fr);
           gap: clamp(2rem, 5vw, 5rem); align-items: start; padding-top: 0 }
  .stage { grid-column: 1; grid-row: 1 / -1; position: sticky;
           top: calc(50vh - 250px); height: 500px; display: flex;
           flex-direction: column; align-items: center; justify-content: center;
           overflow: hidden }
  .stage .board { width: 100%; max-width: 460px; margin: 0 }
  .stage-note { margin-top: 1rem; font-family: var(--label); font-weight: 600;
                font-size: .76rem; letter-spacing: .2em; text-transform: uppercase;
                color: var(--txt-dim); transition: color .4s }
  .stage-note.full { color: var(--green-ink) }

  .prob { grid-column: 2; min-height: 74vh; display: flex; flex-direction: column;
          justify-content: center; padding: 2rem 0; border-bottom: none }
  .prob-fig { display: none }          /* the board carries the pieces on desktop */
  .prob-t { font-size: clamp(1.85rem, 3vw, 2.6rem); margin-bottom: .8rem }
  .prob-k { margin-bottom: .8rem; display: flex; align-items: center; gap: .7rem }
  .prob-k::before { content: ""; width: 22px; height: 1.5px; background: var(--green-ink);
                    flex: none }

  .done-sec { grid-column: 2; background: none; border-bottom: none;
              padding: 2rem 0 4rem; min-height: 74vh; display: flex;
              flex-direction: column; justify-content: center }
  .done-sec .board { display: none }   /* the sticky board completes instead */
  .done-h { font-size: clamp(2rem, 3.3vw, 2.8rem) }

  .roads-sec { padding: clamp(3.5rem, 8vw, 6rem) 0; border-top: 1px solid var(--hair);
               background: var(--paper-0) }
  .road { display: grid; grid-template-columns: auto minmax(0, 1fr) minmax(0, 1.15fr);
          gap: clamp(1rem, 3vw, 2.6rem); align-items: baseline; padding: 1.9rem 0 }
  .road-hd { display: contents }
  .road-k { font-size: clamp(1.9rem, 3.2vw, 2.8rem); width: 2.2ch }
  .road h3 { font-size: clamp(1.4rem, 2.1vw, 1.9rem) }
  .road-body { grid-column: 3 }
  .road-when { grid-column: 2; margin-top: .5rem }

  .who-sec { padding: clamp(3.5rem, 8vw, 6rem) 0 }
  .who-grid { display: grid; grid-template-columns: minmax(0, .68fr) minmax(0, 1.32fr);
              gap: clamp(2rem, 4.5vw, 4rem); align-items: start }
  .face { max-width: none; margin-bottom: 0 }

  .close { padding: clamp(3.5rem, 8vw, 6rem) 0 }
  .btn { font-size: 1.24rem; padding: .92rem 2.1rem }
}

@media (prefers-reduced-motion: reduce) {
  /* No travelling ground and no crossfades: the page renders at its resolved,
     clear state and stays there. */
  :root { --t: 0 !important }
  body, .done-sec, .roads-sec { transition: none }
  /* .prob-pz resolves through the .on class rather than a transition, so it
     already renders finished here — but leaving its transition declared
     contradicted "all transitions are removed" and would animate any future
     state change on it. */
  .prob-pz, .tally, .tally-txt, .stage-note { transition: none }
  .bd-pz, .bd-me { transition: opacity .2s ease }
  .bd-pz { transform: none !important; opacity: 1 }
  .bd-me { transform: none !important; opacity: 1 }
  .bd-me-slot { opacity: 0 }
  .pp-body { fill: rgba(15, 122, 90, .14); stroke: var(--green-ink) }
  .pp-gl { stroke: var(--green-ink) }
}
