/* Company site.
   Black type on an off-white paper, one family (Instrument Sans), no
   decoration. The paper is a barely warm grey-white, not a cream, so
   the page stays black and white.
   The work carries the colour: each project is a panel in its own brand
   colour with its logo, photo or app screen. Everything else stays quiet.
   The mark is a black disc split by a slim prism-shaped wedge: the prism
   is the gap, not a solid triangle like Prism+'s. The company itself is
   black and white only, so the only colour on the page is the work.
   Interaction inverts rather than adding a colour: buttons turn to
   outlines, and selected text, focus rings and active fields go full ink,
   or full white on black. */

:root {
  --ink: #000;
  --paper: #f4f4f1;
  --muted: #5c5c5c;
  --line: #deded9;
  --dark-muted: #a3a3a3;
  --dark-line: rgba(255, 255, 255, 0.2);

  --font: "Instrument Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;

  --t-small: 0.875rem;
  --t-body: 1.0625rem;
  --t-h3: 1.25rem;
  --t-title: clamp(1.75rem, 1.3rem + 1.4vw, 2.5rem);
  --t-statement: clamp(1.625rem, 1.1rem + 1.9vw, 2.75rem);
  --t-hero: clamp(3.25rem, 0.4rem + 12.6vw, 14rem);

  --gutter: clamp(1rem, 0.4rem + 2.6vw, 3rem);
  --max: 90rem;
  --section: clamp(5rem, 3rem + 7vw, 10rem);
  /* The width of the page's content column, for type sized to fill it. */
  --column: calc(min(100vw, var(--max)) - 2 * var(--gutter));

  --header-h: 5.25rem;
  --marquee-size: clamp(1.125rem, 0.95rem + 0.6vw, 1.5rem);
  --marquee-pad: clamp(0.75rem, 0.6rem + 0.4vw, 1rem);

  /* One easing for everything that moves: quick to start, long to settle. */
  --ease: cubic-bezier(0.2, 0.7, 0.2, 1);
}

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: var(--header-h);
}

body {
  margin: 0;
  padding-top: var(--header-h);
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--t-body);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-x: clip;
}

img {
  display: block;
  max-width: 100%;
  height: auto;
}

a {
  color: inherit;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.22em;
}

:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 4px;
}

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

.contact ::selection,
.cta ::selection,
.site-footer ::selection {
  background: var(--paper);
  color: var(--ink);
}

h1,
h2,
h3,
p,
ul,
dl,
dd {
  margin: 0;
}

h1,
h2,
h3 {
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.1;
  text-wrap: balance;
}

p {
  text-wrap: pretty;
}

ul {
  padding: 0;
  list-style: none;
}

.wrap {
  width: 100%;
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip {
  position: absolute;
  left: var(--gutter);
  top: -4rem;
  z-index: 30;
  padding: 0.6rem 1rem;
  background: var(--ink);
  color: var(--paper);
}

.skip:focus {
  top: 0.75rem;
}

/* ---------- Ink bar ---------- */

/* The house highlight, like selected text: a black bar sweeps in behind
   words from the left, and the letters turn paper right behind its edge.
   The bar is the outer span's background and the letters are the inner
   span's, clipped to the text, so both edges move together, and each line
   of a wrapped title gets its own bar. The padding gives the bar room past
   the letters, a little more at the end, where the tight spacing pulls the
   box in. .is-inked shows it; the homepage headline has its own version
   (Hero), because its letters move one by one. */
.ink {
  background: linear-gradient(var(--ink), var(--ink)) no-repeat left / 0% 100%;
  transition: background-size 0.5s var(--ease);
}

.ink > span {
  padding-inline: 0.05em 0.09em;
  color: transparent;
  background: linear-gradient(to right, var(--paper) 50%, var(--ink) 50%) right / 200% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  transition: background-position 0.5s var(--ease);
}

.ink,
.ink > span {
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}

.ink.is-inked {
  background-size: 100% 100%;
}

.ink.is-inked > span {
  background-position: left;
}

@keyframes ink-bar {
  from {
    background-size: 0% 100%;
  }
}

@keyframes ink-letters {
  from {
    background-position: right;
  }
}

/* ---------- Header ---------- */

/* The header is a solid paper bar that stays at the top, so it reads the
   same over every tile and section. Once the page scrolls under it,
   site.js adds .is-scrolled and a hairline separates it from the page. */
.site-header {
  position: fixed;
  inset: 0 0 auto;
  z-index: 20;
  background: var(--paper);
  box-shadow: 0 1px 0 transparent;
  transition: box-shadow 0.2s ease;
}

.site-header.is-scrolled {
  box-shadow: 0 1px 0 var(--line);
}

.site-header .wrap {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 2rem;
  padding-block: 1.75rem;
}

.logo {
  font-size: 1.125rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  text-decoration: none;
}

/* The mark sits on the baseline like a letter. */
.logo-mark {
  display: inline-block;
  width: auto;
  height: 0.9em;
  margin-right: 0.4em;
  vertical-align: baseline;
}

.nav ul {
  display: flex;
  gap: clamp(1.25rem, 2.5vw, 2.5rem);
}

.nav a {
  text-decoration: none;
}

.nav a:hover {
  text-decoration: underline;
}

@media (max-width: 40rem) {
  .nav li:not(:last-child) {
    display: none;
  }
}

/* ---------- Buttons ---------- */

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 3.25rem;
  padding: 0.8rem 1.75rem;
  border: 0;
  border-radius: 999px;
  background: var(--ink);
  color: var(--paper);
  font: inherit;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease;
}

/* On hover the filled pill empties to an outline in the same colour. */
.button:hover {
  background: transparent;
  color: var(--ink);
  box-shadow: inset 0 0 0 2px currentColor;
}

.button--light {
  background: var(--paper);
  color: var(--ink);
}

.button--light:hover {
  color: var(--paper);
}

/* On black, focus rings are white whatever the text colour, so the
   white button's ring doesn't vanish. */
.contact :focus-visible,
.cta :focus-visible,
.site-footer :focus-visible {
  outline-color: var(--paper);
}

/* ---------- Hero ---------- */

/* The stage is the first screen: the headline low on it, then the
   marquee. When site.js runs the story it adds .is-story and --stretches,
   and the hero grows by --frame-scroll per stretch while the stage stays
   pinned under the header, so scrolling steps through the frames before
   the page moves on. A stretch is short, so the next frame comes soon
   and the page doesn't feel stuck. */
.hero {
  --frame-scroll: 25svh;
}

.hero-stage {
  display: flex;
  flex-direction: column;
  min-height: calc(100svh - var(--header-h));
}

.hero.is-story {
  height: calc(100svh - var(--header-h) + var(--stretches) * var(--frame-scroll));
}

.hero.is-story .hero-stage {
  position: sticky;
  top: var(--header-h);
}

.hero .wrap {
  display: flex;
  flex: 1;
  flex-direction: column;
  padding-block: clamp(1.5rem, 1rem + 1.5vw, 2.5rem);
}

/* Any height the headline doesn't fill is shared two to one above and
   below it, so it sits low but not on the floor. */
.hero .wrap::before,
.hero .wrap::after {
  content: "";
}

.hero .wrap::before {
  flex: 2;
}

.hero .wrap::after {
  flex: 1;
}

/* The size here is the most the headline may be: about a sixth of the
   screen's width, a fifth of its height, and no wider than the column
   for the shortest line, "Made to fit." (4.67em). site.js sets --fs to that
   or less, so two lines still fit above the foot, and then narrows every
   longer line to the shortest one's width (--fit), so the lines finish
   together. The padding leaves room for descenders. Without JavaScript
   there's no fitting, so the type is sized for the longest line instead. */
.hero-title {
  display: grid;
  padding-bottom: 0.15em;
  font-size: var(--fs, min(17vw, 22svh, calc(var(--column) / 4.7)));
  font-weight: 500;
  line-height: 0.9;
  letter-spacing: -0.05em;
}

.no-js .hero-title {
  font-size: min(17vw, 22svh, calc(var(--column) / 5.3));
}

/* site.js measures copies of the lines here to fit them, so their
   letters change width at once. */
.hero-probe {
  position: absolute;
  visibility: hidden;
  white-space: nowrap;
}

.hero-title .hero-probe .c {
  transition: none;
}

/* The frames sit on top of each other, so the headline keeps its place
   as they change. */
.frame {
  grid-area: 1 / 1;
  align-self: end;
}

/* Each line keeps its fitted width (--fit, set by site.js) as the stage
   scrolls away, so the lines and their ink bars stay true. */
.hero-line {
  display: block;
  font-stretch: var(--fit, 100%);
  white-space: nowrap;
}

/* Each word masks its letters, so they can rise in from below its edge
   and out past its top. The mask leaves room for descenders. */
.hero-title .w {
  display: inline-block;
  clip-path: inset(-0.1em -0.1em -0.25em -0.1em);
}

.hero-title .c {
  display: inline-block;
}

/* On a phone each line breaks in two, sized so the widest piece, "Made
   to" (3.44em), spans the column. */
@media (max-width: 40rem) {
  .hero-title {
    font-size: calc(var(--column) / 3.55);
  }

  /* Plain wrapping, not the balanced wrapping of other headings, so
     lines break where they read naturally ("Seen a / gap?"). */
  .hero-line {
    white-space: normal;
    text-wrap: wrap;
  }
}

.hero-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: 2rem 4rem;
  margin-top: clamp(2rem, 1rem + 3vw, 4.5rem);
}

/* Wide enough to stay at two lines on a laptop, leaving the headline
   the height. */
.hero-foot p {
  max-width: 52ch;
  font-size: clamp(1.25rem, 1rem + 0.7vw, 1.75rem);
  line-height: 1.3;
  letter-spacing: -0.015em;
}

/* Letter by letter, each frame rises in from below, stretching from its
   narrowest width to its full one, and leaves upwards the same way
   (scrolling back reverses it). Both lines move together, the second a
   beat behind: --l marks the second line, and the word classes and each
   letter's place in its word set --i, its place in its line. */
.frame .hero-line + .hero-line { --l: 1; }
.hero-title .w2 { --w: 4; }
.hero-title .w3 { --w: 8; }
.hero-title .w4 { --w: 12; }
.hero-title .w5 { --w: 16; }
.hero-title .c:nth-child(2) { --c: 1; }
.hero-title .c:nth-child(3) { --c: 2; }
.hero-title .c:nth-child(4) { --c: 3; }
.hero-title .c:nth-child(5) { --c: 4; }
.hero-title .c:nth-child(6) { --c: 5; }
.hero-title .c:nth-child(7) { --c: 6; }
.hero-title .c:nth-child(8) { --c: 7; }
.hero-title .c:nth-child(9) { --c: 8; }
.hero-title .c:nth-child(10) { --c: 9; }

/* Leaving is quick; arriving waits a moment so the old frame is mostly
   gone, then the whole frame lands within about a second. */
.hero-title .c {
  --i: calc(var(--w, 0) + var(--c, 0));
  transition:
    transform 0.45s var(--ease) calc(var(--i) * 6ms),
    font-stretch 0.45s var(--ease) calc(var(--i) * 6ms);
}

.hero-title .is-current .c {
  transition-duration: 0.8s;
  transition-delay: calc(0.12s + var(--l, 0) * 70ms + var(--i) * 14ms);
}

/* One word in each frame is inked (it's in [brackets] in story.json).
   It doesn't rise with the rest: the bar brings it. The build lays a copy
   of its letters over it, in paper on a black bar, and once the frame's
   other words have landed the bar wipes in from the left with the word
   already in place. As the frame leaves, the bar wipes on out to the
   right and takes the word with it, in either direction of scroll. The word's own letters only hold its
   place, and they and the copy stay still at full width (set below the
   frame rules), so the bar never meets a letter mid-move. On the last
   line the bar reaches down further, for descenders. */
.hero-title .is-inked {
  position: relative;
}

.hero-title .is-inked > .c {
  visibility: hidden;
}

.ink-cover {
  --top: 0.05em;
  --bottom: 0em;
  position: absolute;
  inset: calc(-1 * var(--top)) -0.09em calc(-1 * var(--bottom)) -0.05em;
  padding: var(--top) 0.09em var(--bottom) 0.05em;
  background: var(--ink);
  color: var(--paper);
  clip-path: inset(0 0 0 100%);
  transition: clip-path 0.35s var(--ease);
}

.hero-line:last-child .ink-cover {
  --bottom: 0.14em;
}

/* The bar arrives by animation, not transition, so it always wipes in
   from the left, whichever way the page is scrolling (see the opening
   below), and leaves to the right by transition. */
.is-current .ink-cover {
  clip-path: inset(0);
  transition: none;
}

.no-js .ink-cover {
  clip-path: inset(0);
}

/* Without JavaScript there's no story, just its last frame. */
.no-js .frame:not(:last-child) {
  display: none;
}

html:not(.no-js) .frame:not(.is-current) .c {
  transform: translateY(150%);
  font-stretch: 75%;
}

html:not(.no-js) .frame.is-past .c {
  transform: translateY(-150%);
}

/* The inked word's letters stay still at full width wherever their frame
   is (see the inked word above), so this comes after the rules that move
   them. */
html .hero-title .frame .is-inked .c {
  transform: none;
  font-stretch: inherit;
  transition: none;
  animation: none;
}

/* The page's opening: the first frame rises in on load. Once the story
   moves on, site.js adds .is-moved and the transitions take over. */
@media (prefers-reduced-motion: no-preference) {
  .hero:not(.is-moved) .is-current .c,
  .no-js .frame:last-child .c {
    animation: letter-in 1.2s var(--ease) backwards;
    animation-delay: calc(0.15s + var(--l, 0) * 150ms + var(--i) * 30ms);
  }

  .hero:not(.is-moved) .is-current .ink-cover,
  .no-js .frame:last-child .ink-cover {
    animation: ink-in 0.55s var(--ease) 0.9s backwards;
  }

  .hero.is-moved .is-current .ink-cover {
    animation: ink-in 0.5s var(--ease) 0.45s backwards;
  }

  .hero-foot {
    animation: fade 1s 0.9s ease backwards;
  }
}

@keyframes ink-in {
  from {
    clip-path: inset(0 100% 0 0);
  }
}

@keyframes letter-in {
  from {
    transform: translateY(150%);
    font-stretch: 75%;
  }
}

@keyframes fade {
  from {
    opacity: 0;
  }
}

/* ---------- Marquee ---------- */

/* The services run past in a slim black band at the foot of the first
   screen, in plain body-width type so they read easily and stay quieter
   than the headline. The track holds the list twice, so moving it half
   its width loops seamlessly. site.js nudges its speed and direction with
   the scroll. */
.marquee {
  overflow: hidden;
  padding-block: var(--marquee-pad);
  background: var(--ink);
  color: var(--paper);
}

.marquee-track {
  display: flex;
  width: max-content;
}

.marquee-group {
  display: flex;
  flex: none;
  align-items: center;
}

.marquee-group span {
  margin-right: 1em;
  font-size: var(--marquee-size);
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.01em;
  white-space: nowrap;
}

.marquee-group img {
  width: auto;
  height: calc(var(--marquee-size) * 0.75);
  margin-right: var(--marquee-size);
}

@media (prefers-reduced-motion: no-preference) {
  .marquee-track {
    animation: marquee 45s linear infinite;
  }
}

@keyframes marquee {
  to {
    transform: translateX(-50%);
  }
}

/* ---------- Sections ---------- */

.section {
  padding-block: var(--section) 0;
}

.section-title {
  font-size: var(--t-title);
  margin-bottom: clamp(2rem, 1.5rem + 2vw, 3.5rem);
}

/* Title on the left, content on the right, from tablet up. */
@media (min-width: 56rem) {
  .split .wrap {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
    gap: 3rem;
  }

  .split .section-title {
    margin-bottom: 0;
  }
}

/* ---------- Work ---------- */

/* A panel is a project's colour, logo, photo or app screen. */
.panel {
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: #f0f0ee;
}

.panel img {
  transition: transform 1s var(--ease);
}

.panel--logo {
  display: grid;
  place-items: center;
}

.panel--logo img {
  max-width: 70%;
  height: auto;
}

.panel--photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* App screens rise from the bottom edge of the panel. */
.panel--device {
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding-top: 6%;
}

.panel--device img {
  width: auto;
  height: 118%;
  filter: drop-shadow(0 30px 40px rgba(0, 0, 0, 0.28));
}

/* Desktop screens sit on the panel like a window and run off its bottom
   edge. The panel takes the screen's shape, so none of it is cropped at
   the sides on any width. */
.panel--screen {
  padding: 7% 9% 0;
}

.panel--screen img {
  width: 100%;
  height: auto;
  margin-bottom: -7%;
  border-radius: 0.5rem 0.5rem 0 0;
  box-shadow: 0 30px 60px rgba(0, 0, 0, 0.3);
}

[data-panel="nellie"] { background: #3e0a6e; }
[data-panel="prism"] { background: #0e1a2b; }
[data-panel="talent-pro"] { background: #17298f; }
[data-panel="jerrie"] { background: #2f7d32; }
[data-panel="kabbie"] { background: #f2b705; }
[data-panel="ernnie"] { background: #b3261e; }
[data-panel="black-square"] { background: #121211; }
[data-panel="gennie"] { background: #4a39c0; }
[data-panel="revvie"] { background: #13233f; }
[data-panel="chronos"] { background: #0b5d68; }

/* Featured projects (the business systems) take half the row; the rest sit four
   across beneath them. */
.projects {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: clamp(2.25rem, 1.75rem + 1.5vw, 3.5rem) clamp(1rem, 0.5rem + 1.2vw, 1.5rem);
}

.project {
  grid-column: span 3;
}

.project--featured {
  grid-column: span 6;
}

.project--featured .panel {
  aspect-ratio: 16 / 10;
}

/* A featured project on its own takes the whole row, so the row doesn't
   end in a gap. */
@media (min-width: 36.0625rem) {
  .projects:not(:has(.project--featured ~ .project--featured)) .project--featured {
    grid-column: 1 / -1;
  }

  .projects:not(:has(.project--featured ~ .project--featured)) .project--featured .panel {
    aspect-ratio: 21 / 9;
  }
}

.project > a {
  display: block;
  text-decoration: none;
}

.project a:hover .panel img {
  transform: scale(1.06);
}

.project-caption {
  display: grid;
  gap: 0.35rem;
  margin-top: 1rem;
}

.project--featured .project-caption p {
  font-size: clamp(1.25rem, 1rem + 0.6vw, 1.625rem);
  letter-spacing: -0.02em;
}

.project-caption p {
  font-size: 1.125rem;
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: -0.01em;
}

.project a:hover .project-caption p {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.22em;
}

.project-tag {
  color: var(--muted);
  font-size: var(--t-small);
}

@media (max-width: 64rem) {
  .project {
    grid-column: span 6;
  }
}

@media (max-width: 36rem) {
  .project {
    grid-column: span 12;
  }

  .project--featured .panel {
    aspect-ratio: 4 / 3;
  }
}

/* Tiles wipe up into view, their contents settling from a slight zoom.
   site.js adds .motion to the page and .is-in to each tile as it arrives,
   with --d staggering tiles that arrive together. */
.motion .project .panel {
  transition: clip-path 1.1s var(--ease) var(--d, 0ms);
}

.motion .project:not(.is-in) .panel {
  clip-path: inset(100% 0 0 0);
}

.motion .project:not(.is-in) .panel img {
  transform: scale(1.2);
}

.motion .project .project-caption {
  transition: opacity 0.8s ease calc(var(--d, 0ms) + 0.35s);
}

.motion .project:not(.is-in) .project-caption {
  opacity: 0;
}

/* ---------- Case studies ---------- */

.case-hero {
  padding-block: clamp(2rem, 1rem + 4vw, 5rem) clamp(1.5rem, 1rem + 2vw, 3rem);
}

/* The ink bar reaches below the line, so the title keeps some room
   beneath it. */
.case-hero h1 {
  padding-bottom: 0.12em;
  font-size: clamp(3.25rem, 0.5rem + 7vw, 8rem);
  font-stretch: 90%;
  line-height: 0.92;
  letter-spacing: -0.05em;
}

/* Project names stretch in on load, like the homepage headline, then
   the ink bar sweeps in behind them. */
@media (prefers-reduced-motion: no-preference) {
  .case-hero h1 {
    animation: title-in 1.2s var(--ease) backwards;
  }

  .case-hero .ink {
    animation: ink-bar 0.6s var(--ease) 0.7s backwards;
  }

  .case-hero .ink > span {
    animation: ink-letters 0.6s var(--ease) 0.7s backwards;
  }

  .case-hero p {
    animation: fade 1s 0.4s ease backwards;
  }
}

@keyframes title-in {
  from {
    opacity: 0;
    transform: translateY(0.3em);
    font-stretch: 75%;
  }
}

.case-hero p {
  margin-top: clamp(1rem, 0.5rem + 1.5vw, 2rem);
  max-width: 24ch;
  font-size: var(--t-statement);
  line-height: 1.15;
  letter-spacing: -0.025em;
  color: var(--muted);
}

.case .panel {
  aspect-ratio: 16 / 9;
}

.case .panel--device img {
  height: 125%;
}

.case .panel.panel--screen {
  aspect-ratio: auto;
}

/* On a wider screen the top of a case study fits the first screen, so
   the picture is all in view before any scrolling: the name sits beside
   its line (their last lines share a baseline), and the picture is 16:9
   but gives up height when the screen is too short for that. On a very
   short screen it keeps enough height to read, and scrolls. */
@media (min-width: 52.0625rem) {
  .case-top {
    display: flex;
    flex-direction: column;
    max-height: max(36rem, calc(100svh - var(--header-h)));
    padding-bottom: var(--gutter);
  }

  .case-hero {
    display: flex;
    flex: none;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: last baseline;
    gap: 1rem 4rem;
  }

  .case-hero p {
    margin-top: 0;
  }

  .case-top .panel {
    flex: 0 1 auto;
    height: calc(var(--column) * 9 / 16);
    min-height: 0;
    aspect-ratio: auto;
  }
}

.case-facts {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1.5rem 2rem;
  padding-block: 1.75rem;
  border-bottom: 1px solid var(--line);
}

.case-facts dt {
  margin-bottom: 0.35rem;
  color: var(--muted);
  font-size: var(--t-small);
}

.case-story {
  display: grid;
  gap: clamp(3rem, 2rem + 3vw, 5.5rem);
  padding-top: var(--section);
}

.case-story section {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  gap: 1rem 3rem;
}

.case-story h2 {
  font-size: var(--t-h3);
  letter-spacing: -0.01em;
  line-height: 1.5;
}

/* Each heading's bar draws in as it comes into view. The bar steps out
   past the letters, so the letters keep to the column. */
.case-story .ink {
  margin-left: -0.05em;
}

.case-story .ink,
.case-story .ink > span {
  transition-duration: 0.6s;
  transition-delay: calc(var(--d, 0ms) + 0.15s);
}

.motion .case-story h2:not(.is-in) .ink {
  background-size: 0% 100%;
}

.motion .case-story h2:not(.is-in) .ink > span {
  background-position: right;
}

.case-story p {
  max-width: 34ch;
  font-size: clamp(1.375rem, 1rem + 1.2vw, 2.125rem);
  line-height: 1.25;
  letter-spacing: -0.02em;
}

.case-next {
  margin-top: var(--section);
}

.case-next a {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.5rem 2rem;
  padding-block: clamp(2rem, 1.5rem + 2vw, 3.5rem);
  border-top: 1px solid var(--ink);
  text-decoration: none;
}

.case-next span {
  color: var(--muted);
}

.case-next strong {
  font-size: clamp(2.5rem, 1rem + 5vw, 6.5rem);
  font-stretch: 90%;
  font-weight: 500;
  line-height: 1;
  letter-spacing: -0.045em;
  transition: font-stretch 0.6s var(--ease);
}

/* The next project's name stretches to full width on hover. */
.case-next a:hover strong {
  font-stretch: 100%;
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 0.12em;
}

.cta {
  background: var(--ink);
  color: var(--paper);
}

.cta .wrap {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: end;
  gap: 2rem;
  padding-block: clamp(4.5rem, 3rem + 6vw, 9rem);
}

@media (max-width: 52rem) {
  .case .panel {
    aspect-ratio: 4 / 3;
  }

  .case-facts {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .case-story section {
    grid-template-columns: 1fr;
  }
}

/* ---------- About ---------- */

.statement {
  max-width: 30ch;
  font-size: clamp(1.75rem, 1.1rem + 2.3vw, 3.25rem);
  line-height: 1.15;
  letter-spacing: -0.03em;
}

/* The statement fills in word by word as it scrolls up the screen.
   site.js wraps each word and lights them in order. */
.statement .word {
  transition: color 0.3s linear;
}

.motion .statement .word {
  color: #c4c4c4;
}

.motion .statement .word.is-lit {
  color: var(--ink);
}

.statement-note {
  margin-top: 2rem;
  max-width: 30rem;
  color: var(--muted);
  font-size: 1.125rem;
}

/* ---------- What we do ---------- */

/* The rows all take the tallest row's height, so they read as an even
   set. Once title and text stack on narrow screens, each row takes its
   own height again. */
.capabilities {
  display: grid;
  grid-auto-rows: 1fr;
  border-top: 1px solid var(--ink);
}

.capabilities li {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  align-items: baseline;
  gap: 0.75rem 3rem;
  padding-block: clamp(1.5rem, 1rem + 1.5vw, 2.5rem);
}

/* Each row's rule, drawn from the left as the row arrives. */
.capabilities li::after {
  content: "";
  position: absolute;
  inset: auto 0 0;
  height: 1px;
  background: var(--line);
  transform-origin: left;
  transition: transform 1.2s var(--ease) var(--d, 0ms);
}

.capabilities h3 {
  font-size: clamp(1.875rem, 1rem + 3vw, 4rem);
  font-stretch: 92%;
  line-height: 1;
  letter-spacing: -0.04em;
  transition: font-stretch 1.2s var(--ease) var(--d, 0ms), opacity 0.8s ease var(--d, 0ms);
}

.capabilities p {
  max-width: 26rem;
  color: var(--muted);
  transition: opacity 0.8s ease calc(var(--d, 0ms) + 0.25s);
}

/* On hover a row's title inks in. Its bar reaches a little past the
   letters, so the letters keep to the column and the bar steps out. */
.capabilities .ink {
  margin-left: -0.05em;
}

@media (hover: hover) {
  .capabilities li:hover .ink {
    background-size: 100% 100%;
  }

  .capabilities li:hover .ink > span {
    background-position: left;
  }
}

.motion .capabilities li:not(.is-in)::after {
  transform: scaleX(0);
}

.motion .capabilities li:not(.is-in) h3 {
  opacity: 0;
  font-stretch: 75%;
}

.motion .capabilities li:not(.is-in) p {
  opacity: 0;
}

@media (max-width: 48rem) {
  .capabilities {
    grid-auto-rows: auto;
  }

  .capabilities li {
    grid-template-columns: 1fr;
  }
}

/* ---------- Rising headings ---------- */

/* Headings marked data-split rise word by word from behind their own
   edge, stretching out as they come. site.js wraps the words. */
.split-w {
  display: inline-block;
  clip-path: inset(-0.3em -0.15em -0.3em -0.15em);
}

.split-i {
  display: inline-block;
  transition: transform 1.1s var(--ease) var(--d, 0ms), font-stretch 1.3s var(--ease) var(--d, 0ms);
}

.motion [data-split]:not(.is-in) .split-i {
  transform: translateY(135%);
  font-stretch: 75%;
}

/* ---------- Contact ---------- */

.contact {
  margin-top: var(--section);
  background: var(--ink);
  color: var(--paper);
}

.contact .wrap {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 3.5rem clamp(3rem, 8vw, 8rem);
  padding-block: clamp(4.5rem, 3rem + 6vw, 9rem);
}

.contact h2,
.cta h2 {
  max-width: 10ch;
  font-size: clamp(2.5rem, 1.2rem + 4.4vw, 5.5rem);
  font-stretch: 92%;
  line-height: 0.98;
  letter-spacing: -0.04em;
}

.contact-copy > p {
  margin-top: 1.5rem;
  max-width: 26rem;
  color: var(--dark-muted);
  font-size: 1.125rem;
}

.contact-copy .contact-email {
  color: var(--paper);
  font-size: clamp(1.25rem, 1rem + 0.8vw, 1.625rem);
  letter-spacing: -0.01em;
}

.form {
  display: grid;
  gap: 1.75rem;
  align-content: start;
}

.field {
  display: grid;
  gap: 0.35rem;
}

.field label {
  color: var(--dark-muted);
  font-size: var(--t-small);
}

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 0.5rem 0 0.7rem;
  border: 0;
  border-bottom: 1px solid var(--dark-line);
  border-radius: 0;
  background: transparent;
  color: var(--paper);
  font: inherit;
  font-size: 1.125rem;
}

.field select {
  appearance: none;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%23fff' stroke-width='1.5'/%3E%3C/svg%3E") no-repeat right 0.25rem center;
}

.field select option {
  color: var(--ink);
}

.field textarea {
  resize: vertical;
}

.field input:focus,
.field select:focus,
.field textarea:focus {
  border-bottom-color: var(--paper);
  outline: 0;
}

.field input:focus-visible,
.field select:focus-visible,
.field textarea:focus-visible {
  box-shadow: 0 1px 0 0 var(--paper);
}

.form .button {
  justify-self: start;
  margin-top: 0.5rem;
}

.trap {
  display: none;
}

@media (max-width: 52rem) {
  .contact .wrap {
    grid-template-columns: 1fr;
  }
}

/* ---------- Footer ---------- */

.site-footer {
  background: var(--ink);
  color: var(--dark-muted);
  font-size: var(--t-small);
}

/* The name, as wide as the column ("Nellie Connect" measures 6.24em at
   full width). --f runs from 0 to 1 as the page reaches its end (set by
   site.js), stretching it from its narrowest to its full width. */
.footer-wordmark {
  padding-block: clamp(3rem, 2rem + 4vw, 6rem) clamp(1.5rem, 1rem + 2vw, 3rem);
  color: var(--paper);
  font-size: calc(var(--column) / 6.3);
  font-weight: 600;
  font-stretch: calc(75% + 25% * var(--f, 1));
  line-height: 0.8;
  letter-spacing: -0.05em;
  white-space: nowrap;
}

.footer-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: baseline;
  gap: 1rem 3rem;
  padding-block: 2rem 2.5rem;
  border-top: 1px solid var(--dark-line);
}

.footer-mark {
  color: var(--paper);
  font-weight: 600;
}

.footer-legal {
  max-width: 44rem;
}

.site-footer ul {
  display: flex;
  gap: 1.5rem;
}

.site-footer a {
  color: var(--paper);
}

@media (max-width: 56rem) {
  .footer-row {
    grid-template-columns: 1fr;
  }
}

/* ---------- Plain pages (privacy, thanks, 404) ---------- */

.page {
  padding-block: clamp(3rem, 2rem + 6vw, 7rem);
}

.page .wrap {
  max-width: 48rem;
}

.page h1 {
  font-size: clamp(2.5rem, 1.6rem + 3vw, 4rem);
  font-stretch: 92%;
  letter-spacing: -0.04em;
  line-height: 1;
  margin-bottom: 1.75rem;
}

.page h2 {
  font-size: 1.5rem;
  margin: 2.75rem 0 0.75rem;
}

.page p,
.page ul {
  max-width: 40rem;
}

.page p + p,
.page p + ul,
.page ul + p {
  margin-top: 1rem;
}

.page ul {
  padding-left: 1.25rem;
  list-style: disc;
}

.page li + li {
  margin-top: 0.4rem;
}

.page .lead {
  font-size: 1.25rem;
  color: var(--muted);
}

.page .button {
  margin-top: 2rem;
}

/* ---------- Motion preferences ---------- */

/* With reduced motion, the opening and the marquee don't run (their
   animations only exist under no-preference above), and site.js only
   runs the story, so nothing waits to be revealed. The story's frames
   swap at once instead of moving. */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }

  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    transition-delay: 0s !important;
  }
}
