/* The Disciples — landing page
   A ground, a tape wordmark, nothing else.

   Both looks live here. Switch with the data-theme attribute on <html>:
   omit it (or set "light") for dark tape on white, "dark" for the inverse.
   Every colour below is a token, so nothing outside this block needs to know
   which way round the page is.

   The tape is drawn at opacity 0.9 and the strips overlap, so overlaps
   composite towards the ground: brighter on black, darker on white. --tape is
   the token, not what you see — at 0.9 over white, #242424 lands at about
   #393939 for one strip and #262626 where two cross. Pure black would give the
   same numeric separation, but a dark-on-dark difference reads far weaker than
   a light-on-light one, which is why the tape is grey and not black. */

:root {
  --ground: #fff;
  --tape: #242424;
  --ink: #6f6a61;

  /* Marks made on the tape itself: they take the ground's colour, so they
     follow the theme without a second set of values. */
  --on-tape: var(--ground);
  --tape-rule: rgba(255, 255, 255, 0.28);
  --tape-press: rgba(255, 255, 255, 0.14);
  --tape-faint: rgba(255, 255, 255, 0.45);

  /* Light noise over a black page lightens; over a white one it has to
     multiply or it does nothing at all. */
  --grain-blend: multiply;
  --grain-opacity: 0.05;

  /* The lockup is wide — roughly 4.5:1 — so it is sized off the viewport
     width and left to find its own height.

     This stylesheet is mobile-first: the value here is the SMALL-SCREEN one,
     and the media query further down overrides it for wide viewports. The two
     are easy to swap by accident, and the result still looks deliberate, so
     check against the rendered sizes rather than the numbers: 176x39px at
     390px wide, 360x79px at 1440px. */
  --wordmark-width: min(45vw, 16rem);       /* mobile */
}

:root[data-theme="dark"] {
  --ground: #000;
  --tape: #fff;
  --ink: #8d8677;
  --tape-rule: rgba(0, 0, 0, 0.22);
  --tape-press: rgba(0, 0, 0, 0.08);
  --tape-faint: rgba(0, 0, 0, 0.4);
  --grain-blend: normal;
  --grain-opacity: 0.035;
}

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

html,
body { height: 100%; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: ui-monospace, "SFMono-Regular", "SF Mono", Menlo, Consolas,
               "Liberation Mono", monospace;
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

.stage {
  position: relative;
  z-index: 1;
  min-height: 100svh;
  display: grid;
  grid-template-rows: 1fr auto;
  place-items: center;
  padding: clamp(1.5rem, 5vw, 4rem);
}

@media (min-width: 60rem) {
  :root { --wordmark-width: min(26vw, 22.5rem); }   /* desktop */
}

/* ---------- wordmark ---------- */

.wordmark {
  margin: 0;
  width: var(--wordmark-width);
  align-self: center;
  /* Centres the plain-text wordmark that stands in when the frames cannot be
     fetched; the frames fill the box themselves. */
  text-align: center;
  font-size: clamp(1.5rem, 6vw, 3rem);
  font-weight: 400;
  letter-spacing: 0.1em;
}

.wordmark__frames {
  display: block;
  position: relative;
  width: 100%;
}

/* Every frame is stacked in the same box; only the active one is painted.
   The first frame stays in flow so the stack keeps its intrinsic height. */
.wordmark__frame {
  display: block;
  width: 100%;
  height: auto;
  opacity: 0;
}

.wordmark__frame + .wordmark__frame {
  position: absolute;
  inset: 0;
}

.wordmark__frame.is-active { opacity: 1; }

/* Set on the group, not the rect: each strip carries its own rotation as a
   transform attribute, and a CSS transform on the same element would replace
   it rather than compose with it. */
.tape { fill: var(--tape); }

/* The strips are pressed down one at a time on load. `is-taping` is only
   present while that runs, so a page that never gets there shows the finished
   wordmark rather than an empty box. */
.wordmark__frames.is-taping .tape-piece { opacity: 0; }

.wordmark__frames.is-taping .tape-piece.is-stuck {
  transform-box: fill-box;
  transform-origin: 50% 50%;
  animation: tape-down 130ms steps(2, end) both;
}

@keyframes tape-down {
  from { opacity: 0; transform: translateY(-9%) rotate(1.6deg); }
  to   { opacity: 1; transform: none; }
}

/* ---------- actions ---------- */

/* Two more strips of tape, stuck on after the wordmark is written. */

.actions {
  align-self: end;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: clamp(0.75rem, 3vw, 1.5rem);
}

.tape-button {
  display: inline-block;
  -webkit-appearance: none;
  appearance: none;
  border: 0;
  border-radius: 0;
  padding: 0.85em 1.9em;
  background: var(--tape);
  color: var(--on-tape);
  font: inherit;
  font-size: clamp(0.7rem, 1.5vw, 0.8rem);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;

  /* Corners pulled off true, so the strip reads as cut rather than drawn. */
  clip-path: polygon(0.6% 6%, 99.4% 0%, 100% 94%, 0% 100%);

  /* `backwards`, not `both`: the fill has to stop holding the transform once
     the strip has landed, or it would outrank both the resting angle below and
     the hover state. Before the delay elapses the from-state still hides it. */
  animation-duration: 150ms;
  animation-timing-function: steps(2, end);
  animation-fill-mode: backwards;
  transition: transform 200ms ease-out;
}

/* Laid down after the wordmark finishes writing itself, one then the other.
   Each rests at its own angle, so each gets its own landing. Keyed by class
   rather than position: the left button is wrapped in a form, so its place in
   the row is not something the stylesheet should depend on. */
.tape-button--left {
  transform: rotate(-1.4deg);
  animation-name: tape-button-a;
  animation-delay: 2.25s;
}

.tape-button--right {
  transform: rotate(1.1deg);
  animation-name: tape-button-b;
  animation-delay: 2.4s;
}

@keyframes tape-button-a {
  from { opacity: 0; transform: translateY(-70%) rotate(-4.2deg); }
  to   { opacity: 1; transform: rotate(-1.4deg); }
}

@keyframes tape-button-b {
  from { opacity: 0; transform: translateY(-70%) rotate(3.9deg); }
  to   { opacity: 1; transform: rotate(1.1deg); }
}

.tape-button:hover,
.tape-button:focus-visible {
  transform: rotate(0deg) translateY(-2px);
}

.tape-button:focus-visible {
  outline: 2px solid var(--tape);
  outline-offset: 5px;
}

.tape-button:active {
  transform: rotate(0deg) translateY(0);
}

/* ---------- mailing list ---------- */

.signup {
  position: relative;
  margin: 0;
}

/* The field takes the button's place, and is one more strip of the same tape:
   same white, same cut corners, same angle. */
.signup__field {
  display: flex;
  align-items: stretch;
  background: var(--tape);
  transform: rotate(-1.4deg);
  clip-path: polygon(0.4% 5%, 99.6% 0%, 100% 95%, 0% 100%);
  animation: tape-button-a 150ms steps(2, end) backwards;
}

.signup__input,
.signup__submit {
  /* Safari otherwise paints these with native macOS chrome — a rounded field
     and a blue capsule button — ignoring the background and border below. */
  -webkit-appearance: none;
  appearance: none;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--on-tape);
  font: inherit;
  font-size: clamp(0.7rem, 1.5vw, 0.8rem);
  letter-spacing: 0.16em;
  padding: 0.85em 1.4em;
}

.signup__input {
  width: min(46vw, 15rem);
  text-transform: none;
}

.signup__input::placeholder {
  color: var(--tape-faint);
  text-transform: none;
}

.signup__input:focus { outline: none; }

.signup__submit {
  border-left: 1px solid var(--tape-rule);
  text-transform: uppercase;
  cursor: pointer;
  transition: background-color 160ms ease-out;
}

.signup__submit:hover,
.signup__submit:focus-visible { background: var(--tape-press); }

.signup__submit:disabled { opacity: 0.45; cursor: default; }

/* The confirmation is another strip of tape rather than a line of text, so the
   form keeps its footprint: hiding the field without it would collapse the
   form to nothing and let the Contact button slide underneath the message. */
.signup__done {
  margin: 0;
  padding: 0.85em 1.9em;
  background: var(--tape);
  color: var(--on-tape);
  font-size: clamp(0.7rem, 1.5vw, 0.8rem);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  white-space: nowrap;
  transform: rotate(-1.4deg);
  clip-path: polygon(0.4% 5%, 99.6% 0%, 100% 95%, 0% 100%);
}

/* Sits above the row, so saying something does not shift the buttons. */
.signup__status {
  position: absolute;
  bottom: calc(100% + 0.9rem);
  left: 50%;
  transform: translateX(-50%);
  margin: 0;
  font-size: clamp(0.65rem, 1.4vw, 0.75rem);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--ink);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  border: 0;
  white-space: nowrap;
}

/* display on .signup__field would otherwise beat the attribute. */
[hidden] { display: none !important; }

/* ---------- film grain ---------- */

.grain {
  position: fixed;
  inset: -50%;
  z-index: 2;
  pointer-events: none;
  opacity: var(--grain-opacity);
  mix-blend-mode: var(--grain-blend);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  animation: grain-shift 700ms steps(1, end) infinite;
}

@keyframes grain-shift {
  0%   { transform: translate(0, 0); }
  25%  { transform: translate(-3%, 2%); }
  50%  { transform: translate(2%, -3%); }
  75%  { transform: translate(-2%, -2%); }
  100% { transform: translate(3%, 1%); }
}

/* ---------- reduced motion ---------- */

@media (prefers-reduced-motion: reduce) {
  .wordmark__frames.is-taping .tape-piece,
  .wordmark__frames.is-taping .tape-piece.is-stuck {
    opacity: 1;
    animation: none;
  }

  .tape-button,
  .signup__field {
    animation: none;
  }

  .grain { animation: none; }
}
