/*
 * resum.ai, pre-launch.
 *
 * One viewport, no scroll, no JavaScript. Standalone on purpose: it shares no
 * code with the application, so it can go up now and nothing happening in src/
 * can break it.
 *
 * The tokens are copied from the app's own design system rather than invented
 * here, so when the product opens the visual language is continuous instead of
 * a rebrand. Two rules carried over from it:
 *
 *   monospace  = a machine record
 *   no cobalt  = no model ran here
 *
 * That second one is why this page is ink and neutral and nothing else. In the
 * app, cobalt marks a screen that cost money to produce. Nothing here has ever
 * called a model, so nothing here is cobalt.
 */

:root {
  --ground: #eef0f3;
  --ink: #12181e;
  --muted: #55616d;
  --hole: #e3e8ec;

  --mono: ui-monospace, "SF Mono", "Cascadia Mono", "Roboto Mono", Menlo, Consolas,
    monospace;

  --pad: clamp(1.25rem, 4vw, 3rem);

  /* Read by .field. Swapped for a phone at the bottom of this file. */
  --columns: 28;
}

@media (prefers-color-scheme: dark) {
  :root {
    --ground: #0f1317;
    --ink: #e6ebf0;
    --muted: #96a3af;
    --hole: #212930;
  }
}

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

html {
  color-scheme: light dark;
}

body {
  position: relative;
  min-height: 100dvh;
  margin: 0;
  overflow: hidden;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--mono);
  -webkit-font-smoothing: antialiased;
}

::selection {
  background: var(--ink);
  color: var(--ground);
}

.mark {
  position: absolute;
  z-index: 2;
  top: clamp(1.5rem, 3.5vw, 2.5rem);
  left: var(--pad);
  font-size: 0.9375rem;
  font-weight: 700;
  letter-spacing: -0.03em;
}

/*
 * The record.
 *
 * The mask is what makes room for the words: the holes fade out where the type
 * sits, so the words displace the field instead of covering it. A panel behind
 * the text would read as a patch.
 */
.field {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: repeat(var(--columns), 1fr);
  grid-auto-rows: 1fr;
  gap: 0.3rem;
  padding: clamp(3.5rem, 8vw, 6rem) var(--pad);
  -webkit-mask-image: radial-gradient(
    ellipse 72% 60% at 0% 100%,
    transparent 44%,
    #000 90%
  );
  mask-image: radial-gradient(ellipse 72% 60% at 0% 100%, transparent 44%, #000 90%);
}

.field span {
  place-self: center;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--hole);
}

/*
 * A punched hole.
 *
 * Written as `.field .on`, not `.on`: the rule above it is `.field span`, which
 * scores one class and one type, and a bare `.on` scores one class and loses.
 * Two classes beat it. Every hole rendered unpunched before this was specific
 * enough, with nothing in the markup to show why.
 *
 * The cycle is 24 seconds and almost all of it is a hold. Reading the keyframes
 * as time: half a second dark, one second to punch in, then a twenty second hold
 * at full brightness, then a two second fade. The hold is the whole trick. A
 * short cycle reads as flicker no matter how gently you ease it, because the eye
 * catches the change; a long hold means the field has quietly become different
 * without you having seen it move.
 */
.field .on {
  background: var(--ink);
  animation: punch 24s cubic-bezier(0.16, 1, 0.3, 1) infinite;
}

@keyframes punch {
  0%,
  2% {
    opacity: 0;
    transform: scale(0.3);
  }
  6%,
  92% {
    opacity: 1;
    transform: scale(1);
  }
  100% {
    opacity: 0;
    transform: scale(1);
  }
}

/*
 * Stagger, in twenty four buckets one second apart across the 24 second cycle.
 *
 * Twenty four rather than twelve because 41 holes over 12 offsets moves three
 * or four of them in unison, which the eye picks up as a group blinking. At one
 * second apart it is closer to one hole at a time.
 *
 * The delays are negative, which is the one change from the mockup. A positive
 * delay pins a hole to the 0% keyframe until its turn arrives, so the field
 * starts nearly empty and takes twenty seconds to fill. That is fine in a
 * mockup you scroll past and wrong for a page someone lands on, where an empty
 * lattice reads as a page that failed to load. A negative delay starts each
 * hole partway through the cycle instead, so the first paint is already
 * mid-flight and the motion has no beginning to catch.
 */
.field .d0 { animation-delay: 0s; }
.field .d1 { animation-delay: -1s; }
.field .d2 { animation-delay: -2s; }
.field .d3 { animation-delay: -3s; }
.field .d4 { animation-delay: -4s; }
.field .d5 { animation-delay: -5s; }
.field .d6 { animation-delay: -6s; }
.field .d7 { animation-delay: -7s; }
.field .d8 { animation-delay: -8s; }
.field .d9 { animation-delay: -9s; }
.field .d10 { animation-delay: -10s; }
.field .d11 { animation-delay: -11s; }
.field .d12 { animation-delay: -12s; }
.field .d13 { animation-delay: -13s; }
.field .d14 { animation-delay: -14s; }
.field .d15 { animation-delay: -15s; }
.field .d16 { animation-delay: -16s; }
.field .d17 { animation-delay: -17s; }
.field .d18 { animation-delay: -18s; }
.field .d19 { animation-delay: -19s; }
.field .d20 { animation-delay: -20s; }
.field .d21 { animation-delay: -21s; }
.field .d22 { animation-delay: -22s; }
.field .d23 { animation-delay: -23s; }

/* The field is the composition, so it stays. It just stops moving. */
@media (prefers-reduced-motion: reduce) {
  .field .on {
    opacity: 1;
    animation: none;
  }
}

.say {
  position: absolute;
  z-index: 2;
  bottom: clamp(2.75rem, 7vw, 5rem);
  left: var(--pad);
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  /* Wide enough that the h1's own 22ch is what wraps the headline. At 42rem the
     container was narrower than the rule and broke the line first. */
  width: min(54rem, calc(100% - var(--pad) * 2));
}

/* 22ch, not 19: at 19 the second line cannot hold "forgotten most of it." and
   the headline breaks to three, stranding one word. */
.say h1 {
  margin: 0;
  max-width: 22ch;
  font-size: clamp(2rem, 5.4vw, 3.75rem);
  font-weight: 600;
  line-height: 1.04;
  letter-spacing: -0.045em;
  text-wrap: balance;
}

.say p {
  margin: 0;
  max-width: 38ch;
  color: var(--muted);
  font-size: clamp(0.95rem, 1.8vw, 1.25rem);
  line-height: 1.45;
}

/*
 * A phone turns the lattice on its side: the same 392 holes reflow into 14
 * columns and 28 rows, which keeps the spacing square instead of stretching it.
 * The mask widens because the type now takes most of the width.
 */
@media (max-width: 760px) {
  :root {
    --columns: 14;
  }

  .field {
    -webkit-mask-image: radial-gradient(
      ellipse 130% 50% at 0% 100%,
      transparent 38%,
      #000 82%
    );
    mask-image: radial-gradient(ellipse 130% 50% at 0% 100%, transparent 38%, #000 82%);
  }

  /*
   * On a phone the container is narrower than 22ch, so that rule stops doing
   * anything and comes off. The type comes down with it: at the desktop size
   * the headline needs three lines in 350px and strands a word, and at 7.2vw it
   * sets in two. Measured at 390px: two lines, 327px inside a 350px box.
   */
  .say h1 {
    max-width: none;
    font-size: clamp(1.5rem, 7.2vw, 2.4rem);
    text-wrap: pretty;
  }
}
