/* The Posters Project — landing page */

* {
  box-sizing: border-box;
}

/* Sideways scrolling is never wanted, at any width. Set on both, because
   html alone leaves body able to overflow in some engines. */
html,
body {
  overflow-x: hidden;
}

html {
  scroll-behavior: smooth;
}

/* The arrow is the one animation on the page; anyone who asked for less
   motion gets it immediately and without the glide. */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  .onward {
    animation: none;
    opacity: 1;
    translate: -50% 0;
  }
}

body {
  margin: 0;
  min-height: 100svh;
  font-family: "Commissioner", system-ui, sans-serif;
  color: #fff6e8;
  background-color: #403914;
  display: flex;
  flex-direction: column;
}

/* The painting, under two washes weighted to the middle and to the very top
   and bottom, so the text and the Instagram pill sit on something dark while
   the edges keep their colour. The washes are heavier than a dark photograph
   would need: this is a bright picture, pale sky above and sandy path below,
   and cream text has to hold against both. */
.background {
  /* Fixed and covering the viewport, so the painting is the whole background
     at every width and at every scroll position. Never letterboxed, never
     leaving the page colour showing: cover crops whatever doesn't fit rather
     than fitting the picture inside the frame. */
  position: fixed;
  inset: 0;
  z-index: -1;
  background-color: #403914;
  background-image: radial-gradient(
      88% 68% at 50% 46%,
      rgba(22, 19, 9, 0.66) 0%,
      rgba(22, 19, 9, 0.42) 55%,
      rgba(22, 19, 9, 0.16) 100%
    ),
    linear-gradient(
      to bottom,
      rgba(22, 19, 9, 0.44) 0%,
      rgba(22, 19, 9, 0.14) 34%,
      rgba(22, 19, 9, 0.18) 62%,
      rgba(22, 19, 9, 0.54) 100%
    ),
    url("/mall.jpg");
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

main {
  /* No fixed cap: the title is sized from the viewport, so the column has
     to track the viewport too. The form is capped separately below — it
     does not want this measure. */
  max-width: 100%;
  padding-inline: clamp(14px, 2vw, 32px);
  text-align: center;
}

/* A full screen, the title and nothing else. Nothing from the section below
   shows: a half-cut line of text reads as a mistake rather than as an
   invitation. The arrow does that job instead. */
.opening {
  position: relative;
  min-height: 100svh;
  display: grid;
  place-items: center;
  /* Keeps the title off the very edges on a short window, and leaves room
     for the rule that runs off the bottom. */
  padding-block: clamp(28px, 7vh, 72px) clamp(56px, 12vh, 104px);
}

/* The way down. Set in the page's own serif rather than drawn as a chevron,
   and held back for two seconds so the title lands first — then it fades in
   and is unmissable. Large and at 0.85, because a cue nobody notices is not
   a cue; the earlier hairline failed exactly there. */
.onward {
  position: absolute;
  left: 50%;
  /* The `translate` property rather than `transform`, so the nudge keyframes
     can animate movement without clobbering the centring. */
  translate: -50% 0;
  bottom: clamp(18px, 3.5vh, 42px);
  padding: 6px 14px;
  font-family: "EB Garamond", Georgia, serif;
  font-size: clamp(30px, 3vw, 46px);
  line-height: 1;
  color: rgba(247, 238, 218, 0.85);
  text-decoration: none;
  text-shadow: 0 2px 12px rgba(22, 19, 9, 0.95);
  opacity: 0;
  /* Two stages. It fades in at 2s, once the title has had the screen to
     itself. Then from 5s — three seconds of being ignored — it starts
     nudging downward. Separate animations because they touch separate
     properties: one opacity, one translate. */
  animation: onward-in 1.2s ease 2s forwards,
    onward-nudge 2.2s ease-in-out 5s infinite;
  transition: color 0.18s ease;
}

.onward:hover,
.onward:focus-visible {
  color: #fff6e8;
}

.onward:focus-visible {
  outline: 2px solid #fff6e8;
  outline-offset: 4px;
  border-radius: 4px;
}

@keyframes onward-in {
  to {
    opacity: 1;
  }
}

/* Drops, half-recovers, drops again, then rests for most of the cycle. The
   long tail is what stops it reading as constant motion — it is a periodic
   nudge, not a bouncing widget. */
@keyframes onward-nudge {
  0%,
  55%,
  100% {
    translate: -50% 0;
  }
  15% {
    translate: -50% 10px;
  }
  30% {
    translate: -50% 3px;
  }
  42% {
    translate: -50% 7px;
  }
}

/* Once you have scrolled, you have found it; no reason to keep nudging. */
.scrolled .onward {
  animation: onward-in 1.2s ease 2s forwards;
}

/* Headline */

h1 {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(6px, 1.4vh, 14px);
}

/* One hairline under the title, the way a title page closes one. A single
   rule, not a decoration: it ends the title so the form below reads as a
   separate act rather than more of the same block. */
h1::after {
  content: "";
  display: block;
  width: clamp(80px, 15vw, 168px);
  height: 1px;
  margin-top: clamp(22px, 4.5vh, 44px);
  background-color: rgba(247, 238, 218, 0.38);
}

/* Headline type is a serif, not the UI sans. Commissioner is a modern
   geometric face, and against an eighteenth-century oil it read as a caption
   pasted onto a painting. EB Garamond is an old-style revival: high stroke
   contrast, small apertures, the shapes of engraved and letterpress
   lettering of roughly the period the picture belongs to. */
.phrase {
  font-family: "EB Garamond", Georgia, serif;
  /* Sized so the longest line, "Two Way Street", runs to roughly nine
     tenths of the measure. vw-driven, so it fills the width rather than
     sitting at a fixed size inside it. */
  font-size: clamp(40px, 14.2vw, 300px);
  font-weight: 500;
  line-height: 1.0;
  /* background-clip: text paints only inside this element's box. A
     line-height tighter than EB Garamond's natural metrics leaves the
     descenders of p and y hanging below that box, where the gradient never
     reaches — so they were being cut clean off. The padding extends the
     paint area past the glyphs; the negative margin puts the layout back
     where it was. */
  padding: 0.05em 0 0.2em;
  margin: -0.05em 0 -0.2em;
  /* Old-style serifs are drawn tighter than a sans; a hair of positive
     tracking opens them up at display size without looking gappy. */
  letter-spacing: 0.005em;
  /* Painted, not glass. Ivory sampled from the highlights in the picture —
     the white dresses and the pale sky — with the faintest warm fall-off, so
     light reads as coming from above the way it does in the painting. No
     image inside the letters: the refraction was the thing that looked
     anachronistic, and it also took the iOS background-attachment caveat
     with it. */
  color: #f7eeda;
  background-image: linear-gradient(
    176deg,
    #fffaf0 0%,
    #f7eeda 46%,
    #e9d8b6 100%
  );
  -webkit-background-clip: text;
  background-clip: text;
  /* Soft and warm rather than a crisp rim: a hard edge is what made the old
     treatment look like a UI element. */
  text-shadow: 0 2px 14px rgba(22, 19, 9, 0.6), 0 6px 40px rgba(22, 19, 9, 0.45);
}

/* background-clip needs a transparent fill to show through, but transparent
   text with no support for it is invisible text — so the colour above stays
   as the fallback and only becomes transparent where clipping works. */
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .phrase {
    color: transparent;
    /* text-shadow does not paint under clipped text in every engine. */
    text-shadow: none;
    filter: drop-shadow(0 2px 10px rgba(22, 19, 9, 0.65))
      drop-shadow(0 6px 34px rgba(22, 19, 9, 0.5));
  }
}

/* "is a" as a period title page would set it: small, letterspaced capitals
   in the same serif, holding the two phrases apart. */
.hinge {
  font-family: "EB Garamond", Georgia, serif;
  font-size: clamp(14px, 2.1vw, 26px);
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.42em;
  text-indent: 0.42em;
  color: rgba(247, 238, 218, 0.9);
  text-shadow: 0 1px 3px rgba(22, 19, 9, 0.85), 0 2px 14px rgba(22, 19, 9, 0.8);
}

/* Reflection form */

.reflect {
  /* Its own narrow measure, well inside the title's. Subordinate by width
     as much as by type size — a full-width form would compete with the
     title however small its letters were. */
  /* 88vw rather than the full width on small screens: at 320px the field
     was reaching to within 14px of both edges, which read as running off
     the screen. Desktop is unaffected — 620px binds long before 88vw. */
  max-width: min(620px, 88vw);
  margin-inline: auto;
  /* A screen of its own, with the form centred in it. Without this the page
     was only about one and a half screens tall, so arriving at the bottom
     left the title still in view and the form crammed into the last third.
     Two stages, two views. */
  min-height: 90svh;
  display: grid;
  align-content: center;
  padding-block: clamp(28px, 6vh, 64px);
}

.reflect h2 {
  /* Tight under the heading, because the note below belongs with it. */
  margin: 0 0 6px;
  /* Serif, matching the headline. Everything from the note downwards stays
     in Commissioner: form controls read better in a sans, and the split
     gives the page a clear voice for its words and another for its UI. */
  font-family: "EB Garamond", Georgia, serif;
  font-size: clamp(25px, 2.7vw, 38px);
  font-weight: 500;
  line-height: 1.26;
  letter-spacing: 0;
  text-shadow: 0 1px 2px rgba(22, 19, 9, 0.5), 0 2px 14px rgba(22, 19, 9, 0.55);
}

/* Reassurance, not a headline: dimmed so it recedes, but kept at 0.66 alpha
   with its own shadow so it stays readable over the brighter parts of the
   photo. Any fainter and it fails against the sunlit gaps in the trees. */
.note {
  margin: 0 0 22px;
  font-size: 15px;
  font-weight: 400;
  letter-spacing: 0.01em;
  color: rgba(255, 246, 232, 0.66);
  text-shadow: 0 1px 6px rgba(22, 19, 9, 0.8);
}

/* Not a box. A rule to write on, over a pool of shadow with no edge to it.
   The border, the radius and the backdrop blur were the three things making
   this read as a form sitting on top of a picture rather than part of one. */
.field {
  position: relative;
  border: none;
  /* Edgeless: dark where the text sits, fading to nothing before it reaches
     any boundary, so there is no rectangle to see. */
  /* Centred on the text, not on the box. At 50% 55% the darkest part of the
     pool sat 93px down a 169px box — below the one line of text — so the
     words floated on the pale top edge while the dark mass filled empty
     space underneath. Anchoring it at 34% seats the text on it. */
  background-image: radial-gradient(
    100% 118% at 50% 34%,
    rgba(22, 19, 9, 0.54) 0%,
    rgba(22, 19, 9, 0.34) 52%,
    rgba(22, 19, 9, 0) 100%
  );
}

/* Hidden at rest. Sitting 90-odd pixels below the text it was meant to
   underline, it read as a stray hairline rather than a rule — so it only
   appears while the field has focus, where it marks where you are typing. */
.field::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  opacity: 0;
  transition: opacity 0.2s ease;
  background-image: linear-gradient(
    to right,
    rgba(247, 238, 218, 0) 0%,
    rgba(247, 238, 218, 0.42) 14%,
    rgba(247, 238, 218, 0.42) 86%,
    rgba(247, 238, 218, 0) 100%
  );
  transition: background-image 0.2s ease;
}

.field:focus-within::after {
  opacity: 1;
}

/* Focus has to be visible without a box to outline, so the pool deepens as
   well as the rule appearing. */
.field:focus-within {
  background-image: radial-gradient(
    100% 118% at 50% 34%,
    rgba(22, 19, 9, 0.68) 0%,
    rgba(22, 19, 9, 0.44) 52%,
    rgba(22, 19, 9, 0) 100%
  );
}

textarea {
  display: block;
  width: 100%;
  /* Right padding keeps typed text clear of the arrow; the bottom padding
     lifts it off the rule. */
  /* Left inset so the first character is not flush against the edge of
     the pool it sits on. */
  padding: 12px 52px 18px 15px;
  font-family: inherit;
  font-size: 17px;
  line-height: 1.55;
  color: #fff6e8;
  background: none;
  border: none;
  /* The browser's resize grip is drawn by the browser, cannot be styled,
     and is unmistakably a web form widget. Worth losing the drag for. */
  resize: none;
  text-align: left;
  /* Typed words get the same soft halo the headline has, so they sit in the
     picture rather than on it. */
  text-shadow: 0 1px 10px rgba(22, 19, 9, 0.8);
}

textarea:focus {
  outline: none;
}

textarea::placeholder {
  color: rgba(255, 246, 232, 0.55);
}

/* A drawn mark at the end of the line, not a filled circle. The cream disc
   was the most recognisably generated component on the page. Scoped to
   .field: as a bare `button` rule this leaked onto every other button,
   which cost real debugging time twice. */
.field button {
  position: absolute;
  right: 6px;
  bottom: 9px;
  width: auto;
  height: auto;
  display: grid;
  place-items: center;
  padding: 4px;
  color: rgba(247, 238, 218, 0.72);
  background: none;
  border: none;
  cursor: pointer;
  filter: drop-shadow(0 1px 8px rgba(22, 19, 9, 0.85));
  transition: color 0.16s ease, transform 0.16s ease;
}

.field button svg {
  width: 30px;
  height: 30px;
}

.field button:hover {
  color: #fff6e8;
  transform: translateX(2px);
}

.field button:focus-visible {
  outline: 2px solid #fff6e8;
  outline-offset: 3px;
}

.field button:disabled {
  cursor: default;
  opacity: 0.55;
  transform: none;
}

/* Only "Sending..." and errors land here now — the thank-you moved into
   .done-title, which takes the question's place. Collapsed when empty, so a
   cleared status leaves no gap behind. */
.status {
  margin: 16px 0 0;
  font-size: 17px;
  font-weight: 400;
  text-shadow: 0 1px 2px rgba(22, 19, 9, 0.5), 0 2px 14px rgba(22, 19, 9, 0.55);
}

.status:empty {
  display: none;
}

/* Shown only after a response is stored, in place of the question */

.done-title {
  /* Matches .reflect h2: it takes over that slot when the question hides. */
  margin: 0 0 6px;
  font-family: "EB Garamond", Georgia, serif;
  font-size: clamp(25px, 2.7vw, 38px);
  font-weight: 500;
  line-height: 1.26;
  letter-spacing: 0;
  text-shadow: 0 1px 2px rgba(22, 19, 9, 0.5), 0 2px 14px rgba(22, 19, 9, 0.55);
}

/* Question on the left, the two boxes on the right. */
.consent {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 22px;
  margin-top: 30px;
  text-align: left;
}

.consent-question {
  margin: 0;
  font-size: 16px;
  line-height: 1.45;
  color: rgba(255, 246, 232, 0.88);
  text-shadow: 0 1px 6px rgba(22, 19, 9, 0.8);
}

.choice {
  display: flex;
  flex: none;
  gap: 20px;
}

/* The input is the accessible control; the span is what you see. Kept
   focusable rather than display:none, so keyboard focus still lands here. */
.choice-option input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

/* Words, underlined when chosen. Filled pills were two more components on
   a page trying not to look like a page. */
.choice-option span {
  display: block;
  padding: 5px 3px 7px;
  font-size: 18px;
  text-align: center;
  color: rgba(247, 238, 218, 0.62);
  border-bottom: 1px solid transparent;
  cursor: pointer;
  text-shadow: 0 1px 8px rgba(22, 19, 9, 0.85);
  transition: color 0.16s ease, border-color 0.16s ease;
}

.choice-option:hover span {
  color: rgba(247, 238, 218, 0.9);
}

.choice-option input:checked + span {
  color: #fff6e8;
  border-bottom-color: rgba(247, 238, 218, 0.75);
}

.choice-option input:focus-visible + span {
  outline: 2px solid #fff6e8;
  outline-offset: 3px;
}

/* Failures only, so it holds no space unless something went wrong. */
.consent-saved {
  margin: 12px 0 0;
  font-size: 14px;
  text-align: left;
  color: rgba(255, 246, 232, 0.66);
  text-shadow: 0 1px 6px rgba(22, 19, 9, 0.8);
}

.consent-saved:empty {
  display: none;
}

/* Stacked when there isn't room for question and boxes side by side. */
@media (max-width: 540px) {
  .consent {
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
  }
}

/* Instagram link, pinned to the bottom of the screen */

/* Last in the order — title, then question, then this. Static rather than
   fixed: pinned to the viewport it sat on top of the opening title, and a
   chip floating over a painting is exactly the look being avoided. */
.bottom {
  display: flex;
  justify-content: center;
  padding-bottom: clamp(26px, 5vh, 48px);
}

/* A mark and a word, not a chip. */
.instagram {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 8px 10px;
  font-size: 15px;
  color: rgba(247, 238, 218, 0.72);
  text-decoration: none;
  filter: drop-shadow(0 1px 8px rgba(22, 19, 9, 0.9));
  transition: color 0.18s ease;
}

.instagram:hover {
  color: #fff6e8;
}

.instagram-glyph {
  width: 20px;
  height: 20px;
}


/* A single canvas grain over the whole composition — the painting, the type,
   the rule, all of it. This is the thing that makes them read as one
   surface: separate layers stop looking separate once they share a texture.
   The picture already has craquelure; this meets it halfway.
   overlay rather than normal, so it modulates what is beneath instead of
   greying it out, and pointer-events: none so it never eats a click. */
.grain {
  position: fixed;
  inset: 0;
  z-index: 5;
  pointer-events: none;
  opacity: 0.2;
  mix-blend-mode: overlay;
  /* baseFrequency is the knob: lower makes the grain coarser. 0.8 was a fine
     film grain; 0.28 is closer to canvas weave, which is what the painting
     actually has. The tile grows with it so the pattern does not repeat
     visibly. */
  background-image: url("data:image/svg+xml;utf8,\
<svg xmlns='http://www.w3.org/2000/svg' width='340' height='340'>\
<filter id='g'><feTurbulence type='fractalNoise' baseFrequency='0.28' numOctaves='3' stitchTiles='stitch'/>\
<feColorMatrix type='saturate' values='0'/></filter>\
<rect width='340' height='340' filter='url(%23g)'/></svg>");
}

/* Held still for anyone who asked for less motion — it is static anyway,
   but the texture is also the kind of thing that bothers some readers. */
@media (prefers-reduced-motion: reduce) {
  .grain {
    opacity: 0.1;
  }
}


/* ═══════════════════════════════════════════════════════════════════════
   The index (/) — the project's own page, a second painting in the same
   style. Everything above is reused as-is; only what genuinely differs
   between the two pages is set here.
   ═══════════════════════════════════════════════════════════════════════ */

/* One screen, not two. There is no form below, so nothing to scroll to and
   no arrow. The opening therefore gives up its 100svh and simply fills
   whatever is left above the Instagram line, which keeps that line on the
   first screen instead of pushing it under the fold. */
.index main {
  flex: 1;
  display: flex;
  flex-direction: column;
}

.index .opening {
  flex: 1;
  min-height: 0;
  padding-block: clamp(28px, 7vh, 72px);
}

/* A different picture, the same treatment: fixed and covering, two washes,
   never letterboxed. Singerie: The Picnic (NGA 1957.7.4), 2400 × 1426, from
   their open-access collection at a web size rather than the 4096px original,
   and cropped in from the shaped canvas so the light mount around its rounded
   corners never shows.

   The washes are lighter than the Mall page's. That picture is bright sky and
   sunlit sand; this one is already a dark wood, and the Mall's weights buried
   it. #403914 still serves as the fallback — this painting's own darkest mass
   samples at #2b2317, near enough that using the house ground avoids
   introducing a third hue for one page. */
.index .background {
  background-image: radial-gradient(
      96% 72% at 50% 48%,
      rgba(22, 19, 9, 0.34) 0%,
      rgba(22, 19, 9, 0.2) 55%,
      rgba(22, 19, 9, 0.08) 100%
    ),
    linear-gradient(
      to bottom,
      rgba(22, 19, 9, 0.4) 0%,
      rgba(22, 19, 9, 0.1) 42%,
      rgba(22, 19, 9, 0.16) 66%,
      rgba(22, 19, 9, 0.5) 100%
    ),
    url("/picnic.jpg");
}

/* A phone gets a 1200px copy — a fifth of the bytes and indistinguishable at
   that size, so the desktop file is never compromised to suit mobile data. */
@media (max-width: 820px) {
  .index .background {
    background-image: radial-gradient(
        96% 72% at 50% 48%,
        rgba(22, 19, 9, 0.34) 0%,
        rgba(22, 19, 9, 0.2) 55%,
        rgba(22, 19, 9, 0.08) 100%
      ),
      linear-gradient(
        to bottom,
        rgba(22, 19, 9, 0.4) 0%,
        rgba(22, 19, 9, 0.1) 42%,
        rgba(22, 19, 9, 0.16) 66%,
        rgba(22, 19, 9, 0.5) 100%
      ),
      url("/picnic-mobile.jpg");
  }
}

/* The project's name, sized to fill the width the way .phrase is — but the
   factor belongs to the string, not to the style. "The Posters Project" is
   longer than "Two Way Street", so .phrase's 14.2vw overflows it; 11.5vw
   measures to ~89% of the viewport at 320, 768, 1440 and 1920, one line at
   each. nowrap is deliberate: it makes a wrong factor show up as overflow
   rather than quietly wrapping to two lines.

   No background-clip gradient here, so none of .phrase's descender-clipping
   workaround is needed — a plain drop-shadow does the lifting. */
.wordmark {
  margin: 0;
  font-family: "EB Garamond", Georgia, serif;
  font-size: clamp(34px, 11.5vw, 240px);
  font-weight: 500;
  line-height: 1.04;
  letter-spacing: 0.005em;
  white-space: nowrap;
  color: #f7eeda;
  filter: drop-shadow(0 2px 18px rgba(22, 19, 9, 0.55));
  /* Undoes the bare `h1` rule above, which stacks the friendship title's three
     spans in a flex column. This title is a single run of text and wants none
     of it — left in place it also skewed any height measurement of the title. */
  display: block;
}

/*
 * No hairline under this title. The rule under the friendship title ends it,
 * so the form below reads as a separate act — there is nothing below here, and
 * a rule closing a page that has already ended is decoration, which is what
 * this style avoids. Same reasoning as the missing arrow.
 *
 * It has to be turned off explicitly because `h1::after` is a bare element
 * selector: every h1 on the site inherits it whether or not it wants it.
 */
.wordmark::after {
  content: none;
}

/* STYLE.md asks for a genuinely visible focus state on every control, and
   there is no box to outline — so focus is built from what is left: full
   ivory plus an underline. Applies to both pages. */
.instagram:focus-visible {
  outline: none;
  color: #fff6e8;
  text-decoration: underline;
  text-underline-offset: 4px;
}
