/* =============================================================================
   The about page.

   Dark end to end, which is the one place on this site a whole page commits to
   the ink ground. Everywhere else the dark bands are punctuation between light
   sections; here it IS the page, so the rhythm has to come from spacing, rule
   weight and type scale rather than from ground changes.

   Every colour below resolves through the tokens that [data-ground="dark"]
   re-points on the wrapper, so there is not one dark-specific literal in here.
   ========================================================================== */

.about {
  background: var(--c-dark);
  color: var(--c-dark-ink);
}

/* A hairline between bands. Enough to keep the page from reading as one long
   scroll of black, quiet enough not to become a table. */
.about > .section + .section {
  border-block-start: 1px solid var(--c-line);
}

/* ------------------------------------------------------------------- hero */

.about__hero {
  padding-block: clamp(4rem, 11vw, 9rem) clamp(3rem, 7vw, 5.5rem);
}

.about__title {
  max-inline-size: 18ch;
  margin-block-start: var(--s-5);
}

.about__lead {
  max-inline-size: 46ch;
  margin-block-start: var(--s-6);
  color: var(--c-ink-soft);
}

/* ----------------------------------------------------------------- origin */

/*
 * Per-band rhythm.
 *
 * On a page that never changes ground, spacing is the only thing separating one
 * argument from the next — so each band gets its own breathing room rather than
 * inheriting one --section-y for all of them. The story and the refusals get
 * more air than the grids, because they are read rather than scanned.
 */
.about__origin {
  padding-block: clamp(3.5rem, 8vw, 6rem);
}

.about__values,
.about__team {
  padding-block: clamp(3rem, 7vw, 5rem);
}

.about__refuse {
  padding-block: clamp(3.5rem, 8vw, 6rem);
}


.about__split {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1fr);
  gap: var(--s-7) clamp(var(--s-6), 6vw, var(--s-9));
  align-items: start;
}

.about__prose {
  display: flex;
  flex-direction: column;
  gap: var(--s-5);
}

@media (max-width: 900px) {
  .about__split {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ---------------------------------------------------------------- mission */

/*
 * One sentence given a whole band. It is set at display size and NOT as a
 * heading, because it is the page's claim rather than a label for what follows
 * it — making it an <h2> would put a promise in the document outline where a
 * section title belongs.
 */
.about__mission {
  padding-block: clamp(4rem, 9vw, 7rem);
  background: var(--c-dark-raised);
}

.about__mission-label {
  color: var(--c-accent);
}

.about__mission-line {
  margin-block-start: var(--s-5);
  max-inline-size: 22ch;
  font-size: var(--t-title);
  font-weight: 500;
  line-height: 1.08;
  letter-spacing: -0.03em;
  text-wrap: balance;
}

/* ----------------------------------------------------------------- values */

.values {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: 1px;
  margin-block-start: var(--s-7);
  /* One gap-coloured background behind a grid of solid cells: the dividing
     lines are the gaps themselves, so there are no borders to double up where
     cells meet. */
  background: var(--c-line);
  border-block: 1px solid var(--c-line);
}

.values__item {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding: var(--s-6) var(--s-6) var(--s-7);
  background: var(--c-dark);
}

.values__num {
  font-size: clamp(2.2rem, 4vw, 3rem);
  line-height: 1;
  color: var(--c-line-strong);
}

.values__name {
  color: var(--c-accent);
}

.values__body {
  color: var(--c-ink-soft);
  font-size: var(--t-small);
}

/* ------------------------------------------------------------------- team */

.crew {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  gap: clamp(var(--s-5), 3vw, var(--s-7));
  margin-block-start: var(--s-7);
}

.crew__card {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}

.crew__portrait {
  aspect-ratio: 4 / 5;
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--c-dark-raised);
}

.crew__portrait img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

/* Just the name now. The rule above it does the separating that a role line
   used to. */
.crew__meta {
  padding-block-start: var(--s-4);
  border-block-start: 1px solid var(--c-line-strong);
}

.crew__name {
  font-size: var(--t-headline);
  line-height: 1.15;
  letter-spacing: -0.02em;
}

/* ----------------------------------------------------------------- refuse */

.refuse {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr));
  gap: var(--s-6) clamp(var(--s-6), 5vw, var(--s-8));
  margin-block-start: var(--s-7);
}

.refuse__item {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  padding-block-start: var(--s-4);
  border-block-start: 1px solid var(--c-line-strong);
}

/* A struck-through rule rather than a bullet: the list is things that do not
   happen, and the mark says so before the words do. */
.refuse__name {
  position: relative;
  font-size: 1.02rem;
  font-weight: 600;
  letter-spacing: -0.015em;
  padding-inline-start: 1.6rem;
}

.refuse__name::before {
  content: '';
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 0.62em;
  inline-size: 0.9rem;
  block-size: 1px;
  background: var(--c-clay);
}

.refuse__body {
  color: var(--c-muted);
  font-size: var(--t-small);
  padding-inline-start: 1.6rem;
}

/* -------------------------------------------------------------------- cta */

.about__cta {
  padding-block: clamp(3.5rem, 8vw, 6rem);
  background: var(--c-dark-raised);
}

.about__cta-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-6);
}

.about__cta .prose {
  max-inline-size: 40ch;
  color: var(--c-ink-soft);
  margin-block-start: var(--s-4);
}

/* =============================================================================
   The About page, animated.

   The page was type on ink end to end: correct, and inert. Everything below
   exists to give it movement WITHOUT giving it decoration — each piece draws
   something the adjacent sentence already claims, so if you removed the words
   the pictures would still say it, and if you removed the pictures nothing
   would be lost but the life.

   All of it is scroll-driven or pointer-driven through custom properties, and
   all of it collapses to a static, complete state under reduced motion.
   ========================================================================== */

/* ------------------------------------------------------------------ spine */

/*
 * One thread the length of the page.
 *
 * It sits in the shell's left gutter, so it never competes with the measure,
 * and it only appears where there is a gutter to put it in. Below that width
 * the hairline rules between sections do the same job on their own.
 */
.spine {
  position: absolute;
  inset-block: 0;
  inset-inline-start: max(var(--s-5), (100vw - var(--grid-max)) / 2 - var(--s-6));
  inline-size: 1px;
  pointer-events: none;
  display: none;
}

.spine__track,
.spine__drawn {
  position: absolute;
  inset-inline: 0;
  inset-block-start: 0;
}

.spine__track {
  block-size: 100%;
  background: var(--c-line);
}

/* The drawn part. `--drawn` is 0 to 1, written by about-page.js from the
   page's own scroll position. */
.spine__drawn {
  block-size: calc(var(--drawn, 0) * 100%);
  background: linear-gradient(
    to bottom,
    var(--c-accent) 0%,
    var(--c-accent) calc(100% - 8rem),
    transparent 100%
  );
}

@media (min-width: 1180px) {
  .about {
    position: relative;
  }

  .spine {
    display: block;
  }
}

/* ------------------------------------------------------------- hero week */

/* The words keep the hero's original measure; the week takes the room that was
   empty beside them. */
.about__hero-words {
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
}

.about__hero-inner {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  align-items: center;
  gap: clamp(var(--s-6), 5vw, var(--s-9));
}

/*
 * A working week, with the hours that exist twice marked out.
 *
 * There is no number anywhere on it, and that is deliberate: three sections
 * below, this page refuses to quote a figure it has not measured, and an hours
 * count here would be exactly that figure. The shape is the argument.
 */
.week {
  /* Small on purpose. At full column width the slots became a calendar app and
     the grid started competing with the headline instead of supporting it; the
     hero's argument is the sentence, and this is the footnote that proves it. */
  margin: 0 0 0 auto;
  inline-size: min(100%, 19.5rem);
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

.week__head,
.week__grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 4px;
}

.week__day {
  font-family: var(--font-mono);
  font-size: var(--t-nano);
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
  color: var(--c-muted);
  text-align: center;
}

/* Column-major: six slots down each day, five days across. */
.week__grid {
  grid-template-rows: repeat(6, 1fr);
  grid-auto-flow: column;
}

/*
 * An hour of the week. The ordinary ones are solid — they are the work, and on
 * an ink ground solid means present. The lift above the page's own black is
 * what separates "an hour that holds something" from "an hour that does not",
 * and it has to be enough to read at this size.
 */
.week__slot {
  aspect-ratio: 1 / 1;
  border-radius: 2px;
  background: color-mix(in oklab, var(--c-ink-soft) 34%, transparent);
  border: 1px solid transparent;
}

/*
 * The hours that are the same thing entered twice.
 *
 * `--cleared` goes 0 to 1 as the section is read. At 0 the slot is hatched and
 * carries the clay accent, which is the only warm colour on this page and is
 * doing a job: it marks the work nobody meant to create. At 1 it is an empty
 * outline — the hour is still there, it just has nothing in it any more.
 */
.week__slot[data-dupe] {
  --cleared: 0;
  /* Hatched and clay while it is still there; an empty outline once it has
     gone. The hour does not disappear — nothing here claims to give anyone
     back a Friday — it is simply no longer full of the same thing twice. */
  border-color: color-mix(
    in oklab,
    var(--c-clay-on-dark) calc(30% + (1 - var(--cleared)) * 45%),
    transparent
  );
  background:
    repeating-linear-gradient(
      -45deg,
      color-mix(in oklab, var(--c-clay-on-dark) calc((1 - var(--cleared)) * 62%), transparent) 0 2px,
      transparent 2px 5px
    );
}

.week__note {
  font-size: var(--t-small);
  color: var(--c-muted);
  text-wrap: balance;
}

@media (max-width: 980px) {
  .about__hero-inner {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s-7);
  }

  .week {
    margin-inline: 0;
    inline-size: min(100%, 22rem);
  }
}

/* --------------------------------------------------------------- handover */

/*
 * One order, retyped four times.
 *
 * Each line is the one above it plus a plausible slip, and the four answers at
 * the bottom are what the paragraph beside it says happens: four people, four
 * answers, all defensible. The slips are IN THE MARKUP rather than generated,
 * because they have to be the same every time — a random corruption would be a
 * different argument on every visit, and a weaker one.
 */
.handover {
  list-style: none;
  margin: var(--s-7) 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0;
  max-inline-size: 46rem;
}

.handover__step {
  --in: 0;
  display: grid;
  grid-template-columns: minmax(0, 13rem) minmax(0, 1fr);
  align-items: baseline;
  gap: var(--s-3) var(--s-5);
  padding-block: var(--s-4);
  border-block-end: 1px solid var(--c-line);
  opacity: calc(0.25 + var(--in) * 0.75);
  transition: opacity var(--d-slow) var(--e-out);
}

.handover__step:last-child {
  border-block-end: 0;
}

.handover__where {
  font-size: var(--t-nano);
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
  color: var(--c-muted);
}

.handover__line {
  font-family: var(--font-mono);
  font-size: var(--t-small);
  color: var(--c-ink-soft);
  font-variant-numeric: tabular-nums;
  /* The glyphs are swapped one at a time, so the box must not resize as they
     land or the whole column would jitter. */
  white-space: pre-wrap;
}

/* The last line before the answers is the one that is visibly wrong. */
.handover__step:nth-last-child(2) .handover__line {
  color: var(--c-clay-on-dark);
}

.handover__answers {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
}

.handover__answer {
  padding: var(--s-2) var(--s-4);
  border: 1px solid var(--c-line-strong);
  border-radius: var(--r-pill);
  font-family: var(--font-mono);
  font-size: var(--t-nano);
  color: var(--c-ink-soft);
  opacity: var(--in);
  transform: translateY(calc((1 - var(--in)) * 4px));
  transition:
    opacity var(--d-slow) var(--e-out),
    transform var(--d-slow) var(--e-out);
  transition-delay: calc(var(--answer, 0) * 90ms);
}

.handover__step--end .handover__where {
  color: var(--c-accent);
}

@media (max-width: 700px) {
  .handover__step {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* ------------------------------------------------------------- mission */

/*
 * The sentence resolves as it is reached.
 *
 * `--focus` runs 0 to 1 across the band, written by about-page.js. Blur is
 * expensive to animate, so it is on one short line only and the element is
 * promoted for the duration rather than for the life of the page.
 */
.about__mission-line {
  --focus: 1;
  filter: blur(calc((1 - var(--focus)) * 7px));
  opacity: calc(0.25 + var(--focus) * 0.75);
}

/* ------------------------------------------------------------- values beam */

.values {
  position: relative;
}

/*
 * The beam. It runs down the left edge of the three steps and fills as the
 * section is read, so the order the copy insists on is the order the eye is
 * given. On one column it runs down the side; on three it runs across the top,
 * because that is the reading direction in each case.
 */
.values::before,
.values::after {
  content: '';
  position: absolute;
  pointer-events: none;
}

.values::before {
  inset-block: 0;
  inset-inline-start: -1px;
  inline-size: 2px;
  background: var(--c-line);
}

.values::after {
  inset-block-start: 0;
  inset-inline-start: -1px;
  inline-size: 2px;
  block-size: calc(var(--beam, 0) * 100%);
  background: var(--c-accent);
}

/* A step that the beam has reached. */
.values__item[data-lit='true'] .values__num {
  color: var(--c-accent);
}

.values__num {
  transition: color var(--d-slow) var(--e-out);
}

@media (min-width: 49rem) {
  /* Three across: the beam is the rule above them, drawn left to right. */
  .values::before {
    inset-block: auto;
    inset-block-start: -1px;
    inset-inline: 0;
    inline-size: auto;
    block-size: 2px;
  }

  .values::after {
    inset-block-start: -1px;
    inset-inline-start: 0;
    block-size: 2px;
    inline-size: calc(var(--beam, 0) * 100%);
  }
}

/* -------------------------------------------------------------- refuse */

/*
 * Each refusal strikes itself through as it arrives.
 *
 * The rule is drawn on a pseudo-element rather than with `text-decoration`,
 * because a line-through cannot be animated and this one has to draw. It is
 * also why the heading text is wrapped: the rule has to span the WORDS, not
 * the heading's full column width, or short refusals would trail a rule into
 * empty space.
 */
.refuse__struck {
  position: relative;
  display: inline;
  background-image: linear-gradient(var(--c-accent), var(--c-accent));
  background-repeat: no-repeat;
  background-position: 0 0.62em;
  background-size: calc(var(--struck, 0) * 100%) 1px;
  transition: background-size var(--d-slow) var(--e-out);
}

.refuse__item {
  --struck: 0;
}

/* ------------------------------------------------------------ crew cards */

/*
 * The portraits, as cards that catch the light.
 *
 * The mechanics are ProfileCard's — pointer-tracked position, a real 3D tilt,
 * a damped return to centre on leave. The palette is not. That component is a
 * holographic rainbow built from six saturated hues and colour-dodge, and it
 * would be the only rainbow on a site made of ink, bone and one green — and it
 * tints the photograph, which is the one thing that must not happen here.
 *
 * So: one specular highlight, white, low opacity, tracking the pointer across
 * the surface. No hue is added at any point. The photograph underneath is
 * exactly the photograph.
 */
.crew {
  perspective: 1400px;
}

.crew__card {
  --pointer-x: 50%;
  --pointer-y: 50%;
  --from-centre: 0;
  --rx: 0deg;
  --ry: 0deg;
}

.crew__portrait {
  position: relative;
  transform: rotateX(var(--rx)) rotateY(var(--ry));
  transform-style: preserve-3d;
  will-change: transform;
  box-shadow:
    0 calc(2px + var(--from-centre) * 10px) calc(18px + var(--from-centre) * 26px)
    rgb(0 0 0 / calc(28% + var(--from-centre) * 22%));
  transition:
    transform var(--d-base) var(--e-out),
    box-shadow var(--d-base) var(--e-out);
}

/* No transition while the pointer is actually on the card, or every move would
   be chasing a 200ms tween and the tilt would lag behind the cursor. */
.crew__card[data-active='true'] .crew__portrait {
  transition: none;
}

.crew__portrait img {
  position: relative;
  z-index: 0;
}

/*
 * The highlight. `soft-light` rather than `screen` or `color-dodge`: it lifts
 * what is already there instead of washing it out, so the photograph keeps its
 * own contrast and its own colour.
 */
.crew__sheen {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  border-radius: inherit;
  mix-blend-mode: soft-light;
  opacity: var(--lit, 0);
  background: radial-gradient(
    38rem 38rem at var(--pointer-x) var(--pointer-y),
    rgb(255 255 255 / 78%) 0%,
    rgb(255 255 255 / 22%) 28%,
    transparent 62%
  );
  transition: opacity var(--d-slow) var(--e-out);
}

/* The cut edge, catching light on the side the pointer is on. This is the only
   part that carries the accent, and it never touches the image: it is a 1px
   inset ring on top of it. */
.crew__edge {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  border-radius: inherit;
  opacity: calc(var(--lit, 0) * 0.9);
  box-shadow: inset 0 0 0 1px
    color-mix(in oklab, var(--c-accent-on-dark) 55%, transparent);
  transition: opacity var(--d-slow) var(--e-out);
}

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

/*
 * Everything arrives, nothing performs.
 *
 * Each piece has a complete resting state that is also its finished state, so
 * this is mostly a matter of pinning the custom properties at 1 — the week is
 * cleared, the order has degraded, the sentence is sharp, the beam is drawn and
 * every refusal is struck. The page still makes every one of its arguments.
 */
@media (prefers-reduced-motion: reduce) {
  .spine__drawn {
    --drawn: 1;
  }

  .week__slot[data-dupe] {
    --cleared: 1;
  }

  .handover__step {
    --in: 1;
  }

  .handover__answer {
    transition: none;
    transition-delay: 0s;
  }

  .about__mission-line {
    --focus: 1;
  }

  .values {
    --beam: 1;
  }

  .refuse__item {
    --struck: 1;
  }

  .refuse__struck {
    transition: none;
  }

  .crew__portrait,
  .crew__sheen,
  .crew__edge {
    transform: none;
    transition: none;
  }

  .crew__sheen,
  .crew__edge {
    opacity: 0;
  }
}
