/* ==========================================================================
   Nadiia Nikiforova, portfolio
   Tokens, then base, then components.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Tokens
   -------------------------------------------------------------------------- */

:root {
  /* Colour */
  --paper: #FFFFFF;   /* was #FBFBF9 */
  --surface: #FFFFFF;
  --ink: #0B0B0C;
  --ink-muted: #6B6B70;
  --rule: #E8E8E4;
  --rule-dot: #D5D5CF;
  --tick: #C4C4BE;
  --fill: #F2F1ED;
  --accent: #1E4DD8;

  /* Warm highlight, for the one block that must not read as white-on-white */
  --highlight: #FCF3D6;
  --highlight-border: #E6D294;
  --highlight-rule: #EEE1AE;

  /* One gradient per project, shared by its cover card and its case header */
  --grad-healthcare: linear-gradient(142deg, #D3A6BC 0%, #BFAFD6 50%, #9DBBDF 100%);  /* was #8E6FE0/#7F8FE8/#6FB3E4 */
  --grad-documents:  linear-gradient(142deg, #5A6478 0%, #4A4F92 52%, #3E45A8 100%);
  --grad-govtech:    linear-gradient(142deg, #1F4E8C 0%, #2C7F9E 50%, #4FB3A4 100%);
  --grad-system:     linear-gradient(142deg, #B5AE9C 0%, #A9BC9A 50%, #93C892 100%);
  --grad-printlab:   linear-gradient(142deg, #C6DE7E 0%, #E4EFB8 55%, #FBFBF2 100%);

  /* Type */
  --font-display: "General Sans", "Geist", system-ui, sans-serif;
  --font-body: "Inter", system-ui, -apple-system, sans-serif;

  /* Seven steps, and nothing outside them. Hierarchy comes from colour and
     space, not from inventing another size. */
  --size-display: clamp(2.5rem, 1.35rem + 4.9vw, 4.75rem);
  --size-title: clamp(1.75rem, 1.35rem + 1.7vw, 2.5rem);
  --size-subtitle: clamp(1.375rem, 1.2rem + 0.75vw, 1.75rem);
  --size-lead: clamp(1.1875rem, 1.05rem + 0.6vw, 1.5rem);
  --size-body: 1.0625rem;      /* 17px */
  --size-small: 0.9375rem;     /* 15px */
  --size-mono: 0.75rem;        /* 12px */

  /* Fine grain, the thing that makes a flat gradient read as a surface */
  --grain: 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.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");

  /* Weight. Hierarchy comes from size and colour, not weight. */
  --weight-hero: 500;
  --weight-heading: 500;
  --weight-name: 600;

  --track-hero: -0.03em;
  --track-section: -0.02em;
  --track-mono: 0.08em;

  --leading-body: 1.65;
  --leading-display: 1.04;

  /* Geometry */
  --radius-card: 20px;
  --radius-cover: 28px;
  --radius-pill: 999px;

  --measure-content: 1120px;
  --measure-prose: 720px;
  --gutter: 24px;

  /* Rhythm */
  --section-gap: 120px;

  /* Motion */
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --dur: 400ms;
}

@media (max-width: 760px) {
  :root {
    --section-gap: 72px;
    --gutter: 20px;
  }
}

/* --------------------------------------------------------------------------
   Base
   -------------------------------------------------------------------------- */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--size-body);
  line-height: var(--leading-body);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: var(--weight-heading);
  line-height: var(--leading-display);
  letter-spacing: var(--track-section);
  margin: 0;
  text-wrap: balance;
}

p { margin: 0; }

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

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

a { color: inherit; }

a:focus-visible,
button:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}

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

/* --------------------------------------------------------------------------
   Utility type
   -------------------------------------------------------------------------- */

/* Small labels: plain grey text in normal case. No capitals, no tracking. */
.mono {
  font-family: var(--font-body);
  font-size: var(--size-small);
  font-weight: 400;
  line-height: 1.5;
  color: var(--ink-muted);
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--ink);
  color: var(--paper);
  padding: 12px 20px;
  border-radius: var(--radius-pill);
  font-family: var(--font-body);
  font-size: var(--size-small);
  text-decoration: none;
  z-index: 10;
}

.skip-link:focus {
  left: var(--gutter);
  top: 16px;
}

/* --------------------------------------------------------------------------
   Layout
   -------------------------------------------------------------------------- */

.shell {
  width: 100%;
  max-width: calc(var(--measure-content) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

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

.section + .section { padding-top: 0; }

.section__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  padding-bottom: 40px;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 48px;
}

.section__head h2 {
  font-size: var(--size-title);
  font-weight: var(--weight-heading);
}

/* --------------------------------------------------------------------------
   Site header
   -------------------------------------------------------------------------- */

.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding-block: 28px;
}

.site-header__name {
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: var(--weight-name);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
}

.site-nav ul {
  display: flex;
  gap: 28px;
}

.site-nav a {
  text-decoration: none;
  font-size: var(--size-small);
  color: var(--ink-muted);
  transition: color 180ms var(--ease);
}

.site-nav a:hover { color: var(--ink); }

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

.hero { padding-block: clamp(40px, 6vw, 88px) 0; }

.hero__title {
  font-size: var(--size-display);
  font-weight: var(--weight-hero);
  letter-spacing: var(--track-hero);
  max-width: 15ch;
}

.hero__lead {
  margin-top: 28px;
  max-width: 58ch;
  font-size: var(--size-lead);
  /* Two tones, one size: the opening runs in ink, the rest recedes to grey.
     This is the hierarchy device from the references. */
  color: var(--ink-muted);
  line-height: 1.45;
  text-wrap: pretty;
}

/* The opening clause, held in ink while the rest of the sentence recedes */
.lead-ink { color: var(--ink); }

.hero__facts {
  font-size: var(--size-small);
  color: var(--ink-muted);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 20px;
  margin-top: 40px;
}

.hero__facts li {
  display: flex;
  align-items: center;
  gap: 20px;
}

.hero__facts li + li::before {
  content: "";
  width: 3px;
  height: 3px;
  border-radius: var(--radius-pill);
  background: var(--rule);
  margin-left: -8px;
}

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

.work-grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 80px 40px;
}

.card { position: relative; }


/* Two equal columns. Every cover the same width, so the text measure under
   it means the same thing on every card. */
.card { grid-column: span 6; }

/* Spacing sets the grouping. Title and its description are one unit at 8px;
   the decision is a separate statement at 20px; the metadata is separate
   again at 26px. No reserved heights: the title now sits directly under the
   cover, so titles align across a row on their own. */

.card__title {
  margin-top: 24px;
  font-size: var(--size-subtitle);
  font-weight: var(--weight-heading);
}

.card__summary {
  margin-top: 8px;
  font-size: var(--size-small);
  line-height: 1.45;
  color: var(--ink-muted);
}

/* Underlined all along, in an invisible colour, so hover fades the line in
   instead of snapping it on. */
.card__title a {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: transparent;
  text-underline-offset: 4px;
  transition: text-decoration-color var(--dur) var(--ease);
}

.card:hover .card__title a,
.card:focus-within .card__title a {
  text-decoration-color: currentColor;
}

.card__title a::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--radius-cover);
}

.card__decision {
  margin-top: 20px;
  font-size: var(--size-small);
  /* Ink, not grey: this is the substance of the card, and it needs to read
     differently from the description above it. */
  color: var(--ink);
  text-wrap: pretty;
}

.card__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 18px;
  margin-top: 26px;
}

.card__meta li {
  display: flex;
  align-items: center;
  gap: 18px;
}

.card__meta li + li::before {
  content: "";
  width: 3px;
  height: 3px;
  border-radius: var(--radius-pill);
  background: var(--rule);
  margin-left: -7px;
}

/* Cover ------------------------------------------------------------------- */

.cover {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-cover);
  aspect-ratio: 16 / 10;
  height: auto;
  background-size: 160% 160%;
  background-position: 0% 0%;
  /* Resting state: the colour is all there, just veiled. Against a near-white
     page, opacity washes the tile toward the paper instead of draining it,
     so it stays a picture rather than becoming a switched-off control.
     Hover brings it to full strength. */
  opacity: 0.62;
  transition:
    transform var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease),
    opacity var(--dur) var(--ease),
    background-position var(--dur) var(--ease);
}



.card:hover .cover,
.card:focus-within .cover {
  transform: translateY(-4px);
  opacity: 1;
  background-position: 100% 100%;
  box-shadow: 0 18px 44px -18px rgba(11, 11, 12, 0.28);
}

/* No hover to reveal it with, so the colour is simply there. */
@media (hover: none) {
  .cover { opacity: 1; }
}

/* Artwork fills the cover. No gradient, no grain: the painting has its own. */
.cover__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.cover--image::after { display: none; }

/* Grain. Sits over the gradient, under the screenshot. */
.cover::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background-image: var(--grain);
  opacity: 0.42;
  mix-blend-mode: overlay;
  pointer-events: none;
}

/* Gradients, one per project */
.cover--healthcare { background-image: var(--grad-healthcare); }
.cover--documents  { background-image: var(--grad-documents); }
.cover--govtech    { background-image: var(--grad-govtech); }
.cover--system     { background-image: var(--grad-system); }
.cover--printlab   { background-image: var(--grad-printlab); }

/* Screenshot inset, angled and cropped */
.cover__inset {
  position: absolute;
  z-index: 2;
  left: 16%;
  right: -8%;
  bottom: -12%;
  aspect-ratio: 16 / 10;
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.7);
  background: rgba(255, 255, 255, 0.5);
  transform: rotate(-2.5deg);
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  padding: 12px 14px;
}



.cover__inset .mono {
  color: rgba(11, 11, 12, 0.45);
}

.cover__inset--image,
.case-hero__shot--image { padding: 0; }

/* A phone screenshot is portrait. Cropping it into the landscape frame would
   show a band of table and nothing else, so it gets a frame of its own shape,
   standing in front of the wide shot. */
.cover__inset.cover__inset--phone {
  /* Left of the wide shot, overlapping its edge, mirroring the header. */
  left: 6%;
  right: auto;
  bottom: -14%;
  width: 25%;
  aspect-ratio: 440 / 758;
  border-radius: 16px;
  z-index: 3;
}

.cover__inset--image img,
.case-hero__shot--image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 0 0;
  border-radius: inherit;
}

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

.about {
  display: grid;
  /* Portrait first, prose second. Order is set here rather than in the source
     so the markup keeps prose before image for screen readers. */
  grid-template-columns: 320px minmax(0, 1fr);
  gap: 64px;
  align-items: start;
}

.about__portrait { grid-column: 1; grid-row: 1; }

.about__prose { grid-column: 2; grid-row: 1; }

.about__prose {
  max-width: var(--measure-prose);
  display: grid;
  gap: 24px;
}

.about__prose p { text-wrap: pretty; }

.about__portrait {
  border-radius: 50%;
  overflow: hidden;
  border: 1px solid var(--rule);
  aspect-ratio: 1;
}

/* The portrait is already cropped square to the exact framing, so it fills the
   round frame 1:1 with no extra cropping. */
.about__portrait img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* --------------------------------------------------------------------------
   Placeholder blocks, replaced by real images later
   -------------------------------------------------------------------------- */

.placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--fill);
  background-image:
    repeating-linear-gradient(
      135deg,
      rgba(11, 11, 12, 0.028) 0 1px,
      transparent 1px 9px
    );
}

/* --------------------------------------------------------------------------
   Case study pages
   -------------------------------------------------------------------------- */

/* One rail width shared by the header, the decision block and every section,
   so every mono label on the page sits on the same vertical line. */
:root {
  --rail: 200px;
  --rail-gap: 56px;
  --measure-case: 940px;
}

/* 1. Gradient header. The screenshot is part of it, same language as the
   cover cards, so the page you land on echoes the card you clicked. */

.case-hero {
  position: relative;
  overflow: hidden;
  display: grid;
  align-content: end;
  gap: 14px;
  min-height: clamp(240px, 26vw, 380px);
  margin-top: 8px;
  padding: clamp(32px, 4vw, 52px) clamp(28px, 4vw, 48px) clamp(28px, 3vw, 40px);
  border-radius: var(--radius-cover);
}

.case-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background-image: var(--grain);
  opacity: 0.42;
  mix-blend-mode: overlay;
  pointer-events: none;
}

.case-hero--healthcare { background-image: var(--grad-healthcare); }
.case-hero--documents  { background-image: var(--grad-documents); }
.case-hero--govtech    { background-image: var(--grad-govtech); }
.case-hero--system     { background-image: var(--grad-system); }
.case-hero--printlab   { background-image: var(--grad-printlab); }

/* Text stays clear of the screenshot */
.case-hero > *:not(.case-hero__shot):not(.case-hero__bg) {
  position: relative;
  z-index: 2;
  max-width: 54%;
}

.case-hero h1 {
  font-size: var(--size-title);
  font-weight: var(--weight-hero);
  letter-spacing: var(--track-hero);
  line-height: 1.08;
}

.case-hero__summary {
  font-size: var(--size-body);
  color: rgba(11, 11, 12, 0.72);
}

.case-hero__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 28px;
  margin-top: 8px;
  padding-top: 24px;
  border-top: 1px solid rgba(11, 11, 12, 0.16);
}

.case-hero__meta li { color: rgba(11, 11, 12, 0.6); }

/* Artwork as the header itself. The painting's empty field carries the type,
   so the gradient underneath only shows if the image fails to load. */
.case-hero__bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 45%;
  z-index: 0;
}

/* No grain over a painting: it has its own surface. */
.case-hero--art::after { display: none; }

/* Scrim. White type needs something to sit on, and these plates are light —
   bare, they measure under 2.5:1. Weighted to the bottom-left, where the type
   is, so the screenshot panel on the right stays clear. */
.case-hero--art::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    linear-gradient(to top, rgba(8, 10, 12, 0.76) 0%, rgba(8, 10, 12, 0.44) 46%, rgba(8, 10, 12, 0) 82%),
    linear-gradient(to right, rgba(8, 10, 12, 0.52) 0%, rgba(8, 10, 12, 0) 58%);
}

.case-hero--art > h1 { color: var(--paper); }

.case-hero--art > .case-hero__summary { color: rgba(255, 255, 255, 0.86); }

.case-hero--art > .case-hero__meta { border-top-color: rgba(255, 255, 255, 0.28); }

.case-hero--art > .case-hero__meta li { color: rgba(255, 255, 255, 0.78); }

/* Deep gradients. Ink on the documents and govtech headers measures 3.3:1
   and 2.4:1, under the 4.5:1 floor, so those two flip to light type. */
.case-hero--dark h1 { color: var(--paper); }

.case-hero--dark .case-hero__summary { color: rgba(255, 255, 255, 0.8); }

.case-hero--dark .case-hero__meta { border-top-color: rgba(255, 255, 255, 0.24); }

.case-hero--dark .case-hero__meta li { color: rgba(255, 255, 255, 0.7); }

/* Type keeps to the painting's empty left field. Same :not() shape as the
   base rule so source order decides, not specificity. */
.case-hero--art > *:not(.case-hero__shot):not(.case-hero__bg) { max-width: 46%; }

.case-hero__shot {
  position: absolute;
  z-index: 2;
  right: -5%;
  bottom: -11%;
  width: 46%;
  aspect-ratio: 16 / 10;
  transform: rotate(-2.5deg);
  border: 1px solid rgba(255, 255, 255, 0.7);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.5);
  display: flex;
  align-items: flex-start;
  padding: 14px 16px;
}

.case-hero__shot .mono { color: rgba(11, 11, 12, 0.42); }

/* Portrait variant, defined after the base so it actually wins. */
.case-hero__shot.case-hero__shot--phone {
  left: auto;
  right: 30%;
  bottom: -12%;
  width: 12%;
  min-width: 96px;
  aspect-ratio: 440 / 758;
  border-radius: 16px;
  z-index: 3;
}

/* On wide screens the hero has room to spare, so the shot grows to fill more
   of the cover. It grows rightward into the bleed (right offset opens up in
   step with the width) so it stays clear of the heading on the left. Scoped
   to the case-study hero only — the home-page covers use .cover, not this. */
@media (min-width: 1200px) {
  .case-hero__shot {
    width: 54%;
    right: -9%;
  }

  /* The portrait variant keeps its own proportions; only the wide shot grows. */
  .case-hero__shot.case-hero__shot--phone {
    width: 12%;
    right: 34%;
  }
}

/* 2. Decision block, the signature element. No card. It sits on the page at
   display size, on the same rail as every other label, and the heavy top rule
   announces it. */

.decision {
  margin-top: clamp(56px, 8vw, 100px);
  border-top: 1px solid var(--ink);
}

.decision > div {
  display: grid;
  grid-template-columns: var(--rail) minmax(0, 1fr);
  gap: var(--rail-gap);
  padding: clamp(30px, 4vw, 44px) 0;
  border-bottom: 1px solid var(--rule);
}

.decision dt { padding-top: 8px; }

.decision dd {
  margin: 0;
  max-width: 46ch;
  font-family: var(--font-display);
  font-size: var(--size-lead);
  font-weight: var(--weight-heading);
  letter-spacing: -0.015em;
  line-height: 1.36;
  text-wrap: pretty;
}

/* 3. Summary. Project metadata in columns under the decision, in the
   reference language: a dotted rule closing the block, hairline dividers
   ticked at both ends, and the outcome lifted into a soft filled panel
   rather than left as one more line of text. */

.summary {
  position: relative;
  margin-top: clamp(48px, 6vw, 72px);
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

/* Dotted, not solid. The reference uses it to close a metadata block. */
.summary::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
  background-image: repeating-linear-gradient(
    to right,
    var(--rule-dot) 0 2px,
    transparent 2px 7px
  );
}

.summary__fact {
  position: relative;
  display: grid;
  gap: 12px;
  align-content: start;
  padding: 30px 36px;
  background-image: repeating-linear-gradient(
    to right,
    var(--rule-dot) 0 2px,
    transparent 2px 7px
  );
  background-size: 100% 1px;
  background-position: 0 100%;
  background-repeat: no-repeat;
}

.summary__fact:first-child { padding-left: 0; }
.summary__fact:last-of-type { padding-right: 0; }

.summary__fact + .summary__fact { border-left: 1px solid var(--rule); }

/* Tick marks crossing each divider, top and bottom */
.summary__fact + .summary__fact::before,
.summary__fact + .summary__fact::after {
  content: "";
  position: absolute;
  left: -5px;
  width: 9px;
  height: 1px;
  background: var(--tick);
}

.summary__fact + .summary__fact::before { top: 0; }
.summary__fact + .summary__fact::after { bottom: 0; }

.summary dd {
  margin: 0;
  text-wrap: pretty;
}

/* The outcome, lifted into a filled panel. Label above the sentence, which
   runs to a 62ch measure so it fills most of the panel without the line
   getting too long to track. */
.summary__outcome {
  grid-column: 1 / -1;
  display: grid;
  gap: 16px;
  margin-top: 40px;
  padding: clamp(28px, 3.4vw, 40px);
  background: var(--fill);
  border-radius: var(--radius-card);
}

.summary__outcome dd {
  max-width: 62ch;
  font-family: var(--font-display);
  font-size: var(--size-lead);
  font-weight: var(--weight-heading);
  letter-spacing: -0.015em;
  line-height: 1.38;
}

/* 4. Body. Heading in a left rail, prose in the middle, figures break wider. */

.case-body { margin-top: clamp(56px, 8vw, 88px); }

.case-section {
  display: grid;
  grid-template-columns: var(--rail) minmax(0, 640px) 1fr;
  column-gap: var(--rail-gap);
  row-gap: 20px;
  padding-block: clamp(48px, 6vw, 72px);
  border-top: 1px solid var(--rule);
}

.case-section > * { grid-column: 2; }

.case-section > h2 {
  grid-column: 1;
  /* Spanning two rows keeps a wrapped rail heading from inflating row 1 and
     opening a gap after the first paragraph. */
  grid-row: 1 / span 2;
  align-self: start;
  position: sticky;
  top: 28px;
  font-size: var(--size-lead);
  line-height: 1.18;
  text-wrap: pretty;
}

.case-section > h3 {
  font-size: var(--size-body);
  margin-top: 16px;
}

.case-section p { text-wrap: pretty; }

.case-section ul {
  list-style: disc;
  padding-left: 22px;
  display: grid;
  gap: 14px;
}

.case-section li::marker { color: #C4C4BE; }

.case-section strong { font-weight: 600; }

/* A line already emphasised in the copy, set at display size. Same words. */
.callout {
  grid-column: 2 / -1;
  margin-block: 16px;
  font-family: var(--font-display);
  font-size: var(--size-subtitle);
  font-weight: var(--weight-heading);
  letter-spacing: var(--track-section);
  line-height: 1.28;
  text-wrap: balance;
}

.callout strong { font-weight: inherit; }

/* Figures break out past the prose column */

.case-section > figure {
  grid-column: 2 / -1;
  margin: 16px 0;
}

.case-section > figure .placeholder {
  aspect-ratio: 16 / 9;
  border: 1px solid var(--rule);
  border-radius: var(--radius-card);
}

.case-section > figure img {
  width: 100%;
  border: 1px solid var(--rule);
  border-radius: var(--radius-card);
}

.case-section > figure figcaption { margin-top: 14px; }

/* Captions are full sentences: sentence case reads better than uppercase */
figcaption.mono {
  font-size: var(--size-small);
  text-transform: none;
  letter-spacing: normal;
}

/* A small artefact — a dialog, a tooltip — should sit at its own size rather
   than being blown up to the full figure width. Capped and left-aligned to
   the prose. */
.case-section > figure.figure--constrained img {
  width: auto;
  max-width: min(100%, 480px);
}

/* A before/after pair: two shots side by side, each with its own label, one
   shared caption beneath. Stacks on narrow screens. */
.figure-compare__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px 24px;
  align-items: start;
}

.figure-compare__item {
  display: grid;
  gap: 10px;
  align-content: start;
}

.figure-compare__label { margin: 0; }

/* Weighted variant: two unrelated shots of different shape, not a matched
   pair. The wider landscape shot takes more of the row; the narrow portrait
   one sits at its own size on the right. The ratio is tuned so the two land
   at roughly equal height. */
.figure-compare--weighted .figure-compare__grid {
  grid-template-columns: minmax(0, 1.8fr) minmax(0, 1fr);
}

@media (max-width: 640px) {
  .figure-compare__grid { grid-template-columns: 1fr; }
}

/* 5. Metrics disclosure */

.metrics {
  max-width: var(--measure-case);
  margin-top: clamp(48px, 6vw, 72px);
  border: 1px solid var(--highlight-border);
  border-radius: var(--radius-cover);
  background: var(--highlight);
}

.metrics summary {
  list-style: none;
  cursor: pointer;
  padding: 28px clamp(28px, 4vw, 48px);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.metrics summary::-webkit-details-marker { display: none; }

.metrics__label {
  font-family: var(--font-display);
  font-size: var(--size-body);
  font-weight: var(--weight-heading);
  letter-spacing: -0.01em;
}

.metrics__body {
  margin-top: 0;
  max-width: none;
  padding: 26px clamp(28px, 4vw, 48px) 34px;
  border-top: 1px solid var(--highlight-rule);
}

.metrics[open] .metrics__hint::after { content: "Close"; }
.metrics:not([open]) .metrics__hint::after { content: "Open"; }

/* 6 and 7. Closing section and next project */

.case-close {
  display: grid;
  grid-template-columns: var(--rail) minmax(0, 640px);
  column-gap: var(--rail-gap);
  row-gap: 20px;
  margin-top: clamp(48px, 6vw, 72px);
  padding-top: clamp(48px, 6vw, 72px);
  border-top: 1px solid var(--rule);
}

.case-close > * { grid-column: 2; }

.case-close > h2 {
  grid-column: 1;
  grid-row: 1 / span 2;
  align-self: start;
  font-size: var(--size-lead);
  line-height: 1.18;
}

.case-close p { text-wrap: pretty; }

/* Next project. A card, not a line of text: the gradient of the project you
   are about to open, its title, and an arrow that moves when you aim at it. */

.next-project {
  display: grid;
  grid-template-columns: 200px minmax(0, 1fr) auto;
  align-items: center;
  gap: 32px;
  color: var(--ink);
  margin-top: var(--section-gap);
  padding-top: 40px;
  border-top: 1px solid var(--rule);
  text-decoration: none;
}

.next-project__thumb {
  position: relative;
  overflow: hidden;
  aspect-ratio: 4 / 3;
  border-radius: var(--radius-card);
  background: var(--fill);
  /* Same reveal as the home-page covers: veiled at rest, full colour on hover. */
  opacity: 0.62;
  transition: opacity var(--dur) var(--ease);
}

/* A miniature of the target project's cover: its gradient artwork, and the
   same screenshot floating over it, so the link previews where it leads
   instead of showing a bare gradient. Mirrors the home-page card. */
.next-project__cover {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur) var(--ease);
}

/* Same grain as the covers, over the gradient but under the screenshot. */
.next-project__thumb::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background-image: var(--grain);
  opacity: 0.42;
  mix-blend-mode: overlay;
  pointer-events: none;
}

.next-project__shot {
  position: absolute;
  z-index: 2;
  left: 15%;
  right: -7%;
  bottom: -10%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  object-position: 0 0;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.7);
  transform: rotate(-2.5deg);
  transition: transform var(--dur) var(--ease);
}

.next-project__body {
  display: grid;
  gap: 10px;
}

.next-project__title {
  display: block;
  font-family: var(--font-display);
  font-size: var(--size-subtitle);
  font-weight: var(--weight-heading);
  letter-spacing: var(--track-section);
  line-height: 1.16;
  text-wrap: balance;
}

.next-project__eyebrow {
  display: block;
  font-size: var(--size-small);
  color: var(--ink-muted);
}

.next-project__arrow {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  border-radius: var(--radius-pill);
  background: var(--ink);
  color: var(--paper);
  transition: transform var(--dur) var(--ease);
}

.next-project:hover .next-project__thumb,
.next-project:focus-within .next-project__thumb { opacity: 1; }
.next-project:hover .next-project__shot { transform: rotate(-2.5deg) translateY(-5px); }
.next-project:hover .next-project__arrow { transform: translateX(6px); }

/* Title underlines on hover, the same fade-in as the home-page card titles. */
.next-project__title {
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-decoration-color: transparent;
  text-underline-offset: 4px;
  transition: text-decoration-color var(--dur) var(--ease);
}

.next-project:hover .next-project__title,
.next-project:focus-within .next-project__title { text-decoration-color: currentColor; }

/* No hover to reveal it with, so the colour is simply there. */
@media (hover: none) {
  .next-project__thumb { opacity: 1; }
}

@media (max-width: 640px) {
  .next-project {
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 20px 24px;
  }

  .next-project__thumb {
    grid-column: 1 / -1;
    aspect-ratio: 16 / 9;
  }
}

/* Quote. Someone else's words, so they sit off the rail behind a hairline
   rather than being set at display size like my own. */

.quote {
  margin: 32px 0 0;
  padding-left: 28px;
  border-left: 1px solid var(--ink);
}

.quote p {
  max-width: 54ch;
  font-size: var(--size-body);
  text-wrap: pretty;
}

.quote footer {
  margin-top: 14px;
}

/* Prose, used inside the metrics disclosure */

.prose > * + * { margin-top: 20px; }
.prose > *:first-child { margin-top: 0; }

.prose ul {
  list-style: disc;
  padding-left: 22px;
  display: grid;
  gap: 14px;
}

.prose li::marker { color: #C4C4BE; }
.prose strong { font-weight: 600; }
.prose p { text-wrap: pretty; }

/* Case study responsive */

@media (max-width: 900px) {
  .case-section,
  .case-close {
    grid-template-columns: minmax(0, 1fr);
    row-gap: 18px;
  }

  .case-section > *,
  .case-close > * { grid-column: 1; }

  .case-section > h2,
  .case-close > h2 {
    grid-column: 1;
    grid-row: auto;
    position: static;
    font-size: var(--size-lead);
    margin-bottom: 6px;
  }

  .case-section > figure,
  .callout { grid-column: 1; }

}

@media (max-width: 760px) {
  .case-hero {
    padding-bottom: 0;
    min-height: 0;
  }

  .case-hero > *:not(.case-hero__shot):not(.case-hero__bg) { max-width: 100%; }

  .case-hero__shot {
    position: static;
    width: 92%;
    margin: 30px -8% 0 auto;
  }

  .case-hero--art > *:not(.case-hero__shot):not(.case-hero__bg) { max-width: 100%; }


}

@media (max-width: 640px) {
  .decision > div {
    grid-template-columns: 1fr;
    gap: 12px;
    padding: 26px 0;
  }

  .decision dt { padding-top: 0; }

  .summary { grid-template-columns: 1fr; row-gap: 28px; }

  .summary__fact { padding-inline: 0; }

  /* Matches the specificity of the rule that sets the divider */
  .summary__fact + .summary__fact { border-left: 0; }

  .summary__fact + .summary__fact::before,
  .summary__fact + .summary__fact::after { display: none; }

}

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

.site-footer {
  border-top: 1px solid var(--rule);
  margin-top: var(--section-gap);
  padding-block: 48px 64px;
}

/* Mono at 12px needs full contrast here, not the muted label grey */
.site-footer .mono { color: var(--ink); }

.site-footer ul {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px 32px;
}

.site-footer a {
  color: var(--ink);
  text-decoration: underline;
  /* Rule grey disappears under 12px mono on white, so the link needs a
     visible underline to read as clickable at rest. */
  text-decoration-color: var(--ink-muted);
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
  transition: text-decoration-color 180ms var(--ease);
}

.site-footer a:hover {
  text-decoration-color: var(--ink);
}

/* --------------------------------------------------------------------------
   Page-load motion, hero only
   -------------------------------------------------------------------------- */

@keyframes rise {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

.hero > * {
  animation: rise var(--dur) var(--ease) both;
}

/* --------------------------------------------------------------------------
   Responsive
   -------------------------------------------------------------------------- */

@media (max-width: 1000px) {
  .about {
    grid-template-columns: 260px minmax(0, 1fr);
    gap: 40px;
  }
}

@media (max-width: 860px) {
  .work-grid { gap: 56px 24px; }
  .card { grid-column: span 12; }

  .about {
    grid-template-columns: 1fr;
    gap: 40px;
  }

  .about__portrait,
  .about__prose { grid-column: 1; }

  /* The portrait comes first, right under the heading, instead of after all
     the text. The source keeps prose first, for screen readers. */
  .about__portrait { grid-row: 1; }
  .about__prose { grid-row: 2; }

  .about__portrait { max-width: 300px; }
}

@media (max-width: 560px) {
  .site-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 16px;
    padding-block: 24px 8px;
  }

  .site-nav ul { gap: 24px; }

  .section__head {
    flex-direction: column;
    gap: 12px;
    padding-bottom: 28px;
    margin-bottom: 36px;
  }

  .hero__title { max-width: 18ch; }
}

/* --------------------------------------------------------------------------
   Reduced motion
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .hero > * { animation: none; }

  .site-nav a,
  .site-footer a {
    transition: none;
  }

  /* No movement, but the fades are not motion: they stay. */
  .cover {
    transition: opacity var(--dur) var(--ease);
  }

  .card:hover .cover,
  .card:focus-within .cover {
    transform: none;
    background-position: 0% 0%;
    box-shadow: none;
  }

  .next-project__shot,
  .next-project__arrow { transition: none; }

  /* The colour reveal is a fade, not motion: it stays. */
  .next-project__thumb { transition: opacity var(--dur) var(--ease); }

  .next-project:hover .next-project__shot { transform: rotate(-2.5deg); }
  .next-project:hover .next-project__arrow { transform: none; }
}

/* --------------------------------------------------------------------------
   Bonus: the story game
   A card under the About quote opens the pixel-art game over the page
   (story-stage.js). Its cover has the pixel hover (pixel-hover.js).
   -------------------------------------------------------------------------- */

/* With the pixels on, the cover stays put: no lift, just the colour and the
   shadow. The script adds .card--pixel only where the effect runs, so touch
   screens and reduced motion keep the live hover, lift included. */
.card--pixel:hover .cover,
.card--pixel:focus-within .cover {
  transform: none;
}

/* Pixel layer over the cover, larger than it on every side so pixels can fly
   past the card's edges. Its box is set from the script. It fades with the
   cover so the two never come apart, and never catches the pointer, so the
   card link still works. */
.cover__pixels {
  position: absolute;
  z-index: 1;
  pointer-events: none;
  image-rendering: pixelated;   /* drawn at 1x, scaled up with hard edges */
  opacity: 0.62;
  transition: opacity var(--dur) var(--ease);
}

.card:hover .cover__pixels,
.card:focus-within .cover__pixels {
  opacity: 1;
}

/* Presented like a piece of work: a case card in every way (radius, veil,
   shadow, title), so the pixel art reads as content in a frame rather than a
   clash with the page. It sits in the About prose column, under the quote,
   at the same width as a case card. */
.about .card--story {
  grid-column: auto;
  margin-top: 40px;
}

/* The game's own shape, 16:9, and its pixels kept hard at any size. */
.cover--story { aspect-ratio: 16 / 9; }
.cover--story .cover__img { image-rendering: pixelated; }

/* The stage the game opens on: over the whole page, in the game's own dark,
   so the game frame sits on it seamlessly. A slim bar on top holds the close
   button, so it never sits on the game's own controls. */
.story-stage {
  position: fixed;
  inset: 0;
  height: 100vh;
  height: 100dvh;   /* the visible screen, as Safari's toolbar comes and goes */
  z-index: 100;
  display: flex;
  flex-direction: column;
  background: #0E0C14;
  animation: story-in var(--dur) var(--ease);
}

.story-stage__bar {
  display: flex;
  justify-content: flex-end;
  padding: 12px 16px 4px;
  padding-top: max(12px, env(safe-area-inset-top));
  padding-right: max(16px, env(safe-area-inset-right));
}

/* A phone held sideways has no height to spare: the close button floats
   in the dark corner beside the game, as an icon only, and the game gets
   the whole screen. */
@media (orientation: landscape) and (max-height: 500px) {
  .story-stage__bar {
    position: absolute;
    top: 0;
    right: 0;
    z-index: 1;
    padding: 8px;
    padding-right: max(8px, env(safe-area-inset-right));
  }

  .story-stage__close { padding: 0; width: 44px; justify-content: center; }
  .story-stage__label { display: none; }
}

.story-stage__game {
  display: block;
  flex: 1;
  width: 100%;
  border: 0;
  transition: opacity var(--dur) var(--ease);
}

/* Hidden while it loads, so the game's title screen never flashes up. */
.story-stage__game.is-loading { opacity: 0; }

.story-stage__close {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  padding: 0 16px;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: var(--radius-pill);
  background: rgba(14, 12, 20, 0.6);
  color: #fff;
  font: inherit;
  font-size: var(--size-small);
  cursor: pointer;
  transition: border-color 180ms var(--ease);
}

.story-stage__close:hover,
.story-stage__close:focus-visible { border-color: #fff; }

/* No page scroll behind the stage, and nothing of the page showing through
   under Safari's see-through toolbar. */
html.is-playing { overflow: hidden; background: #0E0C14; }
html.is-playing body > :not(.story-stage) { visibility: hidden; }

@keyframes story-in {
  from { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .story-stage { animation: none; }
}
