/*
 * cameras.fyi holding page, plus the small confirm and unsubscribe pages (src/pages.ts).
 * Lab envelope: quiet paper and ink, one bold thing (the film strip), and red grease pencil as the only mark.
 * Motion animates transform, opacity and stroke-dashoffset only, and only in answer to a touch.
 */

@font-face {
  font-family: "Bowlby One";
  src:
    url("/fonts/BowlbyOne-Regular.woff2") format("woff2"),
    url("/fonts/BowlbyOne-Regular.ttf") format("truetype");
  font-display: swap;
}
@font-face {
  font-family: "Familjen Grotesk";
  src:
    url("/fonts/FamiljenGrotesk.woff2") format("woff2"),
    url("/fonts/FamiljenGrotesk.ttf") format("truetype");
  font-weight: 400 700;
  font-display: swap;
}
@font-face {
  font-family: "DotGothic16";
  src:
    url("/fonts/DotGothic16-Digits.woff2") format("woff2"),
    url("/fonts/DotGothic16-Digits.ttf") format("truetype");
  font-display: swap;
}

:root {
  --paper: #FAF8F4;
  --ink: #0F0F0F;
  --muted: #6B6B6B;
  --field: oklch(0.993 0.004 80);
  --rule: #E5E0D7;
  --film: #0F0F0F;
  --on-film: #FAF8F4;
  --hole: var(--paper); /* perforations show the page through the film */
  --edge: oklch(0.78 0.12 70); /* the amber edge print on real film */
  --red: oklch(0.57 0.205 34);
  --red-ink: oklch(0.985 0.006 34);

  --display: "Bowlby One", Impact, "Arial Black", sans-serif;
  --body: "Familjen Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif;
  --dot: "DotGothic16", ui-monospace, monospace;

  --ease-out: cubic-bezier(0.23, 1, 0.32, 1);
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
  --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);

  --gutter: max(16px, env(safe-area-inset-left, 0px), min(64px, 5vw));
  --frame-w: clamp(240px, 72vw, 380px);
  --cell-w: calc(var(--frame-w) + 16px);
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper: #151311;
    --ink: #F3EFE8;
    --muted: #A8A29A;
    --field: #1E1C19;
    --rule: #2E2B27;
    --hole: #3A3530; /* paper-tone rebate, so the film still reads as film on a dark page */
    --red: oklch(0.65 0.17 34);
    --red-ink: oklch(0.18 0.03 34);
    color-scheme: dark;
  }
}
:root[data-theme="dark"] {
  --paper: #151311;
  --ink: #F3EFE8;
  --muted: #A8A29A;
  --field: #1E1C19;
  --rule: #2E2B27;
  --hole: #3A3530;
  --red: oklch(0.65 0.17 34);
  --red-ink: oklch(0.18 0.03 34);
  color-scheme: dark;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}
html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 400 17px/1.45 var(--body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
a {
  color: inherit;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}
:focus-visible {
  outline: 2px solid var(--red);
  outline-offset: 3px;
  border-radius: 4px;
}
[hidden] {
  display: none !important;
}
.sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---- masthead and lede -------------------------------------------------------------------------- */

.masthead {
  padding: calc(20px + env(safe-area-inset-top, 0px)) var(--gutter) 0;
}
.brand {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  color: var(--ink);
  border-radius: 6px;
}
.lockup {
  display: block;
  width: clamp(224px, 62vw, 300px);
  height: auto;
}
.brand-dot {
  fill: var(--red);
}
.tagline {
  margin: 8px 0 0;
  font: 500 12px/1.3 var(--body);
  letter-spacing: 0.24em;
  text-transform: uppercase;
}
/* What cameras.fyi is, in two lines, so the strip of cameras lands above the fold. */
.reel {
  margin-top: 20px;
}
.stage h1 {
  margin: 0 var(--gutter);
  max-width: 20ch;
  font: 400 clamp(28px, 7vw, 46px)/1.04 var(--display);
  text-wrap: balance;
  overflow-wrap: anywhere; /* a long word breaks rather than overflows at large text zoom */
}
.stage h1 span {
  display: block;
}
.lede {
  margin: 10px var(--gutter) 22px;
  max-width: 44ch;
  color: var(--muted);
  text-wrap: pretty;
}
/* ---- the film strip: the one bold thing ---------------------------------------------------------- */

.film {
  position: relative;
  background: var(--film);
  color: var(--edge);
  user-select: none;
  -webkit-user-select: none;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .film {
    border-block: 2px solid var(--hole);
  }
}
:root[data-theme="dark"] .film {
  border-block: 2px solid var(--hole);
}

.tri {
  display: inline-block;
  width: 0.5em;
  height: 0.62em;
  margin-right: 0.28em;
  background: currentColor;
  clip-path: polygon(0 0, 100% 50%, 0 100%);
}

/* Without .js nothing pins and nothing tilts: the stage is plain flow and the tilt box is just a wrapper. */
.tilt {
  position: relative;
}
/* With JS the reel pins: a tall section (100svh plus PIN_SVH, 120svh) whose stage stays on screen while
   scrolling through it drives the zoom, the turn and the circle (client/main.ts). The film sits in the
   stage's free space, so the zoom grows into it rather than over the headline or the quote.
   Only on screens tall enough for the stage: a landscape phone keeps the plain flow, and main.ts adds
   .flat when the stage's content still doesn't fit (large text), so nothing is ever clipped away. */
@media (min-height: 520px) {
  .js .reel:not(.flat) {
    height: calc(100vh + 120vh + var(--quote-room, 0px));
    height: calc(100svh + 120svh + var(--quote-room, 0px));
  }
  .js .reel:not(.flat) > .stage {
    position: sticky;
    top: 0;
    display: flex;
    flex-direction: column;
    height: 100vh;
    height: 100svh;
    padding-bottom: 76px; /* room for the dock, so it never covers the quote */
    overflow: clip;
  }
  /* The spare height goes a third above the film (the lede grows; it has no background) and two thirds below
     the quote, none between them: on arrival the stage still sits under the masthead, and this lifts the
     quote's label into the first screen, above the dock, so it shows there is more below. 20 px plus the
     quote's 22 px top margin is the room the zoom grows into. Every camera's quote is as tall as the longest
     (--quote-h, set by client/main.ts), so landing another camera never moves the film or the form. */
  .js .reel:not(.flat) > .stage > .lede {
    flex-grow: 1;
  }
  .js .reel:not(.flat) > .stage > .film {
    flex: none;
    margin-bottom: 20px;
  }
  /* Too short for that gap as well (main.ts adds .tight): the zoom grows into whatever room is left. */
  .js .reel:not(.flat) > .stage.tight > .film {
    margin-bottom: 0;
  }
  .js .reel:not(.flat) > .stage > .taste-copy {
    flex-grow: 2;
    flex-basis: var(--quote-h, auto); /* a basis, not a min-height: the spare height is shared out from it */
  }
  /* For a moment while main.ts measures: no spare height handed out, so it reads the content's own heights. */
  .js .reel:not(.flat) > .stage.sizing > * {
    flex-grow: 0;
  }
  .js .reel:not(.flat) > .stage.sizing > .taste-copy {
    flex-basis: auto;
  }
}
/* The pinned stage keeps 76 px at its foot for the dock, which is empty once the pin lets go: the form rides
   up into it, and into the room the quote grew by (--quote-room, set by client/main.ts; the reel is that
   much taller, so the pin is as long as ever), so the quote leads straight into the form instead of across a
   blank strip. The scrub is done before the form starts to ride up (pinProgress in client/reel.ts). */
@media (min-height: 520px) and (prefers-reduced-motion: no-preference) {
  .js .reel:not(.flat) + .join {
    margin-top: calc(-68px - var(--quote-room, 0px));
  }
}
/* A phone browser with bars top and bottom (Chrome on iOS) leaves the stage a few dozen px short, and .flat
   would turn the whole scroll scrub off. Tighten the pinned stage there instead: a smaller headline, less
   air around the film, and the dock's room cut to the dock itself (52 px plus its 12 px inset). */
@media (min-height: 520px) and (max-height: 739px) {
  .js .reel:not(.flat) > .stage {
    padding-bottom: 66px;
  }
  .js .reel:not(.flat) > .stage h1 {
    font-size: clamp(26px, 6.3vw, 46px);
  }
  .js .reel:not(.flat) > .stage .lede {
    margin-bottom: 12px;
  }
  .js .reel:not(.flat) > .stage .taste-copy {
    margin-top: 12px;
  }
}
@media (min-height: 520px) and (max-height: 739px) and (prefers-reduced-motion: no-preference) {
  .js .reel:not(.flat) + .join {
    margin-top: calc(-58px - var(--quote-room, 0px));
  }
}
/* The film base (and its dark-mode edges) moves onto the tilt box, so it tilts and zooms with the frames. */
:root.js .film {
  background: none;
  border-block: 0;
}
.js .tilt {
  background: var(--film);
  transform-origin: 50% 50%;
  will-change: transform;
}
@media (prefers-color-scheme: dark) {
  :root.js:not([data-theme="light"]) .tilt {
    border-block: 2px solid var(--hole);
  }
}
:root.js[data-theme="dark"] .tilt {
  border-block: 2px solid var(--hole);
}
.viewport {
  overflow: hidden; /* fallback */
  overflow-x: clip;
  overflow-y: visible;
  touch-action: pan-y pinch-zoom;
  cursor: grab;
}
.film.dragging .viewport {
  cursor: grabbing;
}
.track {
  display: flex;
  width: max-content;
  will-change: transform;
}
.set {
  display: flex;
}
.cell {
  position: relative;
  flex: 0 0 var(--cell-w);
  width: var(--cell-w);
  padding: 22px 8px 0;
}

/* Perforations: one row above and one below each frame, eight holes per frame. */
.cell::before,
.edge::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 22px;
  background: var(--hole);
  --perf: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 34 22' preserveAspectRatio='none'%3E%3Crect x='8' y='5' width='18' height='12' rx='2.6'/%3E%3C/svg%3E");
  -webkit-mask: var(--perf) 0 0 / var(--pitch, 34px) 22px repeat-x;
  mask: var(--perf) 0 0 / var(--pitch, 34px) 22px repeat-x;
}
.cell::before {
  top: 0;
}
.edge {
  position: relative;
  display: block;
  height: 48px;
  font: 12px/1 var(--dot);
  letter-spacing: 0.08em;
  white-space: nowrap;
}
.edge::before {
  top: 0;
}
.edge > span {
  position: absolute;
  top: 30px;
}
.ep-name {
  left: 4px;
  font: 600 11px/1 var(--body);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.ep-n {
  left: 72%; /* clear of the longest name, Canon PowerShot SD1000 */
}
.ep-a {
  right: -10px;
}

/* The frame: a button holding a 3:2 photo. */
.frame {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 3px;
  background: #1D1B19;
  color: var(--on-film);
  font: inherit;
  cursor: pointer;
  touch-action: pan-y pinch-zoom;
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  transform: scale(1);
  transform-origin: 50% 50%;
  transition: transform 160ms var(--ease-out);
}
.film.dragging .frame {
  cursor: grabbing;
}
.frame:focus-visible {
  outline: 3px solid var(--on-film);
  outline-offset: 4px;
  border-radius: 4px;
}
.photo {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: inherit;
}
.photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  pointer-events: none;
}
.turn {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border-radius: inherit;
  pointer-events: none;
}
.frame.pressed {
  transform: scale(0.97);
  transition-duration: 120ms;
}
/* The grease pencil. */
.pencil {
  position: absolute;
  left: -12%;
  top: -12%;
  width: 124%;
  height: 124%;
  overflow: visible;
  pointer-events: none;
}
.pencil path {
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.pencil .halo {
  stroke: var(--film);
  opacity: 0.85;
}
.pencil .ink {
  stroke: var(--red);
}

/* Arrow buttons, for a mouse only: touch has the drag, the keyboard has the arrow keys. */
.nav {
  display: none;
}
@media (hover: hover) and (pointer: fine) {
  /* Outside the tilt box, so the zoom never pushes them off the screen. */
  .nav {
    position: absolute;
    z-index: 4;
    top: calc(22px + var(--frame-w) / 3 - 24px);
    display: grid;
    place-items: center;
    width: 48px;
    height: 48px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: var(--paper);
    color: var(--ink);
    box-shadow: 0 6px 18px -6px rgb(0 0 0 / 0.6);
    cursor: pointer;
    transition: transform 160ms var(--ease-out);
  }
  .nav:active {
    transform: scale(0.94);
  }
  .nav svg {
    width: 22px;
    height: 22px;
    fill: none;
    stroke: currentColor;
    stroke-width: 2.4;
    stroke-linecap: round;
    stroke-linejoin: round;
  }
  .nav-prev {
    left: max(12px, calc(var(--gutter) - 24px));
  }
  .nav-next {
    right: max(12px, calc(var(--gutter) - 24px));
  }
}

/* ---- the form ------------------------------------------------------------------------------------ */

.join {
  margin-top: 32px; /* collapses with the quote's 24px: 32px from quote to heading, as on the pinned stage */
  padding: 0 var(--gutter);
  max-width: calc(60ch + 2 * var(--gutter));
}
.join-stack {
  display: grid;
}
.join-stack > * {
  grid-area: 1 / 1;
  min-width: 0;
  transition:
    opacity 200ms var(--ease-out),
    visibility 0s linear 0s;
}
.join-stack > .is-off {
  opacity: 0;
  visibility: hidden;
  transition:
    opacity 200ms var(--ease-out),
    visibility 0s linear 200ms;
}
.join h2 {
  margin: 0 0 12px;
  max-width: 28rem; /* the input column */
  font: 600 22px/1.25 var(--body);
  text-wrap: balance;
}
#join-name,
#done-name {
  font-family: var(--display);
  font-weight: 400;
}
#join-name {
  font-size: 0.96em;
  letter-spacing: 0.005em;
}
.join-form label {
  display: block;
  margin-bottom: 8px;
  font: 500 15px/1.3 var(--body);
}
.join-form input {
  display: block;
  width: 100%;
  max-width: 28rem;
  height: 52px;
  padding: 0 16px;
  border: 1.5px solid var(--muted);
  border-radius: 12px;
  background: var(--field);
  color: var(--ink);
  font: 400 17px var(--body);
}
.join-form input::placeholder {
  color: var(--muted);
  opacity: 1;
}
.join-form input:focus-visible {
  outline: 2px solid var(--red);
  outline-offset: 2px;
  border-color: var(--ink);
}
.join-form input[aria-invalid="true"] {
  border-color: var(--red);
}
.msg {
  display: flex;
  gap: 8px;
  align-items: baseline;
  max-width: 28rem;
  margin: 10px 0 0;
  font: 500 15px/1.4 var(--body);
}
.msg:empty {
  display: none;
}
.msg.info::before {
  display: none;
}
.msg::before {
  /* a grease-pencil cross, so the message isn't told by colour alone */
  content: "";
  flex: none;
  width: 13px;
  height: 13px;
  background: var(--red);
  --cross: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M3 3.5 17 16.5M16 3 4 17' stroke='black' stroke-width='3.6' stroke-linecap='round' fill='none'/%3E%3C/svg%3E");
  -webkit-mask: var(--cross) center / contain no-repeat;
  mask: var(--cross) center / contain no-repeat;
}
.send {
  display: block;
  width: 100%;
  max-width: 28rem;
  height: 54px;
  margin-top: 14px;
  border: 0;
  border-radius: 12px;
  background: var(--red);
  color: var(--red-ink);
  font: 600 17px var(--body);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: transform 140ms var(--ease-out);
}
.send:active {
  transform: scale(0.97);
}
.send:disabled {
  cursor: progress;
}
.send-label {
  display: inline-block;
}
/* The docked link to the form: a full-width red button over the page foot. */
.dock {
  position: fixed;
  z-index: 10;
  inset-inline: var(--gutter);
  bottom: max(12px, env(safe-area-inset-bottom, 0px));
  display: block; /* not flex: the spaces around #dock-name must survive */
  min-height: 52px;
  padding: 15px 16px;
  border-radius: 12px;
  background: var(--red);
  color: var(--red-ink);
  font: 600 17px/22px var(--body);
  text-align: center;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transform: scale(1);
  opacity: 1;
  transition:
    transform 160ms var(--ease-out),
    opacity 160ms var(--ease-out);
}
.dock:active {
  transform: scale(0.97);
}
/* Out of the way while the form is on screen, while typing, and after a signup. */
.dock.is-off {
  opacity: 0;
  transform: translateY(12px);
  pointer-events: none;
}
.dock.no-anim {
  transition: none;
}
/* On arrival the dock slides up once from below the screen: the first screen's sign that the page goes on.
   Until then it is hidden, so it can't be tapped or focused unseen. main.ts adds .arrived once the film has
   wound in, so the two never move at once. Without JS it is a plain link, there from the start. */
html.js .dock:not(.arrived) {
  visibility: hidden;
}
.dock.enter {
  animation: dock-in 480ms var(--ease-drawer);
}
@keyframes dock-in {
  from {
    opacity: 0;
    transform: translateY(calc(100% + 24px));
  }
}
@keyframes dock-fade {
  from {
    opacity: 0;
  }
}
html.home {
  /* Focus never lands under the dock (WCAG 2.4.11): scrolling to a focused element leaves the dock's room. */
  scroll-padding-bottom: calc(74px + max(12px, env(safe-area-inset-bottom, 0px)));
}
body.home {
  /* the dock's height (52px, 74px when the camera name wraps) plus its offset, so it never covers the foot */
  padding-bottom: calc(74px + max(12px, env(safe-area-inset-bottom, 0px)));
}
.note {
  max-width: 28rem;
  margin: 12px 0 0;
  font-size: 15px;
  color: var(--muted);
}
.ts:empty {
  display: none;
}
.ts {
  margin-top: 12px;
}
.join-done p {
  max-width: 34ch;
  margin: 0;
}
.join-done b {
  font-weight: 600;
  overflow-wrap: anywhere;
}
.join-done h2:focus {
  outline: none;
}
.again {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-top: 10px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink);
  font: 500 15px var(--body);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  cursor: pointer;
}

/* ---- after a pick: a photo it took, one line from its sheet, and the look ---------------------------- */

.taste {
  display: flex;
  justify-content: center; /* the print sits centred in its column, on the table */
  max-width: 34rem;
  margin: 48px 0 0;
}
.print {
  --print-paper: oklch(0.997 0.002 90); /* whiter than the page, as photo paper is */
  margin: 0;
  color: #2A2724;
  transform: rotate(-2deg);
}
.print-img {
  display: block;
  overflow: hidden;
  background: oklch(0.32 0.025 220);
}
.print img {
  display: block;
  width: 100%;
  height: auto;
}
/* An instant print (the SX-70): square picture, the fat white bottom it was written on. */
.print[data-kind="instant"] {
  width: min(100%, 250px);
  padding: 5.5% 5.5% 20%;
  background: var(--print-paper);
  border-radius: 3px;
  box-shadow:
    0 0 0 1px oklch(0.2 0.02 40 / 0.08),
    0 14px 28px -14px oklch(0.2 0.02 40 / 0.55);
}
.print[data-kind="instant"] .print-img {
  aspect-ratio: 1;
}
.print[data-kind="instant"] img {
  height: 100%;
  object-fit: cover;
  /* The scan has its own white frame: crop it off (the print here is the frame), and nudge the picture,
     which sits a little up and left in the scan, to the middle of the square. */
  transform: scale(1.14) translate(0.4%, 1.1%);
}
.print[data-kind="instant"] figcaption {
  margin-top: 8px;
  font: 500 13px/1.2 var(--body);
  text-wrap: balance;
}
/* A lab print (35 mm and the CCD): the whole frame at its own shape, a thin even white border, and the
   caption underneath on the table, not on the print. */
.print[data-kind="lab"] {
  width: min(100% - 8px, 360px);
}
.print[data-kind="lab"] .print-img {
  padding: clamp(7px, 3.2%, 12px); /* the white border; padding, since a border can't be a percentage */
  background: var(--print-paper);
  border-radius: 2px;
  box-shadow:
    0 0 0 1px oklch(0.2 0.02 40 / 0.08),
    0 14px 28px -14px oklch(0.2 0.02 40 / 0.55);
}
.print[data-kind="lab"] figcaption {
  margin-top: 10px;
  text-align: center;
  font: 500 13px/1.3 var(--body);
  color: var(--muted);
}
/* Developing: dark and murky first, clearing over a few seconds, like an instant print in your hand. */
.print.developing img {
  opacity: 0;
  filter: saturate(0.2) contrast(0.7) brightness(0.6);
}
.print.developing.developed img {
  opacity: 1;
  filter: none;
  transition:
    opacity 2.4s var(--ease-out) 150ms,
    filter 4.5s var(--ease-out) 150ms;
}
.taste-copy {
  min-width: 0;
  max-width: 34rem;
  margin: 22px var(--gutter) 24px;
}
.taste-label {
  margin: 0 0 6px;
  font: 500 13px/1.35 var(--body);
  color: var(--muted);
}
#taste-name {
  font-weight: 600;
  color: var(--ink);
}
.taste-line {
  position: relative;
  margin: 0;
  padding-left: 0.1em;
  font: 600 17px/1.4 var(--body);
  font-style: oblique 9deg;
  font-synthesis: style;
  text-wrap: pretty;
}
/* Said out loud: big red quote marks, the opening one hung into the margin. */
.taste-line::before,
.taste-line::after {
  font: 800 1.9em/0 var(--body);
  font-style: normal;
  color: var(--red);
  vertical-align: -0.36em;
}
.taste-line::before {
  content: "\201C";
  margin-left: -0.42em;
  margin-right: 0.04em;
}
.taste-line::after {
  content: "\201D";
  margin-left: 0.06em;
}
/* With JS the quote comes up word by word as the pencil draws (client/main.ts sets each word, and --qo and
   --qc for the marks, from scroll). The marks are stamped on: they land from bigger and twisted. */
.taste-line .w {
  display: inline-block;
}
.js .taste-line::before,
.js .taste-line::after {
  display: inline-block;
  transform-origin: 50% 70%;
}
.js .taste-line::before {
  opacity: var(--qo, 1);
  transform: scale(calc(1.8 - 0.8 * var(--qo, 1))) rotate(calc((1 - var(--qo, 1)) * -24deg));
}
.js .taste-line::after {
  opacity: var(--qc, 1);
  transform: scale(calc(1.8 - 0.8 * var(--qc, 1))) rotate(calc((1 - var(--qc, 1)) * 24deg));
}
.join-what {
  max-width: 28rem;
  margin: 0 0 28px;
  font-size: 16px;
  line-height: 1.45;
}
.extras {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 20px;
  align-items: center;
  justify-content: center; /* under the print */
  max-width: 34rem;
  margin-top: 4px;
}
.flickr {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink);
  font: 600 15px var(--body);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  cursor: pointer;
}
.flickr {
  font-weight: 500;
}
.flickr::after {
  content: "\2197";
  margin-left: 4px;
  text-decoration: none;
}
.flickr:focus-visible {
  outline: 2px solid var(--red);
  outline-offset: 2px;
}
/* ---- footer -------------------------------------------------------------------------------------- */

.foot {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  column-gap: 28px;
  margin: 56px var(--gutter) 0;
  padding: 4px 0 calc(28px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--rule);
  font-size: 14px;
  color: var(--muted);
}
.foot p {
  margin: 0;
}
.foot .credits[open] {
  flex-basis: 100%; /* the open list takes the row; the question moves under it */
  order: 1;
}
.foot a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
.credits summary {
  display: flex;
  align-items: center;
  min-height: 44px;
  width: fit-content;
  cursor: pointer;
}
.credits ul {
  display: grid;
  gap: 6px;
  max-width: 64ch;
  margin: 0 0 12px;
  padding-left: 1.1em;
  line-height: 1.5;
}
.credits li a {
  display: inline;
  min-height: 0;
  padding-block: 12px;
}

/* ---- confirm and unsubscribe pages (src/pages.ts): same envelope, Bowlby heading, pencil underline -- */
/* (body.in is the "You're in" page, styled in its own section below.) */

body:not(.home, .in) main {
  padding-top: calc(2rem + env(safe-area-inset-top, 0px)) !important;
}
body:not(.home, .in) main::before {
  content: "";
  display: block;
  width: 168px;
  aspect-ratio: 5831 / 986;
  margin-bottom: 44px;
  background: url("/brand/lockup.svg") left center / contain no-repeat;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) body:not(.home, .in) main::before {
    background-image: url("/brand/lockup-white.svg");
  }
}
:root[data-theme="dark"] body:not(.home, .in) main::before {
  background-image: url("/brand/lockup-white.svg");
}
body:not(.home, .in) h1 {
  position: relative;
  display: inline-block;
  margin: 0 0 22px;
  padding-bottom: 12px;
  font: 400 clamp(34px, 9vw, 48px)/1.02 var(--display);
  text-wrap: balance;
}
body:not(.home, .in) h1::after {
  content: "";
  position: absolute;
  left: -2%;
  bottom: -4px;
  width: 104%;
  height: 16px;
  background: var(--red);
  --ul: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 240 16' preserveAspectRatio='none'%3E%3Cpath d='M5 11.2C74 10.4 152 7.6 225 6.2Q231 5.7 208 10.1' stroke='black' stroke-width='4.5' stroke-linecap='round' stroke-linejoin='round' fill='none'/%3E%3C/svg%3E");
  -webkit-mask: var(--ul) 0 0 / 100% 100% no-repeat;
  mask: var(--ul) 0 0 / 100% 100% no-repeat;
}
body:not(.home, .in) main p {
  margin: 0 0 16px;
  max-width: 34ch;
}
body:not(.home, .in) main > p:last-child {
  margin-top: 28px;
  font-size: 15px;
}
body:not(.home, .in) main > p:last-child a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
body:not(.home, .in) form button {
  height: 54px;
  margin-top: 6px;
  padding: 0 28px;
  border: 0;
  border-radius: 12px;
  background: var(--red);
  color: var(--red-ink);
  font: 600 17px var(--body);
  cursor: pointer;
  transition: transform 140ms var(--ease-out);
}
body:not(.home, .in) form button:active {
  transform: scale(0.97);
}

/* ---- "You're in" (src/pages/in.ts): the cheat sheet as a lab envelope (ruling R19) ---------------------
 * Bowlby section headings each get a seeded grease-pencil underline; the camera name sits huge over the
 * bottom of the hero; red marks only for the pencil, the numerals and the button. Everything here is
 * scoped to body.in and reuses the tokens at the top of this file.
 */

body.in {
  line-height: 1.6;
}
/* Full width so the pencil marks may run into the gutter; clipped only at the screen edge. */
.in-wrap {
  overflow-x: clip;
  padding:
    calc(20px + env(safe-area-inset-top, 0px))
    max(16px, env(safe-area-inset-right, 0px))
    calc(28px + env(safe-area-inset-bottom, 0px))
    max(16px, env(safe-area-inset-left, 0px));
}
.in-wrap > * {
  max-width: 1120px;
  margin-inline: auto;
}
.in-brand {
  display: block;
  width: 168px;
  height: 44px;
  border-radius: 6px;
  background: url("/brand/lockup.svg") left center / 168px auto no-repeat;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .in-brand {
    background-image: url("/brand/lockup-white.svg");
  }
}
:root[data-theme="dark"] .in-brand {
  background-image: url("/brand/lockup-white.svg");
}

/* A Bowlby heading with its pencil underline: the svg spans the text, its stroke never scales. */
.in .h-pencil,
.in .sheet h3 {
  position: relative;
  display: inline-block;
  padding-bottom: 10px;
  font-family: var(--display);
  font-weight: 400;
  text-wrap: balance;
}
.in .ul {
  position: absolute;
  left: -2%;
  bottom: -4px;
  width: 104%;
  height: 16px;
  overflow: visible;
}
.in .ul path,
.in .loop path {
  fill: none;
  stroke: var(--red);
  stroke-width: 4.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.in-head h1 {
  margin: 28px 0 0;
  font: 400 clamp(27px, 6vw, 34px)/1.05 var(--display);
}
.in-sub {
  max-width: 40ch;
  margin: 18px 0 0;
  text-wrap: pretty;
}

/* The hero: the camera's photo, plain (R25: the circled frame is on the share card), with the name set huge over its bottom edge. */
.in .top {
  position: relative;
  margin-top: 32px;
  transform-origin: 50% 40%;
}
.in .hero {
  position: relative;
  margin: 0;
}
.hero-photo {
  display: block;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  border-radius: 14px;
  background: var(--field);
}
.hero-photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.in .cam {
  position: relative;
  margin: -0.42em 0 0 -0.04em;
  font: 400 clamp(44px, 10.5vw, 104px)/0.9 var(--display);
  letter-spacing: -0.01em;
  text-wrap: balance;
  /* a paper outline, so the letters read where they cross the photo and the pencil */
  text-shadow:
    0.04em 0.04em 0 var(--paper),
    -0.04em -0.04em 0 var(--paper),
    0.04em -0.04em 0 var(--paper),
    -0.04em 0.04em 0 var(--paper);
}
.in .loupe {
  max-width: 30ch;
  margin: 14px 0 0;
  font: 500 clamp(20px, 3.4vw, 27px)/1.3 var(--body);
  text-wrap: balance;
}

.in .cols {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 8px;
  margin-top: 8px;
}
@media (min-width: 821px) {
  .in .cols {
    grid-template-columns: minmax(0, 1fr) 260px;
    gap: 56px;
    align-items: start;
  }
  .in .side {
    grid-column: 2;
    grid-row: 1;
    margin-top: 56px;
  }
  .in .sheet {
    grid-column: 1;
    grid-row: 1;
  }
}

/* Share: the Stories card, a red share button and a plain save link. */
.sharebox {
  display: grid;
  grid-template-columns: 112px minmax(0, 1fr);
  gap: 16px;
  align-items: center;
  margin-top: 36px;
}
@media (min-width: 821px) {
  .sharebox {
    grid-template-columns: minmax(0, 1fr);
    margin-top: 0;
  }
  .sharebox .card {
    width: 200px;
  }
}
.sharebox .card {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--rule);
  border-radius: 10px;
}
.share-copy p {
  margin: 0;
  font-size: 15px;
  line-height: 1.45;
}
.in .share {
  display: block;
  width: 100%;
  max-width: 16rem;
  height: 50px;
  margin-top: 12px;
  padding: 0 20px;
  border: 0;
  border-radius: 12px;
  background: var(--red);
  color: var(--red-ink);
  font: 600 17px var(--body);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: transform 140ms var(--ease-out);
}
.in .share:active {
  transform: scale(0.97);
}
.in .save {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font: 500 15px var(--body);
}
.in .share-copy .share-status {
  color: var(--muted);
  font-size: 14px;
  overflow-wrap: anywhere;
}
/* Live regions stay in the accessibility tree when empty; they just take no space. */
.share-status:empty,
.copied:empty {
  margin: 0;
}

/* Most hunted: the order always, the numbers only from RANKING_REVEAL_AT confirmed. */
.rankbox {
  margin-top: 44px;
}
.rankbox h2 {
  margin: 0 0 14px;
  font-size: clamp(27px, 4.6vw, 34px);
  line-height: 1.05;
}
.rank {
  display: grid;
  margin: 0;
  padding: 0;
  list-style: none;
  counter-reset: r;
}
.rank li {
  counter-increment: r;
  display: flex;
  align-items: baseline;
  gap: 10px;
  min-height: 44px;
  padding: 10px 0;
  border-bottom: 1px solid var(--rule);
}
.rank li::before {
  content: counter(r);
  flex: 0 0 1.4em;
  font: 400 20px/1 var(--display);
  color: var(--muted);
}
.rank li.is-yours::before {
  color: var(--red);
}
.rank .rname {
  font-weight: 600;
}
.rank .yours {
  padding: 0 8px;
  border: 1.5px solid var(--red);
  border-radius: 999px;
  font: 600 13px/1.5 var(--body);
}
.rank .n {
  margin-left: auto;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.rank-note {
  margin: 12px 0 0;
  font-size: 14px;
  line-height: 1.45;
  color: var(--muted);
}

/* The sheet. */
.in .sheet {
  min-width: 0;
  max-width: 68ch;
}
.in .sheet h3 {
  margin: 56px 0 18px;
  font-size: clamp(27px, 4.6vw, 34px);
  line-height: 1.05;
}
.in .sheet p,
.in .sheet ul,
.in .sheet ol,
.in .sheet dl {
  margin: 0;
}

.in .why {
  display: grid;
  gap: 22px;
}
.in .why div {
  display: grid;
  grid-template-columns: 8.5em minmax(0, 1fr);
  gap: 4px 20px;
}
@media (max-width: 560px) {
  .in .why div {
    grid-template-columns: minmax(0, 1fr);
  }
}
.in .why dt {
  font: 700 18px/1.4 var(--body);
}
.in .why dd {
  margin: 0;
}

/* Samples: a swipe strip, each photo at its own shape so the camera's real rendering shows uncropped. */
.in .sheet .samples {
  --sh: clamp(220px, 60vw, 320px);
  display: flex;
  gap: 12px;
  margin: 32px -16px 0;
  padding: 0 16px 6px;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: 16px;
  scrollbar-width: thin;
}
.samples figure {
  flex: 0 0 auto;
  width: min(84%, 460px, calc(var(--sh) * var(--r, 1.5)));
  margin: 0;
  scroll-snap-align: start;
}
.samples img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 10px;
  background: var(--field);
}
.samples figcaption {
  margin-top: 6px;
  font-size: 13px;
  line-height: 1.4;
  color: var(--muted);
}

.in .what {
  font-size: 19px;
  line-height: 1.5;
}

.in .breaks {
  display: grid;
  gap: 14px;
  padding: 0;
  list-style: none;
}
.in .breaks li {
  position: relative;
  padding-left: 34px;
}
.in .breaks li::before {
  /* the pencil X, as on the form's error message */
  content: "";
  position: absolute;
  left: 2px;
  top: 0.32em;
  width: 18px;
  height: 18px;
  background: var(--red);
  --cross: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Cpath d='M3 3.5 17 16.5M16 3 4 17' stroke='black' stroke-width='3.4' stroke-linecap='round' fill='none'/%3E%3C/svg%3E");
  -webkit-mask: var(--cross) center / contain no-repeat;
  mask: var(--cross) center / contain no-repeat;
}

.in .ask {
  display: grid;
  gap: 16px;
  padding: 0;
  list-style: none;
  counter-reset: q;
}
.in .ask li {
  counter-increment: q;
  display: grid;
  grid-template-columns: 2.2em minmax(0, 1fr);
  align-items: baseline;
}
.in .ask li::before {
  content: counter(q);
  font: 400 28px/1 var(--display);
  color: var(--red);
}
.copyrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  margin-top: 20px;
}
.in .copy {
  min-height: 44px;
  padding: 0 18px;
  border: 1.5px solid var(--ink);
  border-radius: 999px;
  background: transparent;
  color: var(--ink);
  font: 600 15px var(--body);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: transform 140ms var(--ease-out);
}
.in .copy:active {
  transform: scale(0.97);
}
.copied {
  font-size: 14px;
  color: var(--muted);
}

/* Arrival checks: tap to tick, the pencil tick draws in 220 ms. */
.in .checks {
  display: grid;
  gap: 6px;
  padding: 0;
  list-style: none;
}
.checks label {
  position: relative;
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr);
  gap: 14px;
  align-items: start;
  padding: 10px 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.checks input {
  position: absolute;
  left: 0;
  top: 10px;
  width: 28px;
  height: 28px;
  margin: 0;
  opacity: 0;
}
.checks .box {
  width: 26px;
  height: 26px;
  margin-top: 1px;
}
.checks .box rect {
  fill: none;
  stroke: var(--ink);
  stroke-width: 1.8;
}
.checks .tick {
  fill: none;
  stroke: var(--red);
  stroke-width: 3.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 24;
  stroke-dashoffset: 24;
  transition: stroke-dashoffset 220ms var(--ease-out);
}
.checks input:checked + .box .tick {
  stroke-dashoffset: 0;
}
.checks input:checked ~ span {
  color: var(--muted);
}
.checks input:focus-visible + .box rect {
  stroke: var(--red);
  stroke-width: 2.6;
}
/* A tick from the keyboard appears at once (in.js sets .kb on the root until the next pointer press). */
.kb .checks .tick {
  transition: none;
}
.kb .in .share:active,
.kb .in .copy:active {
  transform: none;
}

.in .spec {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 20px 28px;
  margin-bottom: 20px;
}
.in .spec dt {
  margin-bottom: 6px;
  font: 400 20px/1.1 var(--display);
}
.in .spec dd {
  margin: 0;
}
.in .quirks {
  display: grid;
  gap: 10px;
  padding-left: 1.1em;
}
.in .quirks li::marker {
  color: var(--red);
}

/* Same vibe, cheaper: circled with the pencilPath loop. Inset on phones so the loop stays on screen. */
.in .cheaper {
  position: relative;
  max-width: 46ch;
  margin: 72px 5% 0;
  padding: clamp(34px, 7vw, 40px) clamp(30px, 8vw, 48px);
}
@media (min-width: 821px) {
  .in .cheaper {
    margin-inline: 0;
  }
}
.cheaper .loop {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}
.in .sheet .cheaper-k {
  display: block;
  margin: 0;
  padding: 0;
  font: 600 17px/1.3 var(--body);
  color: var(--red);
}
.in .sheet .cheaper-n {
  margin: 6px 0 10px;
  font: 400 clamp(24px, 4vw, 30px)/1.1 var(--display);
}
.nw {
  white-space: nowrap;
}

.in-foot {
  margin-top: 56px;
  font-size: 14px;
  color: var(--muted);
}
.in-foot p {
  margin: 0;
}
.in-foot > p a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}

/* The page's one orchestrated entrance: the frame settles in, then the blocks after it, 60 ms apart. */
@keyframes in-settle {
  from {
    opacity: 0;
    transform: scale(0.96);
  }
}
@keyframes in-rise {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
}
@keyframes in-fade {
  from {
    opacity: 0;
  }
}
.in .top {
  animation: in-settle 300ms var(--ease-out) both;
}
.in .sharebox {
  animation: in-rise 300ms var(--ease-out) 60ms both;
}
.in .rankbox {
  animation: in-rise 300ms var(--ease-out) 120ms both;
}
.in .sheet {
  animation: in-rise 300ms var(--ease-out) 180ms both;
}

/* ---- touch-only hover, reduced motion ------------------------------------------------------------ */

@media (hover: hover) and (pointer: fine) {
  .send:hover,
  .dock:hover,
  .in .share:hover {
    background: color-mix(in oklch, var(--red) 92%, var(--ink));
  }
  .in .copy:hover {
    background: color-mix(in oklch, var(--ink) 6%, transparent);
  }
}

/* ---- desktop: one centre line ----------------------------------------------------------------------
   The landed frame sits dead centre, so on a wide screen the words line up on it too: the masthead,
   headline and quote centre over and under the circled camera, the form and the print sit side by side
   in one centred block below, and the dock shrinks to a button instead of a slab across the screen. */
@media (min-width: 900px) {
  .masthead,
  .foot {
    text-align: center;
  }
  .foot {
    justify-content: center;
    max-width: 64rem;
    margin-inline: auto;
  }
  .foot .credits summary {
    margin-inline: auto;
  }
  .foot .credits ul {
    margin-inline: auto;
    text-align: left;
  }
  .stage h1,
  .lede,
  .taste-copy {
    margin-inline: auto;
    text-align: center;
  }
  .stage h1 {
    max-width: 18ch;
    font-size: clamp(46px, 4.6vw, 64px);
  }
  .lede {
    font-size: 18px;
  }
  .taste-line {
    font-size: 20px;
  }
  .join {
    display: grid;
    grid-template-columns: minmax(0, 28rem) auto;
    grid-template-rows: auto auto 1fr;
    column-gap: clamp(56px, 7vw, 104px);
    justify-content: center;
    align-items: start;
    max-width: none;
  }
  .join-stack {
    grid-column: 1;
    grid-row: 1 / span 3;
  }
  .taste {
    grid-column: 2;
    grid-row: 1;
    margin: 6px 0 0;
  }
  .print[data-kind="instant"] {
    width: clamp(240px, 22vw, 300px);
  }
  .print[data-kind="lab"] {
    width: clamp(300px, 30vw, 420px);
  }
  .extras {
    grid-column: 2;
    grid-row: 2;
    flex-direction: column;
    gap: 0;
    margin-top: 4px;
  }
  .dock {
    inset-inline: 0;
    width: fit-content;
    margin-inline: auto;
    padding-inline: 32px;
    border-radius: 999px;
  }
}

/* Two columns under the quote want a little more air than the phone's single column. */
@media (min-width: 900px) and (min-height: 520px) and (prefers-reduced-motion: no-preference) {
  .js .reel:not(.flat) + .join {
    margin-top: calc(-56px - var(--quote-room, 0px));
  }
}

@media (prefers-reduced-motion: reduce) {
  /* No movement or scaling: the circle appears whole. Opacity stays. */
  .frame,
  .frame.pressed,
  .send,
  .send:active,
  .dock:active,
  body:not(.home, .in) form button,
  .in .share,
  .in .share:active,
  .in .copy,
  .in .copy:active,
  .checks .tick {
    transform: none;
    transition: none;
  }
  /* The dock only fades, its entrance too. */
  .dock,
  .dock.is-off {
    transform: none;
    transition: opacity 160ms var(--ease-out);
  }
  .dock.enter {
    animation-name: dock-fade;
    animation-duration: 300ms;
  }
  /* No pin: the reel is ordinary flow. The stage still clips sideways, so the static zoom never scrolls. */
  .js .reel,
  .js .reel:not(.flat) {
    height: auto;
  }
  .js .reel > .stage,
  .js .reel:not(.flat) > .stage {
    position: static;
    display: block;
    height: auto;
    padding-bottom: 0;
    overflow-x: clip;
    overflow-y: visible;
  }
  .js .reel > .stage > .film,
  .js .reel:not(.flat) > .stage > .film {
    margin-block: calc(25px + var(--frame-w) / 12); /* the zoom's growth (an eighth of the film's height) plus a gap */
  }
  .js .tilt {
    will-change: auto;
  }
  /* "You're in": nothing moves; the blocks only fade in, in the same order. */
  .in .top,
  .in .sharebox,
  .in .rankbox,
  .in .sheet {
    animation-name: in-fade;
  }
}
