/* ==========================================================================
   Oleksandra Mykhalchuk — Interior Architect & Designer
   main.css · depends on tokens.css
   Structure: 1 Reset · 2 Base · 3 Utilities · 4 Header · 5 Hero
              6 About · 7 Work · 7b Contact · 7c Project · 7d About page
              8 Footer
              9 Reduced motion
   ========================================================================== */

/* ==========================================================================
   1 · RESET
   ========================================================================== */

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

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

body,
h1, h2, h3, h4, p, figure, blockquote, ul, ol, dl, dd {
  margin: 0;
}

ul[class], ol[class] {
  list-style: none;
  padding: 0;
}

img, picture, video, svg {
  display: block;
  max-width: 100%;
}

button, input, select, textarea { font: inherit; color: inherit; }

/* A class with a display value outranks the UA [hidden] rule — restore it,
   otherwise `el.hidden = true` silently does nothing. */
[hidden] { display: none !important; }

button {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

a { color: inherit; }

/* ==========================================================================
   2 · BASE
   ========================================================================== */

body {
  background: var(--c-paper);
  color: var(--c-ink-soft);
  font-family: var(--font-base);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  letter-spacing: var(--track-normal);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;           /* belt-and-braces: never scroll sideways */
}

/* Locks scroll while the mobile menu is open */
body.is-locked { overflow: hidden; }

h1, h2, h3 {
  color: var(--c-ink);
  font-weight: var(--w-regular);
  text-wrap: balance;           /* avoids orphan words in headings */
}

p { text-wrap: pretty; }

/* --- Links ---------------------------------------------------------------
   Underline is drawn as a border so its offset and colour are controllable. */
a { text-decoration: none; }

.link {
  display: inline-block;
  border-bottom: 1px solid var(--c-rule-strong);
  padding-bottom: 2px;
  transition: border-color var(--dur-mid) var(--ease-soft),
              color var(--dur-mid) var(--ease-soft);
}
.link:hover { border-bottom-color: var(--c-ink); color: var(--c-ink); }

/* --- Focus ---------------------------------------------------------------
   Never remove focus rings. :focus-visible keeps them off mouse clicks. */
:focus-visible {
  outline: 2px solid var(--c-ink);
  outline-offset: 3px;
  border-radius: 1px;
}

/* The header no longer follows the viewport, so anchors need only a little
   breathing room above them rather than a header-sized offset. */
:target,
section[id] { scroll-margin-top: var(--space-sm); }

/* ==========================================================================
   3 · UTILITIES
   ========================================================================== */

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

.wrap--text { max-width: var(--wrap-text); }

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

.skip-link {
  position: absolute;
  top: var(--space-xs);
  left: var(--space-xs);
  z-index: var(--z-overlay);
  padding: var(--space-2xs) var(--space-sm);
  background: var(--c-ink);
  color: var(--c-paper);
  font-size: var(--t-label);
  letter-spacing: var(--track-meta);
  text-transform: uppercase;
  transform: translateY(-160%);
  transition: transform var(--dur-fast) var(--ease-out);
}
.skip-link:focus { transform: translateY(0); }

/* --- Hover underline -----------------------------------------------------
   Wrap the text of any link in <span class="u-line"> and the rule appears on
   hover, --underline-gap below the text. It must sit on an inline span, never
   on the link itself: links here are 32–44px tall for their tap target, and a
   border on that box floats far beneath the words. */
.u-line {
  display: inline-block;
  padding-bottom: var(--underline-gap);
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur-mid) var(--ease-soft);
}
a:hover .u-line { border-bottom-color: currentColor; }

/* The page you are on keeps that rule permanently. Same mechanism, same 3px
   gap, so the marker and the hover state are the one gesture rather than two
   competing ones — and it is driven by aria-current, which the markup already
   carried for screen readers. Nothing new to remember to set. */
a[aria-current="page"] .u-line { border-bottom-color: currentColor; }

/* Small uppercase label used for section eyebrows and meta */
.eyebrow {
  font-size: var(--t-meta);
  letter-spacing: var(--track-meta);
  text-transform: uppercase;
  color: var(--c-muted);
}

.rule {
  border: 0;
  border-top: 1px solid var(--c-rule);
}

/* --- Scroll reveal -------------------------------------------------------
   JS adds .is-in when the element enters the viewport. Elements are visible
   by default if JS never runs — progressive enhancement, not a dependency. */
.reveal {
  opacity: 0;
  transform: translateY(1.5rem);
  transition: opacity var(--dur-slow) var(--ease-out),
              transform var(--dur-slow) var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
}
.reveal.is-in { opacity: 1; transform: none; }

/* If JS is unavailable the content must still be readable */
.no-js .reveal { opacity: 1; transform: none; }

/* ==========================================================================
   4 · HEADER
   ========================================================================== */

/* Fixed, not sticky: the reel runs full-bleed underneath it.
   A translucent white plate keeps the dark type legible over any frame —
   compositing white at 82% over even a black frame still lands near #d0d0d0,
   which holds ~11:1 against the ink. */
/* Absolute, not fixed: the header sits at the top of the page over the reel
   and scrolls away with it. Nothing follows the viewport.
   No blur — a flat translucent plate. White at 60% over a black frame
   composites to ~#999, which holds 6.5:1 against the ink, so every header
   item is full-strength ink and the title, name and nav are separated by
   weight rather than by lighter greys.

   Pages with no reel behind the header take `.header--solid` (below), which
   changes the positioning and NOTHING else — see the note on that rule. */
.header {
  position: absolute;
  inset-inline: 0;
  top: 0;
  z-index: var(--z-header);
  background: rgba(255, 255, 255, 0.6);
  /* The site's hairline is a warm light grey, but the header sits over
     photography — a light rule vanishes against a pale frame. This is the same
     hairline weight expressed as ink, so it reads at consistent strength
     whether the frame behind it is bright or dark. Kept at 0.28: below about
     0.2 it disappears against the brighter frames of the reel. */
  border-bottom: 1px solid rgba(22, 19, 15, 0.28);
}

/* The ONLY difference between the header on the homepage and on every other
   page. Position, and nothing else: the homepage header is absolute because a
   reel runs underneath it, and a subpage with no reel would have its content
   slide up behind an absolute bar.

   Background and border are deliberately NOT overridden here. The homepage
   values already resolve correctly over white — rgba(255,255,255,0.6)
   composites to solid white, and the ink hairline renders at the same strength
   — so leaving them alone is what makes the three headers pixel-identical.
   Setting --c-paper and --c-rule here (as this rule used to) gave subpages a
   visibly lighter rule than the homepage. */
.header--solid { position: static; }

/* Edge margin, not page margin — title and menu sit in the actual corners. */
.header__inner {
  display: grid;
  grid-template-columns: auto 1fr auto;   /* mobile: name · spacer · toggle */
  align-items: center;
  gap: var(--space-sm);
  min-height: 3.25rem;
  padding-block: var(--space-3xs);
  padding-inline: var(--edge-margin);
  max-width: none;                        /* overrides .wrap — go full width */
}

/* Professional title — hidden on mobile, hard left from 900px up. Full ink,
   lightened by weight rather than by colour, so it survives the transparent
   plate over a dark frame. */
.header__title {
  display: none;
  font-size: var(--t-meta);
  font-weight: var(--w-regular);
  letter-spacing: var(--track-meta);
  text-transform: uppercase;
  color: var(--c-ink);
  justify-self: start;
}

.header__name {
  font-size: var(--t-name);               /* 15px */
  font-weight: var(--w-medium);
  letter-spacing: var(--track-wide);
  text-transform: uppercase;
  color: var(--c-ink);
  justify-self: start;
  line-height: 1.2;
}
.header__name a {
  display: inline-block;
  padding-block: var(--space-3xs);   /* keeps the tap target ≥ 44px tall */
}

/* --- Desktop navigation --- */
.nav {
  display: none;
  justify-self: end;
}

.nav__list {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.nav__link {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;              /* 44px touch target */
  font-size: var(--t-meta);
  font-weight: var(--w-regular);
  letter-spacing: var(--track-meta);
  text-transform: uppercase;
  color: var(--c-ink);
}

/* The 44px target is what sets the header's height. Mice don't need it, so on
   fine pointers the links shrink to 32px — still well clear of the 24px
   minimum — and the bar slims with them. Touch devices keep the full 44px. */
@media (pointer: fine) {
  .nav__link { min-height: 2rem; }
}

/* --- Menu toggle (mobile only) --- */
.menu-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;                  /* 44 × 44 */
  /* Explicitly the last column. The header grid is `auto 1fr auto` — name,
     spacer, nav — but on mobile BOTH .header__title and .nav are display:none,
     so they are not grid items at all and auto-placement drops the toggle into
     the 1fr spacer instead of the final track. It then sat 48px from the right
     edge while the wordmark started at 20, which read as a header that was not
     square to the page. */
  grid-column: 3;
  justify-self: end;
  /* Half the difference between the 44px tap target and the 20px bars inside
     it, so the BARS land on --edge-margin rather than the invisible box around
     them. That matches the wordmark on the left, whose glyphs also start on the
     margin, and the nav links at ≥900, whose text ends on it. */
  margin-right: -0.75rem;
  color: var(--c-ink);
}

.menu-toggle__bars {
  display: block;
  position: relative;
  width: 1.25rem;
  height: 1px;
  background: currentColor;
  transition: background-color var(--dur-fast) var(--ease-soft);
}
.menu-toggle__bars::before,
.menu-toggle__bars::after {
  content: "";
  position: absolute;
  left: 0;
  width: 100%;
  height: 1px;
  background: currentColor;
  transition: transform var(--dur-mid) var(--ease-out);
}
.menu-toggle__bars::before { top: -6px; }
.menu-toggle__bars::after  { top:  6px; }

/* Bars morph into a cross while the panel is open */
.menu-toggle[aria-expanded="true"] .menu-toggle__bars { background: transparent; }
.menu-toggle[aria-expanded="true"] .menu-toggle__bars::before { transform: translateY(6px) rotate(45deg); }
.menu-toggle[aria-expanded="true"] .menu-toggle__bars::after  { transform: translateY(-6px) rotate(-45deg); }

/* --- Mobile panel --- */
.menu-panel {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: var(--space-xl) var(--page-margin);
  background: var(--c-paper);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-mid) var(--ease-out),
              visibility 0s linear var(--dur-mid);
}
.menu-panel.is-open {
  opacity: 1;
  visibility: visible;
  transition-delay: 0s;
}

.menu-panel__close {
  position: absolute;
  top: calc(var(--space-xs) + 0.5rem);
  right: var(--page-margin);
  width: 2.75rem;
  height: 2.75rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: var(--t-meta);
  letter-spacing: var(--track-meta);
  text-transform: uppercase;
  color: var(--c-muted);
}

.menu-panel__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.menu-panel__link {
  display: block;
  padding-block: var(--space-2xs);
  font-size: var(--t-lead);
  font-weight: var(--w-light);
  line-height: var(--lh-snug);
  color: var(--c-ink);
}

.menu-panel__meta {
  margin-top: var(--space-xl);
  font-size: var(--t-meta);
  letter-spacing: var(--track-meta);
  text-transform: uppercase;
  color: var(--c-muted);
}

/* ==========================================================================
   5 · HERO
   ========================================================================== */

.hero {
  position: relative;
  background: var(--c-wash);
}

/* The box now matches the reel's own 16:9 ratio, so the whole frame is always
   on screen. Previously this was 100svh with `cover`, which forced the 16:9
   video to fill a taller box and cut the sides off — that is what removed
   parts of the composition. Nothing is cropped here at any width.

   At 1440 this still stands 810px tall, so it reads as a full-page opening;
   max-height keeps it from overshooting on short, wide windows. */
/* Full bleed, edge to edge, no plate behind it. The box is 16:9 — the reel's
   own ratio — so `cover` fills it completely without cropping a single pixel.
   There is deliberately NO max-height here: that was what squeezed the box on
   short windows and produced the letterbox bars. */
.hero__media {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
}

/* The still is the base layer and always renders — it is the LCP element.
   The video sits on top and only fades in once it can actually play, so the
   page looks finished whether or not the encoded reel exists yet. */
/* `cover` fills the box with no bars. Because the box carries the reel's own
   16:9 ratio, cover and contain resolve identically here — nothing is cropped. */
.hero__video,
.hero__still {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

.hero__video {
  opacity: 0;
  transition: opacity 700ms var(--ease-soft);
}
.hero__video.is-ready { opacity: 1; }

/* ==========================================================================
   6 · ABOUT — one centred statement, nothing else
   ========================================================================== */

/* Left-aligned, on the same edge gutter as the header, grid and footer, so the
   copy starts on the page's single left margin rather than floating centred. */
.about {
  padding-block: var(--space-section);
  padding-inline: var(--edge-margin);
  max-width: none;
  text-align: left;
}

/* ONE size for this whole section. The opening statement, the body copy and
   the labels are all --t-about; they separate by weight and tone alone.
   Do not reintroduce a larger size here — that is what made it feel shouty. */
.about__lead,
.about__body p {
  font-size: var(--t-about);
  /* Long measures need air between the lines or the eye loses its place
     returning to the left edge — the wider the column, the more leading. */
  line-height: 1.85;
  letter-spacing: 0.012em;
}

/* Both blocks share ONE max-width and are centred on the page, so their left
   edges line up exactly — which is the whole point of ranged-left text inside
   a centred column.
   Deliberately rem, not ch: `ch` resolves against each element's own font-size,
   and .about__body inherits the 16px base while its paragraphs are 14px, so
   an identical `64ch` on both produced two different widths and two different
   left edges. rem is immune to that. */
/* 960px — sized so the opening sentence sets on a single line at this weight.
   It still wraps below that width, which is correct: forcing one line with
   nowrap would overflow the page on anything narrower. */
.about__lead,
.about__body {
  max-width: 60rem;
  margin-inline: auto;
}

.about__lead {
  font-weight: var(--w-medium);     /* the statement — heavier, full ink */
  color: var(--c-ink);
  /* The lead is an <h2>, and headings carry `text-wrap: balance` sitewide.
     Balance evens the length of every line, which on a full sentence means
     breaking well before the container's edge — that is what made this wrap
     early while the paragraphs below ran the full width. Balance suits short
     headings; a sentence this long wants to simply fill its measure.
     `wrap` is the initial value — `normal` is NOT valid for text-wrap (that
     belongs to white-space) and is dropped silently, leaving balance active. */
  text-wrap: wrap;
}

.about__body {
  margin-top: var(--space-md);
  display: grid;
  gap: var(--space-sm);
}

.about__body p {
  font-weight: var(--w-regular);    /* supporting copy — lighter, softer tone */
  color: var(--c-ink-soft);
}

/* Labels sit inline, in the same size — set apart by weight alone. */
.about__body b {
  font-weight: var(--w-medium);
  color: var(--c-ink);
}

/* ==========================================================================
   7 · WORK — selected projects
   ========================================================================== */

/* Full page width — the grid runs edge to edge on the tighter gutter, so it
   lines up with the header title and the footer rule.
   No top padding: About already ends with a full --space-section beneath it,
   and stacking both would make the gap under About twice the one above it. */
.work {
  padding-block: 0 var(--space-lg);
  padding-inline: var(--edge-margin);
  max-width: none;
}

/* On a subpage the grid is the first thing in <main>, with no hero, heading or
   divider above it. All four margins are the SAME --edge-margin, so the grid
   sits in an even frame: the gap above the first row matches the gap below the
   last caption and both match the gutters down the sides.
   The grid's own top margin is zeroed so it cannot stack a second gap on top. */
.work--page {
  padding-top: var(--edge-margin);
  padding-bottom: var(--edge-margin);
}
.work--page .work__grid { margin-top: 0; }

.work__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
  padding-bottom: var(--space-2xs);   /* title sits close to the divider */
  border-bottom: 1px solid var(--c-rule);
}

/* Outranks the project names beneath it, but stays under the site name —
   nothing on the page is set larger than that. Tracking is pulled in from the
   0.14em used for small caps, which becomes a picket fence at this size. */
.work__title {
  font-size: var(--t-section);
  font-weight: var(--w-regular);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--c-ink);
}

.work__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-lg) var(--space-sm);
  margin-top: var(--space-sm);      /* tight to the divider above it */
}

.work__item { min-width: 0; }

/* A uniform 4:3 tile across the whole grid. The source images run 1.25–1.91,
   so `cover` trims a little from the widest ones — that is the trade a tidy
   3-up grid asks for, and 4:3 sits close to where most of them already are. */
.work__frame {
  position: relative;
  overflow: hidden;
  aspect-ratio: 4 / 3;
  background: var(--c-wash);
}

/* No idle transform: holding six 2500px textures on their own compositor
   layers costs real memory for nothing. The layer is created on hover, when
   it is actually needed, and discarded afterwards. */
.work__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1200ms var(--ease-out);
}

.work__link {
  display: block;
  color: inherit;
}
.work__link:hover .work__img { transform: scale(1.03); }

/* The caption sits close under its image. It used to clear it by 16px and then
   put another 12px between the rule and the words — 29px in total from picture
   to project name, which let each caption drift toward the tile below it rather
   than reading as a label belonging to the tile above. Halved on both counts:
   8px to the rule, 8px under it, ~21px picture to name. The 64px row gap does
   the work of separating one project from the next. */
.work__caption {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3xs) var(--space-sm);
  margin-top: var(--space-3xs);
  padding-top: var(--space-3xs);
  border-top: 1px solid var(--c-rule);
  transition: border-color var(--dur-mid) var(--ease-soft);
}
.work__link:hover .work__caption { border-top-color: var(--c-ink); }

/* Project name and its location differ by weight, size and tone — not family. */
/* Names in caps — which need positive tracking, since letterforms set in caps
   at text size crowd each other without it. */
.work__name {
  font-size: var(--t-title);        /* 16px — under the section heading */
  font-weight: var(--w-regular);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: var(--lh-snug);
  color: var(--c-ink);
}

/* Locations stay as written in the markup — Title Case, no transform. */
.work__place {
  font-size: var(--t-label);
  letter-spacing: 0.02em;
  text-transform: none;
  color: var(--c-muted);
  white-space: nowrap;
}

/* --- Call to action ---
   Used twice: under About and under the project grid, both centred. */
.work__cta,
.about__cta {
  display: flex;
  justify-content: center;
  margin-top: var(--space-lg);
}

.cta {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  min-height: 2.75rem;              /* 44px target without a border */
  font-size: var(--t-label);
  font-weight: var(--w-regular);
  letter-spacing: var(--track-meta);
  text-transform: uppercase;
  color: var(--c-muted);
  transition: color var(--dur-mid) var(--ease-soft);
}
.cta:hover { color: var(--c-ink); }

/* Spans label and arrow together, on the same --underline-gap as every other
   underline on the page. The arrow does not slide on hover — it would travel
   out past the end of the rule now that the rule reaches it. */
.cta__inner {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  padding-bottom: var(--underline-gap);
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur-mid) var(--ease-soft);
}
.cta:hover .cta__inner { border-bottom-color: currentColor; }

/* ==========================================================================
   7b · CONTACT — one screen, no scroll
   ========================================================================== */

/* Makes the document a flex column so the footer sits on the bottom edge of
   the viewport rather than under the fold, with the content stretching to fill
   what's left between it and the header.
   min-height, never a fixed height: if a short window or a large text setting
   makes the content taller than the screen, the page scrolls instead of
   clipping the form. That trade — a scrollbar in the rare case, nothing lost
   in any case — is the only safe way to build a "no scroll" screen.
   svh, not vh: on mobile, vh is measured against the browser chrome's
   *collapsed* state, which overflows the visible screen by the height of the
   toolbar and hands you a scrollbar on exactly the page meant not to have one. */
.page--screen {
  min-height: 100vh;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
}

/* <main> is the flex item, not the section inside it, so the growth has to be
   passed down one level: main takes the free space, then hands it to the
   section. Without this the footer floats mid-screen on a tall window. */
.page--screen > main {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
}

/* Top-aligned, not centred. The block sits just under the header and the
   leftover height collects at the foot of the page — which is what makes the
   reference layout read as a held, unhurried page rather than a card floating
   in the middle of the screen. */
.contact {
  /* Measured off the reference at 1440, box edge to box edge. Two of these
     fall between rungs of the shared space ladder, so they are declared here
     rather than bent to the nearest token — the point of this column is that
     its rhythm matches, and 40px where the reference has 48px is exactly the
     kind of drift that makes a copy look approximately right and wrong. */
  --gap-eyebrow: 1.5rem;   /* 24px — label to statement */
  --gap-lead:    2rem;     /* 32px — statement to copy   */
  --gap-block:   3rem;     /* 48px — copy to the details */

  flex: 1 1 auto;
  display: grid;
  align-content: start;
  padding-block: clamp(var(--space-md), 6vh, var(--space-xl))
                 clamp(var(--space-sm), 5vh, var(--space-lg));
  /* --edge-margin, not --page-margin: this block now sits on the same gutter
     as the header, the project grid and the footer, so its left column starts
     exactly where the header title and the footer copyright do. */
  padding-inline: var(--edge-margin);
}

/* Phone and tablet: one stacked column, full width on --edge-margin, so the
   text begins on the page's own left margin — flush with the header title and
   the footer copyright, like every other section on the site.
   From 900 up it becomes two columns and centres instead; see the note in the
   56.25em block. */
.contact__grid {
  width: 100%;
  display: grid;
  grid-template-columns: 1fr;
  /* Elastic, because the whole premise of this page is that it fits the
     screen. A flat 96px is right on a 900px-tall window and is overflow on a
     660px one — which is what a 1366x768 laptop actually leaves once the
     browser chrome is taken out. */
  gap: clamp(var(--space-lg), 6vh, var(--space-xl));
}

/* --- Left column ---------------------------------------------------------
   Four steps, in this order: small, large, medium, small. The label names the
   page, the statement carries it, the copy explains it, the details close it. */

/* The smallest mark on the page — heading level says what a thing IS in the
   document, type size says how loudly it speaks, and those are not the same
   question. One rule for both pages: the About page asked for "the same as
   Contact", so it shares the declaration rather than copying it. */
.contact__eyebrow,
.about__eyebrow {
  font-size: var(--t-meta);
  font-weight: var(--w-regular);
  letter-spacing: var(--track-meta);
  text-transform: uppercase;
  color: var(--c-muted);
}

/* The one line on the site set above the site name. Light weight and a tight
   line-height keep it a statement rather than a headline; at --w-regular this
   size turns shouty immediately. */
.contact__lead {
  margin-top: var(--gap-eyebrow);
  font-size: var(--t-display);
  font-weight: var(--w-light);
  line-height: 1.08;
  /* Inter's default tracking is drawn for text sizes; at 48px it opens up and
     the words start to float apart. A touch negative closes it back. The
     reference sets -0.025em on its display line; this is the same move. */
  letter-spacing: -0.02em;
  color: var(--c-ink);
  text-wrap: balance;
}

/* Two even lines, breaking after "collaborations," the way the reference sets
   it. `balance` rather than a hand-tuned max-width: at this measure the natural
   break drags a lone "I" onto the end of the first line, and the width that
   excludes it is a 9px window — which the moment Inter fails to load and a
   fallback face steps in, is no longer the right 9px. balance is computed from
   what actually rendered, so it survives that. (The site default for <p> is
   `pretty`; this is the one paragraph that wants the other one.) */
.contact__body {
  margin-top: var(--gap-lead);
  max-width: 22rem;
  font-size: var(--t-body);
  font-weight: var(--w-regular);
  line-height: 1.9;
  color: var(--c-ink-soft);
  text-wrap: balance;
}

.contact__reach { margin-top: var(--gap-block); }

.contact__list {
  display: grid;
  justify-items: start;         /* links only as wide as their text, so the
                                   hover rule stops at the last character */
  /* Tight, because the leading inside each link already separates them. The
     reference runs a 4px gap under a line box half again the type size; this
     is the same rhythm at this type size. */
  gap: 0.25rem;
  margin-top: var(--space-2xs);
}

/* A step BELOW the body copy, not level with it — the reference sets its
   details at 0.75x its body size, and having them match was what flattened the
   hierarchy here. 1.75 leading is not decoration: it is what keeps the tap
   target at 24.5px, over the WCAG 2.2 minimum, without padding. */
.contact__link {
  display: inline-block;
  font-size: var(--t-about);
  line-height: 1.75;
  color: var(--c-ink);
  transition: color var(--dur-mid) var(--ease-soft);
}

/* ---- The form ----------------------------------------------------------
   Underline-only fields: no boxes, no fills, no corner radii — the same
   hairline vocabulary as every divider on the site. */

.form {
  display: grid;
  /* Same reasoning as the grid gap above: four of these stack up, so they are
     the single biggest thing standing between this form and a short window. */
  gap: clamp(0.75rem, 2.4vh, var(--space-sm));
}

.field {
  display: grid;
  gap: var(--space-3xs);
}

.field__input {
  width: 100%;
  padding: 0 0 var(--space-3xs);
  font-family: inherit;
  font-size: var(--t-about);
  /* The UA stylesheet hard-sets letter-spacing:normal on form controls, which
     beats inheritance — without this the typed text is the only run on the
     page without the site's --track-normal. */
  letter-spacing: inherit;
  line-height: 1.6;
  color: var(--c-ink);
  background: transparent;
  border: 0;
  /* --c-muted, not the --c-rule hairline used for dividers: this line IS the
     field — it is the only thing identifying the control — so it has to clear
     WCAG 1.4.11 non-text contrast at 3:1. The divider grey measures 1.77:1 on
     white and would fail; this one measures 4.78:1. */
  border-bottom: 1px solid var(--c-muted);
  border-radius: 0;             /* iOS rounds inputs unless told otherwise */
  -webkit-appearance: none;
          appearance: none;
  transition: border-color var(--dur-mid) var(--ease-soft);
}

.field__input:hover  { border-bottom-color: var(--c-ink-soft); }
/* The rule darkens to ink on focus. The global :focus-visible ring is left in
   place on top of it deliberately — the colour shift alone is a 1px cue, and
   it is not enough on its own for anyone relying on the keyboard. */
.field__input:focus  { border-bottom-color: var(--c-ink); }

/* Touch devices get taller fields and links. On a mouse the compact rhythm is
   what lets this page hold to a single screen, so the extra height is spent
   only where it buys something — the same trade the header nav makes, in
   reverse. Both sit above the 24px minimum either way. */
@media (pointer: coarse) {
  .field__input  { padding-top: var(--space-3xs); }
  .contact__link { padding-block: var(--space-3xs); }
}

.field__input--area {
  min-height: clamp(3.75rem, 9vh, 6rem);
  resize: vertical;             /* horizontal resize would break the column */
}

/* Ranged left, on the column's own left edge — unlike the page-level CTAs,
   which are centred under their sections. */
.form__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-start;
  gap: var(--space-sm);
  margin-top: var(--space-2xs);
}

/* Left in the document at all times, even while empty — it is 0px tall with no
   text in it. Hiding it with display:none would take it out of the
   accessibility tree, and a live region that is not in the tree when its text
   arrives is never announced. */
.form__status {
  font-size: var(--t-meta);
  letter-spacing: var(--track-meta);
  color: var(--c-muted);
}

/* ==========================================================================
   7c · PROJECT — one work, two asymmetrical columns
   ========================================================================== */

/* Same --edge-margin as the header, the project grid and the footer, so the
   photographs and the header title share one line down the left of the page. */
/* Top-aligned, not centred. The picture is now sized to fill the screen on
   purpose, so the block is taller than the viewport — and centring a block
   taller than its container pushes the top of it up out of view. */
.page--screen .project {
  flex: 1 1 auto;
  display: grid;
  align-content: start;
}

.project {
  padding-block: var(--edge-margin);
  padding-inline: var(--edge-margin);
  max-width: none;
}

.project__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-lg);
}

/* --- The slider ----------------------------------------------------------
   A fixed 4:3 box that fills its column, so its size follows the 2:1 split
   rather than the viewport height. The horizontal photographs are cropped to
   4:3 at source and fill it exactly, edge to edge; the squares and verticals
   fill its HEIGHT and sit narrower, uncropped, centred. Nothing is ever cut. */
.slider__frame {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  overflow: hidden;
}

/* Inactive slides are display:none rather than transparent, and that is a
   loading decision, not a visual one: twelve 2.2MB photographs are ~26MB, and
   a lazy image inside a display:none box is never fetched, so only the slide
   you are looking at costs anything. main.js §6 warms the next and previous
   ones in the background so navigation still feels instant. */
.slider__slide {
  display: none;
  position: absolute;
  inset: 0;
}

.slider__slide.is-active {
  display: block;
  animation: slide-fade var(--dur-mid) var(--ease-soft);
}

@keyframes slide-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

.slider__img {
  width: 100%;
  height: 100%;
  object-fit: contain;          /* never crop */
  object-position: center;
}

/* Sits in the grid row directly beneath the frame and spans the same column,
   so the arrows land on the box's bottom-left and bottom-right corners with
   the count on its centre line. */
.slider__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: var(--space-2xs);
}

.slider__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;              /* 44 x 44 */
  margin-inline: calc(var(--space-2xs) * -1);   /* glyph on the box's edge */
  color: var(--c-muted);
  transition: color var(--dur-mid) var(--ease-soft),
              transform var(--dur-mid) var(--ease-out);
}
.slider__btn:hover { color: var(--c-ink); }
.slider__btn[data-dir="-1"]:hover { transform: translateX(-2px); }
.slider__btn[data-dir="1"]:hover  { transform: translateX(2px); }

.slider__btn svg {
  width: 0.875rem;
  height: 0.875rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.25;
}

.slider__count {
  font-size: var(--t-meta);
  letter-spacing: var(--track-meta);
  color: var(--c-muted);
  /* Proportional digits would let the count jitter sideways as it counts —
     a 1 is narrower than an 8. Tabular figures hold it still. */
  font-variant-numeric: tabular-nums;
}

/* The current number is the only part in full ink; the total stays quiet. */
.slider__count b {
  font-weight: var(--w-regular);
  color: var(--c-ink);
}

/* Without JS there is no slider — the photographs simply stack down the column
   at their own proportions, and the controls that would do nothing are removed
   rather than left sitting there. */
.no-js .slider__frame { height: auto; width: 100%; aspect-ratio: auto; overflow: visible; }
.no-js .slider__slide { display: block; position: static; }
.no-js .slider__slide + .slider__slide { margin-top: var(--space-sm); }
.no-js .slider__img { height: auto; }
.no-js .slider__bar { display: none; }

/* --- Previous / next project ---------------------------------------------
   Sits below both columns, on the page's full width, with the catalogue's
   neighbours either side. Kept shallow: it is a footer to the page, not a
   section of it. */
/* Sits centred in the band between its own divider and the footer rule: the
   padding above it is the same --edge-margin that .project puts below it. */
.project-nav {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-sm);
  margin-top: var(--space-sm);
  padding-top: var(--edge-margin);
  border-top: 1px solid var(--c-rule);
}

.project-nav__link {
  display: flex;
  flex-direction: column;
  /* Not stretch: it keeps each line at its own text width, which is what lets
     the hover rule hug the label instead of running the full width of the
     link box. The --next variant flips this to flex-end. */
  align-items: flex-start;
  /* Enough room for the hover rule to sit between the two lines rather than
     crowding either of them. */
  gap: var(--space-3xs);
  color: var(--c-ink);
}

.project-nav__link--next { text-align: right; align-items: flex-end; }

.project-nav__label {
  font-size: var(--t-meta);
  letter-spacing: var(--track-meta);
  text-transform: uppercase;
  color: var(--c-muted);
  transition: color var(--dur-mid) var(--ease-soft);
}
.project-nav__link:hover .project-nav__label { color: var(--c-ink); }

.project-nav__name {
  font-size: var(--t-label);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  line-height: var(--lh-snug);
}

/* --- The record ---------------------------------------------------------- */

/* Uppercase, on the site name's size — the ceiling. The reference sets this at
   35px; held here to --t-name so the project title never outranks the studio
   name in the header above it. */
.project__title {
  font-size: var(--t-name);
  font-weight: var(--w-regular);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  line-height: var(--lh-snug);
  color: var(--c-ink);
}

/* Left exactly as written in the folder — "Kyiv | 2026 | 110 sq.m." — with no
   text-transform, because uppercasing it would turn "sq.m." into "SQ.M.". */
.project__meta {
  margin-top: var(--space-2xs);
  font-size: var(--t-label);
  letter-spacing: 0.02em;
  color: var(--c-muted);
}

/* Several of the projects carry credit lines after the description — "Project
   by ...", "Scope of work: ..." — so this can repeat. The first sits away from
   the meta line; the rest sit close under the paragraph they belong to. */
.project__desc + .project__desc { margin-top: var(--space-2xs); }

.project__desc {
  margin-top: var(--space-sm);
  font-size: var(--t-about);
  line-height: 1.85;
  letter-spacing: 0.012em;
  color: var(--c-ink-soft);
}

/* Stacked, below the two-column breakpoint, this column would otherwise run the
   full page width and the square plan would become a 1200px block. Capped to a
   readable measure; the cap is lifted once the column has its own width. */
.project__info { max-width: 34rem; }

/* No max-width on the description, which is the point: the description
   fills it, so the plan below — set to 100% — lands exactly as wide as the
   text above it. */
.project__plan {
  margin-top: var(--space-md);
}

.project__plan img {
  display: block;
  width: 100%;
  height: auto;
}

/* ==========================================================================
   7d · ABOUT PAGE
   The text blocks reuse `.about` and `.about__body` from section 6 — same
   60rem measure, same 14px type, same 1.85 leading, same --edge-margin
   gutters as the homepage. Only the three rules below are new.
   ========================================================================== */

/* On the homepage the body sits under a lead paragraph and needs the gap. Here
   it is the first thing in the section, so the gap would be dead space. */
.about__body--flush { margin-top: 0; }

/* The marquee owns the space on both sides of itself. Zeroing the padding
   that would otherwise stack under the About block and over Services is what
   makes the two gaps identical — otherwise the top gap is a section padding
   PLUS the strip's padding, and the bottom is only the strip's. */
.about--about-page { padding-bottom: 0; }
.about--services   { padding-top: 0; }

/* The stage names, set apart by weight alone at the same size — exactly as
   the homepage's "My approach:" labels are. */
.about__body b {
  font-weight: var(--w-medium);
  color: var(--c-ink);
}

/* --- The marquee ---------------------------------------------------------
   Full bleed, ignoring the page gutter: the strip is meant to run off both
   edges so it reads as continuous rather than as a boxed gallery. */
.marquee {
  /* The only spacing between the strip and the text either side of it, so the
     two gaps are the same figure by construction rather than by arithmetic.
     This padding sits OUTSIDE the scroll viewport below, which is what keeps
     the empty band above and below the pictures from behaving as part of the
     strip — hovering nowhere used to stop the scroll. */
  padding-block: var(--space-section);
}

/* The strip is a real scroll container, not a transformed track.
   It used to be a CSS keyframe animation on a translated track. That could not
   be dragged, and the pause was `:hover`, which on a touchscreen latches on
   first tap and never releases — the reported bug where one touch froze the
   strip for good. A scroll container gives native touch panning and momentum
   for free, drag-to-scroll with a few lines of pointer handling, and a pause
   that JavaScript owns and can always clear. */
.marquee__viewport {
  overflow-x: auto;
  overflow-y: hidden;
  /* html sets scroll-behavior:smooth for anchor links. Inherited here it would
     turn every one-pixel step of the auto-scroll into a queued smooth scroll
     and the strip would judder to a halt. */
  scroll-behavior: auto;
  /* Horizontal panning is ours; vertical still scrolls the page, so a finger
     that lands on a picture can still scroll past it. */
  touch-action: pan-x pan-y;
  overscroll-behavior-x: contain;
  cursor: grab;
  /* The scrollbar is noise under a photograph — the strip advertises itself by
     moving. Firefox, then WebKit below. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.marquee__viewport::-webkit-scrollbar { display: none; }

.marquee__viewport.is-dragging { cursor: grabbing; }
/* While a drag is in flight the pictures must not swallow the pointer or start
   a native image drag, which would abandon the gesture halfway. */
.marquee__viewport.is-dragging img { pointer-events: none; }

.marquee__track {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  width: max-content;
}

.marquee__item {
  flex: none;
  height: clamp(16rem, 34vh, 30rem);
}

.marquee__item img {
  display: block;
  height: 100%;
  width: auto;
  filter: grayscale(1);
  transition: filter var(--dur-slow) var(--ease-soft);
  /* A picture dragged out of the page is the browser's default and it fights
     the grab gesture. */
  -webkit-user-drag: none;
  user-select: none;
}

/* Grayscale by default, full colour under the cursor — a filter and a
   transition, nothing more.
   Behind a hover query: on a touchscreen `:hover` sticks after the first tap
   and the picture stays coloured with no way to undo it. */
@media (hover: hover) and (pointer: fine) {
  .marquee__item:hover img { filter: grayscale(0); }
}

/* The touch equivalent. :active releases on touchend where :hover does not, so
   a held picture colours and lets go by itself. */
.marquee__item:active img { filter: grayscale(0); }

/* Keyboard focus colours it too, on any input type. */
.marquee__item:focus-within img { filter: grayscale(0); }

/* Reduced motion is handled in JS now: the auto-advance never starts, and the
   strip stays a scroll container the visitor can pan by hand or by wheel. The
   scrollbar comes back for them, since without motion there is nothing else to
   say the strip goes on past the edge of the screen. */
@media (prefers-reduced-motion: reduce) {
  .marquee__viewport {
    scrollbar-width: thin;
    -ms-overflow-style: auto;
  }
  .marquee__viewport::-webkit-scrollbar { display: block; height: 8px; }
}

/* With JavaScript off there is no auto-advance, so the strip must still be
   readable by hand. It is a scroll container either way — this just restores
   the scrollbar so it looks like one. */
.no-js .marquee__viewport {
  scrollbar-width: thin;
  -ms-overflow-style: auto;
}
.no-js .marquee__viewport::-webkit-scrollbar { display: block; height: 8px; }


/* ==========================================================================
   8 · FOOTER
   ========================================================================== */

/* Full width, on the same gutter as the header and the work grid.
   Even padding top and bottom — the arrow now sits ABOVE the icon row rather
   than below it, so the footer no longer needs a deep empty band to clear it. */
.footer {
  padding-block: var(--space-md);
  padding-inline: var(--edge-margin);
  border-top: 1px solid var(--c-rule);
}
.footer > .wrap {
  max-width: none;
  padding-inline: 0;
}

/* One line, three parts: copyright left · Media centred · Contact right.
   Grid rather than space-between, so the middle group is centred on the PAGE
   rather than in whatever gap the other two leave behind. */
.footer__inner {
  display: grid;
  grid-template-columns: 1fr;
  justify-items: center;
  align-items: center;
  gap: var(--space-sm);
  text-align: center;
}

.footer__group {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
}

.footer__label {
  font-size: var(--t-meta);
  letter-spacing: var(--track-meta);
  text-transform: uppercase;
  color: var(--c-muted);
  white-space: nowrap;
}

.icon-row {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
}

.icon-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.75rem;
  height: 2.75rem;                  /* 44 × 44 with 12px gaps between */
  color: var(--c-muted);
  transition: color var(--dur-mid) var(--ease-soft),
              transform var(--dur-mid) var(--ease-out);
}
.icon-link:hover { color: var(--c-ink); transform: translateY(-2px); }
.icon-link svg { width: 1.125rem; height: 1.125rem; fill: currentColor; }

.footer__fine {
  font-size: var(--t-meta);
  letter-spacing: var(--track-meta);
  text-transform: uppercase;
  color: var(--c-muted);
}

/* ==========================================================================
   8b · BACK TO TOP
   Appears once the reel is behind you. Hidden from assistive tech and removed
   from the tab order while invisible, so it is never a focus stop that leads
   nowhere on a page you have not scrolled.
   ========================================================================== */

/* A 32px disc carrying a 14px arrow. It floats over whatever the page happens
   to be showing, so it brings its own ground rather than trusting the backdrop.
   Permanently fixed: it appears once you have scrolled and then stays put all
   the way to the foot of the page. It never docks and never hides. The footer
   carries extra bottom padding so its icons clear this corner instead. */
.to-top {
  position: fixed;
  /* The disc is 28px and the footer's icon boxes are 44px, and both rows end
     at --edge-margin — so pinning the disc to that margin left it sitting 8px
     out from the phone icon's centre rather than above it. Inset it by half
     the difference and the two share a centre line. Written from the two sizes
     rather than as a flat 8px, so it stays centred if either changes. */
  --to-top-size: 1.75rem;
  right: calc(var(--edge-margin) + (2.75rem - var(--to-top-size)) / 2);
  /* Rests just above the footer's icon row (--space-md + 44px from the bottom),
     plus 1px so the boxes never touch. Derived from the footer's own padding,
     so the two stay in step if it changes. */
  bottom: calc(var(--space-md) + 2.75rem + 1px);
  z-index: var(--z-header);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* A disc, 28px, carrying a 12px arrow. It floats over whatever the page is
     showing — project photography, the About strip — so it carries its own
     ground rather than relying on what happens to be behind it.
     The arrow came down with the disc (14 -> 12) to keep the 8px ring of
     ground around it. That ring is what makes the glyph read as sitting on a
     plate rather than crowded by its own edge, so the two sizes move together;
     shrinking the disc alone eats the ring first.
     28 is the hard floor: WCAG 2.5.8 asks 24 x 24 of any pointer target, and
     below that the button fails outright. At the other end, 44 would cross the
     footer's top rule when the page is scrolled to the end. */
  width: var(--to-top-size);
  height: var(--to-top-size);
  border-radius: 50%;
  /* 82% and not 70%, and the figure is a contrast floor rather than a taste
     call. The arrow is --c-muted (#7a7168); over a black photograph a 70%
     white disc mixes to #b3b3b3 and the arrow measures 2.28:1 against it,
     under the 3:1 WCAG 1.4.11 asks of a graphical control. 82% mixes to #d1d1d1
     and measures 3.13:1, so the worst backdrop the site can put behind it
     still clears the bar. Over the page's own white it disappears, which is
     where the arrow alone was already legible. The hairline gives it an edge
     on a pale image without darkening it on a plain background. */
  background: rgba(255, 255, 255, 0.82);
  border: 1px solid rgba(22, 19, 15, 0.08);
  color: var(--c-muted);
  opacity: 0;
  visibility: hidden;
  transform: translateY(0.5rem);
  transition: opacity var(--dur-mid) var(--ease-out),
              transform var(--dur-mid) var(--ease-out),
              background-color var(--dur-mid) var(--ease-soft),
              color var(--dur-mid) var(--ease-soft),
              visibility 0s linear var(--dur-mid);
}

.to-top.is-visible {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition-delay: 0s;
}


.to-top:hover {
  color: var(--c-ink);
  background: rgba(255, 255, 255, 0.92);
}

/* 12px, down from 14 when the disc went 32 -> 28. Sized with the disc so the
   ring of ground around the glyph stays at 8px — see the note on `.to-top`. */
.to-top svg {
  width: 0.75rem;
  height: 0.75rem;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.25;
}

/* ==========================================================================
   BREAKPOINTS — 640 / 900 / 1024 / 1280
   Mobile-first: every rule above is the small-screen case.
   ========================================================================== */

/* --- Phones: the reel starts below the header, not behind it ---------------
   The homepage header is absolute so the reel can run full-bleed underneath
   it, which is the intended look on a wide screen where the reel stands
   500-800px tall and the bar covers a sliver of sky. On a phone the reel is
   only as tall as 16:9 of a 390px screen — 220px — and the 72px bar swallowed
   the top third of the composition.

   Below 640 the header goes back into the flow and the reel starts under it.
   Nothing else changes: the plate is rgba(255,255,255,0.6) over white, which
   composites to the same solid white every other page's header already shows,
   and the ink hairline renders at identical strength. This is the same switch
   `.header--solid` makes on the subpages. */
@media (max-width: 39.999em) {
  .header { position: static; }
}

@media (min-width: 40em) {          /* 640px — two up */
  .work__grid { grid-template-columns: repeat(2, 1fr); }

  /* Equal outer columns keep the Media group on the page's true centre. */
  .footer__inner {
    grid-template-columns: 1fr auto 1fr;
    text-align: left;
  }
  .footer__inner > :first-child { justify-self: start; }
  .footer__inner > :last-child  { justify-self: end; }
}

@media (min-width: 56.25em) {       /* 900px — three-column header appears */
  .header__inner { grid-template-columns: 1fr auto 1fr; }
  .header__title { display: block; }
  .header__name  { justify-self: center; text-align: center; }
  .nav { display: block; }
  .menu-toggle { display: none; }

  /* Content-sized tracks, centred. Two equal 1fr columns looked centred but
     were not: the form is capped at 24rem and sat at the left of a 592px
     column, so 208px of slack collected on the right — the block came out
     375px from the left edge against 583px from the right. Sizing each track
     to what is actually in it and centring the pair puts equal space either
     side, and leaves the gap between the columns at one --space-lg. */
  /* Two columns, and from here up they are CENTRED on the page rather than
     pushed onto the outer margins.

     900px is exactly the right line to draw it at, because it is the same line
     where the block stops being one stacked column and becomes two. Below it
     there is a single column of text and the only sensible place for it is the
     page's left margin, flush with the header and the footer. At and above it
     there are two short columns that do not grow — the text holds its measure,
     the form is capped at 24rem — so pushing them to opposite edges just opens
     a hole down the middle that gets wider with every extra pixel of screen.

     `auto` on the text and `minmax` on the form size each track to what is
     actually in it; centring the pair then puts equal space either side and
     leaves one --space-lg between them. */
  .contact__grid {
    max-width: 78rem;
    margin-inline: auto;
    grid-template-columns: auto minmax(16rem, 24rem);
    justify-content: center;
    /* Fluid, and the floor is doing real work. At 900 — the tightest the two
       columns ever get — the pair is 800px inside an 838px measure, so there
       are only 38px of slack and a flat --space-xl (96px) would crowd it. 6vw
       stays below 64 until about 1066px, so narrow laptops keep exactly the
       gap they have now, and it opens up from there: 77px at 1280, 86 at 1440,
       96 from 1600 up, where it stops. */
    column-gap: clamp(var(--space-lg), 6vw, var(--space-xl));
    row-gap: clamp(var(--space-sm), 5vh, var(--space-lg));
    align-items: start;
  }

  /* Fills its own track now; the track is what caps it. A 550px-wide text
     input reads as a form to fill in rather than a note to write. */
  .contact__panel { width: 100%; }
}

@media (min-width: 64em) {          /* 1024px — the 3 × 2 grid */
  .work__grid {
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-lg) var(--space-md);
  }
}

/* ==========================================================================
   9 · REDUCED MOTION
   Honour the OS setting: no reveals, no scaling, no smooth scroll.
   The pause button in the hero also starts in the paused state via JS.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }

  .reveal { opacity: 1; transform: none; }
  .work__link:hover .work__img { transform: scale(1.001); }
  .icon-link:hover   { transform: none; }
  .slider__btn:hover { transform: none; }
}

/* ==========================================================================
   PRINT — portfolio pages get printed more often than you'd think
   ========================================================================== */

@media print {
  .header, .hero__pause, .menu-panel, .skip-link, .work__cta { display: none; }
  body { color: #000; }
  .work__grid { gap: var(--space-md); }
}

/* ==========================================================================
   PROJECT — two columns
   Held back to 1280px, not 1024. At 1024 a quarter column is only ~227px, and
   a 14px paragraph in 227px sets about 26 characters to the line — a ribbon,
   not a column of text. Below this the slider takes the full width and the
   record sits beneath it.
   ========================================================================== */

@media (min-width: 80em) {
  /* Two thirds picture, one third record — held at every width, which is the
     whole point. An earlier version sized the picture from the viewport height
     so that it and its arrows always fitted one screen; that kept the arrows in
     view but let the split slide from 76/24 on a tall window down to 50/50 on a
     laptop. The proportion is what matters, so the width sets the size and a
     short window simply scrolls. */
  .project__grid {
    grid-template-columns: 2fr 1fr;
    column-gap: var(--space-lg);
    align-items: start;
  }

  .project__info { max-width: none; }
}

/* ==========================================================================
   PROJECT — the plan stops growing
   A square drawing at a third of a 2560px page comes out 800px across, far
   larger than a reference plan needs and it dwarfs the description above it.

   Held back to 1600px on purpose. Below that the column is close enough to
   26rem that a cap would clip the plan a few pixels narrower than the text
   beside it — near enough to matching to read as a mistake. So under 1600 the
   plan simply fills the column and the two are exactly equal.
   ========================================================================== */

@media (min-width: 100em) {
  .project__plan img { max-width: 31rem; }

  /* Contact needs nothing here: it centres from 900 up, in the block above. */

  /* Large screens only: the picture and its arrows must land on the first
     screen with no scrolling.

     Nothing else moves to achieve it. The columns stay 2:1, the block stays
     full width on the standard margin, and every screen under 1600px is left
     exactly as it is — those scroll, which is what the client asked for.

     Only the picture box gets a ceiling: 9.5rem is the header, this section's
     top padding, the gap and the arrow bar, so what is left is the height the
     picture may occupy. Its width follows from 4:3, capped at the column. When
     the column is the smaller of the two — a tall window — this does nothing
     at all and the picture fills its column as before. */
  .slider {
    --picture-w: min(100%, calc((100svh - 9.5rem) * 4 / 3));
  }

  /* Both, from one figure: the bar has to stay exactly as wide as the picture
     or the arrows stop sitting on its corners. */
  .slider__frame,
  .slider__bar { width: var(--picture-w); }
}
