/* TGIF Cocktails - tgif.yellowdoor.club
 *
 * THE WORLD IS NOT INVENTED. It is lifted from two real things in the archive:
 *
 *   1. the 2019 posts (assets/photos/tgif-2019-12-05-052.png, -12-13-053.png) - hard yellow
 *      blocks, black blocks, photographs inset behind an offset frame, TGIF in letterspaced
 *      caps. The yellow is sampled from those files: rgb(255,222,89).
 *   2. the cellar itself - every photograph here is a dark brick room under tungsten. So the
 *      page is dark, and that is a decision from the use scene rather than a category habit:
 *      this is the Friday EVENING twin of a Thursday morning site.
 *
 * MOTION. One authored moment: the brass PRESS FOR CHAMPAGNE plate, which was a real button
 * in a real wall. Everything else is feedback or continuity. See docs/motion-direction.md.
 * Content is visible by default everywhere - every entrance is armed by JS from an already
 * readable state, so a failed script leaves a finished page rather than a blank one.
 */

:root {
  --ink:       #0E0D0B;   /* the poster black, warmed */
  --ink-2:     #17150F;   /* a panel lifted off it */
  --ink-3:     #221E16;   /* hairline / inset */
  --paper:     #F4F0E6;   /* warm off-white, not #fff */
  --paper-dim: #B9B2A2;   /* secondary text, tinted from the ground, never grey */
  --yellow:    #FFDE59;   /* sampled from the 2019 posts */
  --yellow-dk: #D9B838;
  --brass:     #C9A227;
  --champagne: #D8A84B;

  --font-display: 'Playfair Display', Georgia, 'Times New Roman', serif;
  --font-body: 'Golos Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

  /* Custom curves only. The browser defaults are too weak to read as intentional, and
     ease-in is never used on anything entering: it delays the first movement, which is the
     exact moment the eye is on it. */
  --ease-out:  cubic-bezier(.22, 1, .36, 1);
  --ease-soft: cubic-bezier(.16, 1, .3, 1);
  --ease-mid:  cubic-bezier(.77, 0, .175, 1);

  --gut: clamp(16px, 4vw, 40px);
  --rule: 1px solid #2E2A20;
}

*, *::before, *::after { box-sizing: border-box; }

html {
  /* clip, never hidden: `hidden` on html/body makes body a scroll container and breaks every
     sticky header on the page. clip crops without creating one. */
  overflow-x: clip;
  -webkit-text-size-adjust: 100%;
}

body {
  overflow-x: clip;
  margin: 0;
  /* The skip link is position:absolute and had no positioned ancestor, so it resolved
     against the viewport - escaped, by the gate's definition and in fact. */
  position: relative;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.6;
  font-synthesis-weight: none;
}

/* An undecoded image paints its ALT TEXT, which on a cold connection reads as a wall of
   words where the pictures should be. An ink ground plus transparent text makes the wait a
   quiet block instead. The alt attribute is untouched, so screen readers still announce it. */
img { background-color: var(--ink-3); color: transparent; display: block; max-width: 100%; height: auto; }

a { color: inherit; }

h1, h2, h3 { font-family: var(--font-display); font-weight: 700; line-height: 1.04; margin: 0; text-wrap: balance; }

p { margin: 0 0 1.1em; }
p:last-child { margin-bottom: 0; }

:focus-visible { outline: 3px solid var(--yellow); outline-offset: 3px; border-radius: 1px; }

.wrap { width: 100%; max-width: 1120px; margin-inline: auto; padding-inline: var(--gut); }
.narrow { max-width: 62ch; }

/* Skip link - the first thing a keyboard reaches, and it must not sit under the estate bar. */
.skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--yellow); color: var(--ink); padding: 12px 18px; font-weight: 700;
}
.skip:focus { left: 8px; top: 8px; }


/* ---------------------------------------------------------------- sections */
.sec { padding-block: clamp(56px, 9vw, 112px); }
.sec + .sec { border-top: var(--rule); }
.sec__h {
  font-size: clamp(30px, 7vw, 58px);
  margin-bottom: clamp(18px, 3vw, 30px);
  letter-spacing: -.015em;
}
.lede { font-size: clamp(17px, 2.1vw, 20px); color: var(--paper); max-width: 60ch; }
.dim { color: var(--paper-dim); }

/* ---------------------------------------------------------------- the hero */
.hero { position: relative; padding-top: clamp(28px, 5vw, 56px); }
/* THE MARK IS THE WORDMARK. It was drawn with its own <style> block setting a dark colour
   for a light page, at specificity `svg {}` - which beats inheriting currentColor from here,
   so it rendered near-black on a near-black page and was invisible. The class selector wins.
   Whatever mark replaces this one, keep this rule. */
.hero__mark { width: min(100%, clamp(240px, 52vw, 460px)); height: auto; color: var(--paper); }
.hero__mark svg { width: 100%; height: auto; display: block; color: var(--paper); }

.hero__h1 {
  font-size: clamp(30px, 6.6vw, 62px);
  margin-top: clamp(14px, 2.4vw, 26px);
  letter-spacing: -.02em;
}
.hero__lede { margin-top: clamp(16px, 2.4vw, 26px); font-size: clamp(17px, 2.2vw, 21px); max-width: 46ch; }
.hero__note { margin-top: 18px; max-width: 52ch; color: var(--paper-dim); font-size: 16px; }

.hero__photo {
  margin-top: clamp(28px, 5vw, 56px);
  position: relative;
  /* isolate, or the z-index:-1 block below sinks behind the page background and vanishes */
  isolation: isolate;
}
.hero__photo img { width: 100%; aspect-ratio: 1684 / 696; object-fit: cover; object-position: 50% 42%; }
/* The offset yellow block, from the posters. It sits BEHIND and below-left, and it is
   decorative, so it never takes space from the photograph. */
.hero__photo::before {
  content: ""; position: absolute; inset: auto auto -14px -14px;
  width: min(42%, 260px); height: 58%; background: var(--yellow); z-index: 0;
}
.hero__photo img { position: relative; z-index: 1; }

/* ---------------------------------------------------------------- how it went */
/* Deliberately NOT three same-size cards. Three pieces of different lengths, set as a list
   with a yellow rule, because that is what they are. */
.how { list-style: none; margin: 0; padding: 0; display: grid; gap: clamp(22px, 3.4vw, 38px); }
.how li { border-left: 2px solid var(--yellow); padding-left: clamp(14px, 2.4vw, 22px); max-width: 58ch; }
.how li:nth-child(2) { max-width: 44ch; }
.how b { font-family: var(--font-display); font-weight: 700; font-size: 1.12em; display: block; margin-bottom: .3em; }

/* ---------------------------------------------------------------- press for champagne
   THE authored moment. The plate is a photograph of a real brass button; the hotspot over
   the red disc is a real <button>. Pressing it fills the coupe tower beside it. */
.press { display: grid; gap: clamp(20px, 4vw, 44px); align-items: start;
  grid-template-columns: minmax(min(100%, 300px), 1fr); }
@media (min-width: 820px) { .press { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); } }

.plate { position: relative; }
.plate img { width: 100%; }
/* The hotspot. Percentages of the frame, so it tracks the photograph at every width.
   Measured against assets/img/plate-1000.webp, where the red disc centres at ~50%/60%. */
.plate__btn {
  position: absolute; left: 34%; top: 47%; width: 32%; height: 26%;
  border: 0; padding: 0; background: transparent; cursor: pointer;
  border-radius: 50%;
  transition: transform 150ms var(--ease-out), filter 150ms var(--ease-out);
}
.plate__btn::after {
  content: ""; position: absolute; inset: 22%;
  border-radius: 50%;
  box-shadow: 0 0 0 2px rgba(255,222,89,.0);
  transition: box-shadow 200ms var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .plate__btn:hover::after { box-shadow: 0 0 0 2px rgba(255,222,89,.85); }
}
.plate__btn:active { transform: scale(.965); filter: brightness(.88); }
.plate__hint {
  margin-top: 14px; font-size: 14px; letter-spacing: .18em; text-transform: uppercase;
  color: var(--yellow); font-weight: 700;
}
.press:has(.pour.is-poured) .plate__hint { opacity: 0; transition: opacity 300ms var(--ease-out); }

/* A slow ring on the hotspot until it is pressed once. This is the one place on the site
   that directs attention rather than acknowledging an action, and it earns it: the whole
   section is a button, and a button nobody notices is a section that does nothing. It stops
   for good on the first press, and it never runs for a reader who asked for less motion. */
@media (prefers-reduced-motion: no-preference) {
  .press:not(:has(.pour.is-poured)) .plate__btn::before {
    content: ""; position: absolute; inset: 20%; border-radius: 50%;
    box-shadow: 0 0 0 0 rgba(255,222,89,.55);
    animation: plate-ring 2.6s var(--ease-out) infinite;
  }
}
@keyframes plate-ring {
  0%   { box-shadow: 0 0 0 0 rgba(255,222,89,.5); }
  70%  { box-shadow: 0 0 0 18px rgba(255,222,89,0); }
  100% { box-shadow: 0 0 0 0 rgba(255,222,89,0); }
}

.pour { position: relative; background: var(--ink-2); border: var(--rule); overflow: clip; }
.pour__stack { position: relative; aspect-ratio: 4 / 3; }
@media (min-width: 820px) { .pour__stack { aspect-ratio: 1 / 1; } }
.pour__stack img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* The tower is the resting state and is fully visible: this is the photograph of the coupes
   as they were. The POUR frame is what the press reveals, wiped upward with clip-path. */
.pour__wet {
  clip-path: inset(100% 0 0 0);
  transition: clip-path 1100ms var(--ease-soft);
}
.pour.is-poured .pour__wet { clip-path: inset(0 0 0 0); }
/* A thin champagne line riding the top of the fill, so the wipe reads as liquid rather than
   as a crossfade. It is the same transition, so the two can never drift apart. */
.pour__line {
  position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: linear-gradient(90deg, transparent, var(--champagne), transparent);
  opacity: 0; transform: translateY(0);
  transition: transform 1100ms var(--ease-soft), opacity 240ms var(--ease-out);
}
.pour.is-poured .pour__line { opacity: .9; transform: translateY(-100%); }
.pour.is-settled .pour__line { opacity: 0; }

.pour__say { display: none; padding: clamp(16px, 2.6vw, 24px); border-top: var(--rule); }
.pour__say p { opacity: 0; transform: translateY(6px);
  transition: opacity 420ms var(--ease-out), transform 420ms var(--ease-out); }
.pour.is-said .pour__say p { opacity: 1; transform: none; }
.pour.is-said .pour__say p:nth-child(2) { transition-delay: 90ms; }
.pour__say p:first-child { font-family: var(--font-display); font-size: clamp(20px, 3.2vw, 28px); }
/* Before the press the resting caption holds the panel's height, so the reveal does not
   shove the page down under the reader's thumb. */
.pour__rest { padding: clamp(16px, 2.6vw, 24px); border-top: var(--rule); color: var(--paper-dim); }
.pour.is-said .pour__rest { display: none; }
.pour.is-said .pour__say { display: block; }

/* ---------------------------------------------------------------- the Fridays */
.years { display: grid; gap: clamp(16px, 2.4vw, 26px); margin-top: clamp(24px, 4vw, 40px); }
.year { display: grid; grid-template-columns: minmax(0, 1fr); gap: 8px; }
@media (min-width: 620px) { .year { grid-template-columns: 112px minmax(0, 1fr); align-items: start; gap: 16px; } }
.year__n { font-family: var(--font-display); font-weight: 700; font-size: 20px; color: var(--yellow); }
.year__n small { display: block; font-family: var(--font-body); font-weight: 400; font-size: 12px;
  letter-spacing: .1em; color: var(--paper-dim); text-transform: uppercase; }
.ticks { display: flex; flex-wrap: wrap; gap: 4px; margin: 0; padding: 0; list-style: none;
  /* The wipe. Armed by JS (.is-armed) so the strip is complete without scripts. */
  clip-path: inset(0 0 0 0); }
.ticks.is-armed { clip-path: inset(0 100% 0 0); }
.ticks.is-armed.is-run { clip-path: inset(0 0 0 0); transition: clip-path 900ms var(--ease-soft); }
.tick { width: 9px; height: 18px; background: var(--yellow); position: relative; }
/* Hollow = the archive lists the evening but will not stand behind the date. Not a Friday
   we can point at a record for, so it is not counted in the 112. */
.tick--soft { background: transparent; box-shadow: inset 0 0 0 1.5px #57503C; }
.tick:hover::after, .tick:focus-visible::after {
  content: attr(data-d); position: absolute; bottom: calc(100% + 6px); left: 50%;
  transform: translateX(-50%); white-space: nowrap;
  background: var(--paper); color: var(--ink); font-size: 11px; font-weight: 700;
  padding: 3px 7px; z-index: 5; pointer-events: none;
}
.after { margin-top: clamp(22px, 3.4vw, 34px); color: var(--paper-dim); }

/* ---------------------------------------------------------------- the archive */
/* Explicit column counts rather than auto-fill. auto-fill plus a taller portrait tile
   stretched every sibling in its row and left craters between them, and at 320 it collapsed
   to a single column - 22 full-width photographs to thumb past. minmax(0, 1fr) everywhere so
   a track can always shrink below its content: a fixed floor hangs past the edge of a 320px
   phone, and because the page clips rather than scrolls, the overflow is simply gone. */
.grid { display: grid; gap: clamp(10px, 1.6vw, 18px); grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (min-width: 600px) { .grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 900px) { .grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
.shot { margin: 0; position: relative; isolation: isolate; }
.shot button {
  display: block; width: 100%; padding: 0; border: 0; background: var(--ink-3); cursor: pointer; position: relative;
  transition: transform 220ms var(--ease-out);
}
/* One ratio for every tile, so the rows line up. The six portrait originals are cropped
   deliberately rather than accidentally: 32% from the top keeps heads and torsos, which is
   where the people are in every one of them. The whole frame is in the lightbox. */
.shot img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; position: relative; z-index: 1; }
.shot--p img { object-position: 50% 32%; }
/* The offset block, from the posters: the print sits proud of a yellow rectangle. On hover
   it settles back onto it, which is the gesture of straightening a photograph on a wall. */
.shot button::before {
  content: ""; position: absolute; inset: 10px -10px -10px 10px; background: var(--yellow);
  z-index: 0; transition: inset 260ms var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .shot button:hover::before { inset: 4px -4px -4px 4px; }
  .shot button:hover { transform: translate(2px, -2px); }
}
.shot button:active { transform: scale(.985); }
.shot figcaption { font-size: 12px; color: var(--paper-dim); margin-top: 14px; letter-spacing: .04em; }

/* the lightbox */
.lb { position: fixed; inset: 0; z-index: 120; border: 0; padding: 0; width: 100%; height: 100%;
  max-width: none; max-height: none; background: rgba(8,7,6,.94); color: var(--paper); }
.lb::backdrop { background: rgba(8,7,6,.86); }
.lb__in { position: absolute; inset: 0; display: grid; place-items: center; padding: clamp(12px, 4vw, 44px); }
.lb img { max-width: 100%; max-height: 78vh; width: auto; object-fit: contain; }
.lb__bar { position: absolute; left: 0; right: 0; bottom: 0; display: flex; justify-content: space-between;
  align-items: center; gap: 10px; padding: 14px var(--gut) calc(14px + env(safe-area-inset-bottom));
  font-size: 13px; color: var(--paper-dim); }
.lb__x { position: absolute; top: max(10px, env(safe-area-inset-top)); right: 10px; }

/* ---------------------------------------------------------------- forms */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  font-family: var(--font-body); font-weight: 700; font-size: 16px;
  background: var(--yellow); color: var(--ink); border: 0; cursor: pointer;
  padding: 15px 26px; min-height: 48px; text-decoration: none;
  transition: transform 160ms var(--ease-out), background-color 160ms var(--ease-out);
}
.btn:active { transform: scale(.97); }
@media (hover: hover) and (pointer: fine) { .btn:hover { background: #FFE679; } }
.btn[disabled] { opacity: .5; cursor: progress; }
.btn--ghost { background: transparent; color: var(--paper); box-shadow: inset 0 0 0 1.5px #57503C; }
@media (hover: hover) and (pointer: fine) { .btn--ghost:hover { background: #1D1A13; } }

.f { display: grid; gap: 14px; max-width: 48ch;
  /* minmax(0, 1fr), not the implicit auto track. The native file picker has a wide intrinsic
     size, and an auto track grows to fit its content: at 320 the photo form measured 310px in
     288px of room, 6px past the edge in both languages, invisible because the page clips.
     This track can shrink below its content, so the form always fits its column. */
  grid-template-columns: minmax(0, 1fr);
  /* the same reason as body above: the off-screen honeypot inside each form needs a
     container to be off-screen INSIDE, or it belongs to the viewport */
  position: relative; }
.f label { font-size: 13px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--paper-dim); }
.f label span { display: block; margin-bottom: 6px; }
.f input[type=text], .f input[type=email], .f textarea, .f select {
  width: 100%; font: inherit; font-size: 16px; /* 16px or iOS zooms the page on focus */
  color: var(--paper); background: var(--ink-2); border: 1px solid #3A3427;
  padding: 12px 13px; min-height: 48px; border-radius: 0;
  transition: border-color 160ms var(--ease-out);
}
.f textarea { min-height: 112px; resize: vertical; line-height: 1.5; }
.f input:focus, .f textarea:focus { border-color: var(--yellow); }
.f input::placeholder, .f textarea::placeholder { color: #7E7767; }
.f__row { display: flex; gap: 10px; align-items: flex-start; }
.f label.f__consent { display: flex; gap: 10px; align-items: flex-start; font-size: 13px;
  text-transform: none; letter-spacing: 0; font-weight: 400; color: var(--paper-dim); line-height: 1.45; }
.f label.f__consent span { display: inline; margin-bottom: 0; }
.f__consent input { margin-top: 1px; width: 22px; height: 22px; min-height: 0; flex: none; accent-color: var(--yellow); }
/* The whole consent row is the target, not the 22px box: min-height makes the row a thumb.
   Measured before: the checkbox alone, 18x18. */
.f label.f__consent { min-height: 44px; padding-block: 8px; cursor: pointer; }
/* The file picker measured 21px tall on a phone - the browser's own control, unstyled. */
.f input[type=file] { font: inherit; font-size: 14px; color: var(--paper-dim); min-height: 48px; padding: 0;
  width: 100%; min-width: 0; max-width: 100%; }
.f input[type=file]::file-selector-button {
  font: inherit; font-weight: 700; font-size: 15px; color: var(--paper); background: #1D1A13;
  border: 0; box-shadow: inset 0 0 0 1.5px #57503C; padding: 0 18px; min-height: 48px; margin-right: 12px;
  cursor: pointer; transition: background-color 160ms var(--ease-out), transform 160ms var(--ease-out);
}
.f input[type=file]::file-selector-button:active { transform: scale(.97); }
@media (hover: hover) and (pointer: fine) { .f input[type=file]::file-selector-button:hover { background: #26221A; } }
/* The honeypot. Off-screen rather than display:none, which some bots skip. */
.f__trap { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.f__status { min-height: 1.4em; font-size: 14px; }
.f__status[data-tone="ok"] { color: var(--yellow); }
.f__status[data-tone="bad"] { color: #FF9E80; }

.give { display: grid; gap: clamp(26px, 4vw, 44px); margin-top: clamp(32px, 5vw, 56px);
  grid-template-columns: minmax(min(100%, 260px), 1fr); }
@media (min-width: 780px) { .give { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.give h3 { font-size: clamp(19px, 2.4vw, 23px); margin-bottom: 8px; }
.give > div > p { color: var(--paper-dim); font-size: 15px; }

/* the list - the one action on the site, so it gets the yellow block */
.list-block { background: var(--yellow); color: var(--ink); }
.list-block .sec__h, .list-block h3 { color: var(--ink); }
.list-block .dim { color: #574A17; }
.list-block .f label { color: #574A17; }
.list-block .f input[type=text], .list-block .f input[type=email] {
  background: #FFF4C9; color: var(--ink); border-color: #C9A62A;
}
.list-block .f input::placeholder { color: #9A8433; }
.list-block .f input:focus { border-color: var(--ink); }
.list-block .f__consent { color: #574A17; }
.list-block .btn { background: var(--ink); color: var(--yellow); }
@media (hover: hover) and (pointer: fine) { .list-block .btn:hover { background: #241F16; } }
/* AFTER the filled rule, not before: both are (0,2,0), so source order decides. Written
   above it first, the ghost button was black on black and could not be read at all. */
.list-block .btn--ghost { background: transparent; color: var(--ink); box-shadow: inset 0 0 0 1.5px var(--ink); }
@media (hover: hover) and (pointer: fine) { .list-block .btn--ghost:hover { background: rgba(14,13,11,.08); } }
.list-block .f__status[data-tone="ok"] { color: #3F6B2A; font-weight: 700; }
.list-block .f__status[data-tone="bad"] { color: #8C2F12; font-weight: 700; }
.list-block :focus-visible { outline-color: var(--ink); }

/* ---------------------------------------------------------------- footer */
.foot { border-top: var(--rule); padding-block: clamp(30px, 4vw, 48px);
  padding-bottom: calc(clamp(30px, 4vw, 48px) + env(safe-area-inset-bottom)); }
.foot__in { display: flex; flex-wrap: wrap; gap: 18px 28px; align-items: center; justify-content: space-between; }
.foot a { font-size: 14px; color: var(--paper-dim); text-decoration-color: #4A4437; text-underline-offset: 3px; }
@media (hover: hover) and (pointer: fine) { .foot a:hover { color: var(--paper); } }

/* A TOUCH RULE, NOT A WIDTH RULE. These links measured 22px tall - half a comfortable thumb -
   and a `max-width: 767px` rule would have missed the case entirely, because a landscape
   iPhone is an 844px-wide viewport. It is the same phone, turned sideways. The pointer half
   catches it; the width half catches a narrow desktop window; a mouse at 1440 keeps the
   compact row the design wants. */
@media (max-width: 767px), (hover: none) and (pointer: coarse) {
  .foot__nav a { display: inline-flex; align-items: center; min-height: 44px; }
  /* Archivo 11.5px at 86% is the house credit and does not change; the target around it can. */
  .credit { min-height: 44px; }
  .foot__in { gap: 10px 24px; }
}

/* THE CREDIT LINE. Archivo 400 / 11.5px / 86%, identical on every site and in every
   language, never translated. Bottom-LEFT, so the bee reads first. */
.credit { display: inline-flex; align-items: center; gap: 7px; font-family: 'Archivo', sans-serif;
  font-weight: 400; font-size: 11.5px; opacity: .86; text-decoration: none; color: var(--paper); letter-spacing: .01em; }
.credit svg { width: 15px; height: 15px; flex: none; }
/* `.foot a` (0,1,1) outranked `.credit` (0,1,0), so the credit rendered at 14px in the dim
   footer colour - measured in the browser, after two green gates, on 24 Sep 2026. The house
   credit is Archivo 400 / 11.5px / 86% on every site, so it is pinned at the footer's own
   specificity, where nothing in the footer can quietly restyle it. */
.foot .credit { font-family: 'Archivo', sans-serif; font-weight: 400; font-size: 11.5px; opacity: .86;
  color: var(--paper); text-decoration: none; }
.credit .bee-w { transform-origin: 60% 60%; }
@media (hover: hover) and (pointer: fine) {
  .credit:hover .bee-w { animation: bee-step .5s steps(2, jump-none) infinite; }
}
@keyframes bee-step { 0%,100% { transform: rotate(-9deg) } 50% { transform: rotate(9deg) } }

/* ---------------------------------------------------------------- privacy page */
.doc { padding-block: clamp(40px, 6vw, 80px); }
.doc h1 { font-size: clamp(30px, 6vw, 50px); margin-bottom: 10px; }
.doc h2 { font-family: var(--font-display); font-size: clamp(20px, 3vw, 26px); margin: 2em 0 .5em; }
.doc { max-width: 70ch; }
.doc ul { padding-left: 1.1em; }
.doc li { margin-bottom: .45em; }
.doc .updated { color: var(--paper-dim); font-size: 14px; }
/* "Back to TGIF" stands alone on its line, so the inline-text exemption does not cover it:
   it measured 21px tall on a phone. */
.doc .back a { display: inline-flex; align-items: center; min-height: 44px; }

/* ---------------------------------------------------------------- more motion, 24 Sep 2026
   The owner asked for "lots of animation, fun". The first build held to one focal moment by
   habit, and the house bar says motion a visitor has to look for has failed. Four more pieces,
   each from this site's own material, none of them a fade-up repeated per section. Every one is
   ARMED by script from an already-finished state, so without scripts the page is complete. */

/* 1. The poster block slides out from under the hero photograph, the way the 2019 posts sit
      a yellow rectangle behind a print. ~60px of travel plus a scale, once, on arrival. */
.hero__photo.is-armed::before { transform: translate(34px, -34px) scale(.7); transform-origin: 0 100%; }
.hero__photo.is-armed.is-in::before { transform: none; transition: transform 900ms var(--ease-soft) 160ms; }

/* 2. The archive prints are tossed onto the table: each lands from a slight tilt and settles
      square onto its yellow block. A list appearing as a list, staggered 45ms, capped. */
.shot.is-armed { opacity: 0; transform: translateY(28px) rotate(var(--r, 2deg)); }
.shot.is-armed.is-in { opacity: 1; transform: none;
  transition: opacity 420ms var(--ease-out) var(--d, 0ms), transform 720ms var(--ease-soft) var(--d, 0ms); }

/* 3. Once the champagne is poured, it keeps being champagne: bubbles rise through the glass.
      Decorative, so it pauses whenever the panel is off screen. ~45px/s - seen, not noticed. */
.bubbles { position: absolute; left: 34%; right: 34%; bottom: 36%; height: 0; pointer-events: none; }
.bubbles i { position: absolute; bottom: 0; width: 6px; height: 6px; border-radius: 50%;
  border: 1px solid rgba(255, 244, 205, .85); background: rgba(255, 236, 170, .25); opacity: 0; }
.pour.is-said .bubbles i { animation: bubble 3s linear infinite; }
.pour.is-offscreen .bubbles i { animation-play-state: paused; }
.bubbles i:nth-child(1)  { left:  4%; animation-delay: .0s;  animation-duration: 2.6s; }
.bubbles i:nth-child(2)  { left: 18%; animation-delay: .7s;  width: 4px; height: 4px; }
.bubbles i:nth-child(3)  { left: 30%; animation-delay: 1.3s; animation-duration: 3.4s; }
.bubbles i:nth-child(4)  { left: 44%; animation-delay: .3s;  width: 8px; height: 8px; }
.bubbles i:nth-child(5)  { left: 55%; animation-delay: 1.9s; animation-duration: 2.8s; }
.bubbles i:nth-child(6)  { left: 64%; animation-delay: .9s;  width: 5px; height: 5px; }
.bubbles i:nth-child(7)  { left: 73%; animation-delay: 2.4s; }
.bubbles i:nth-child(8)  { left: 82%; animation-delay: 1.6s; animation-duration: 3.6s; width: 4px; height: 4px; }
.bubbles i:nth-child(9)  { left: 92%; animation-delay: .5s; }
.bubbles i:nth-child(10) { left: 38%; animation-delay: 2.1s; width: 3px; height: 3px; }
.bubbles i:nth-child(11) { left: 60%; animation-delay: 2.8s; animation-duration: 2.4s; }
@keyframes bubble {
  0%   { opacity: 0;   transform: translate(0, 0) scale(.6); }
  15%  { opacity: .9; }
  60%  { transform: translate(3px, -80px) scale(1); }
  85%  { opacity: .7; }
  100% { opacity: 0;   transform: translate(-2px, -135px) scale(1.1); }
}

/* 4. Joining the list is answered by two small glasses clinking beside the confirmation -
      the same gesture as the mark, where the reader actually is when it happens. */
.cheers { display: inline-flex; vertical-align: -5px; margin-right: 8px; width: 26px; height: 22px; }
.cheers svg { width: 100%; height: 100%; overflow: visible; }
.cheers .gl { transform-box: fill-box; transform-origin: 50% 100%; }
.cheers .gl--l { animation: cheer-l .62s var(--ease-out) .08s both; }
.cheers .gl--r { animation: cheer-r .62s var(--ease-out) .08s both; }
@keyframes cheer-l { 0% { transform: rotate(-16deg) translateX(-3px) } 55% { transform: rotate(7deg) } 100% { transform: rotate(4deg) } }
@keyframes cheer-r { 0% { transform: rotate(16deg) translateX(3px) }  55% { transform: rotate(-7deg) } 100% { transform: rotate(-4deg) } }

/* ---------------------------------------------------------------- reduced motion
   FEWER AND GENTLER, never a blanket kill. Movement goes; the state changes that tell you
   what happened stay, because a pour that never resolves is a broken button, not a calm one. */
@media (prefers-reduced-motion: reduce) {
  .pour__wet { transition: clip-path 1ms linear; }
  .pour__line { display: none; }
  .ticks.is-armed, .ticks.is-armed.is-run { clip-path: inset(0 0 0 0); transition: none; }
  .shot button, .shot button::before { transition: none; }
  @media (hover: hover) and (pointer: fine) {
    .shot button:hover { transform: none; }
    .credit:hover .bee-w { animation: none; }
  }
  .pour__say p { transition: opacity 200ms linear; transform: none; }
  /* the extras: no travel, no loop. The prints and the block are simply where they belong,
     the bubbles never rise, and the glasses beside the confirmation are drawn already clinked. */
  .hero__photo.is-armed::before, .shot.is-armed { transform: none; }
  .shot.is-armed { opacity: 1; }
  .bubbles { display: none; }
  .cheers .gl--l, .cheers .gl--r { animation: none; }
}
