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

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

:root {
  --ink: #0d0f12;
  --paper: #f4f2ef;
  --line: rgba(255, 255, 255, 0.12);
}

html {
  scroll-behavior: smooth;
}

body {
  font-family: "Jost", system-ui, sans-serif;
  font-weight: 300;
  color: #fff;
  background: var(--ink);
  overflow-x: hidden;
}

img {
  user-select: none;
}

/* present for screen readers and search engines, invisible on screen */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------- Navigation ---------- */

.nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 20;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  column-gap: 3rem;
  padding: 1.5rem 2rem;
}

/* Soft shade behind the header so the wordmark stays readable on any photo */
.nav::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 190px;
  background: linear-gradient(
    to bottom,
    rgba(8, 9, 11, 0.5) 0%,
    rgba(8, 9, 11, 0.28) 45%,
    rgba(8, 9, 11, 0) 100%
  );
  pointer-events: none;
  z-index: -1;
}

/* The bar takes on its own ground gradually, in step with the scroll:
   --nav-solid runs 0 -> 1 across the height of the hero image, so the bar
   is fully opaque exactly when the photo gives way to the next section.
   Script.js writes the variable; no transition, the scroll drives it. */
.nav {
  --nav-solid: 0;
  background: rgba(13, 15, 18, calc(var(--nav-solid) * 0.88));
  backdrop-filter: blur(calc(var(--nav-solid) * 12px));
  -webkit-backdrop-filter: blur(calc(var(--nav-solid) * 12px));
  border-bottom: 1px solid rgba(255, 255, 255, calc(var(--nav-solid) * 0.09));
  padding-top: calc(1.5rem - var(--nav-solid) * 0.5rem);
  padding-bottom: calc(1.5rem - var(--nav-solid) * 0.5rem);
}

.nav::before {
  opacity: calc(1 - var(--nav-solid));
}

.nav a {
  color: #fff;
  text-decoration: none;
  /* the shadow is only needed while the bar is still see-through */
  text-shadow: 0 1px 10px rgba(0, 0, 0, calc(0.42 * (1 - var(--nav-solid))));
}

.nav__logo {
  font-family: "Italiana", serif;
  font-size: 1.35rem;
  text-transform: uppercase;
  letter-spacing: 0.34em;
  line-height: 1;
  white-space: nowrap;
  padding-left: 0.34em; /* balance the trailing letter-spacing */
}

.nav__links {
  display: flex;
  gap: 2.25rem;
}

.nav__links--left {
  justify-self: end;
}

.nav__links--right {
  justify-self: start;
}

.nav__links a {
  position: relative;
  font-size: 0.8rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  opacity: 0.7;
  transition: opacity 200ms ease;
}

.nav__links a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -5px;
  height: 1px;
  background: #fff;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 280ms cubic-bezier(0.16, 1, 0.3, 1);
}

.nav__links a:hover,
.nav__links a.is-current {
  opacity: 1;
}

.nav__links a:hover::after,
.nav__links a.is-current::after {
  transform: scaleX(1);
}

.lang {
  display: inline-flex;
  gap: 0.4rem;
  align-items: center;
}

.lang a {
  font-size: 0.72rem;
  letter-spacing: 0.09em;
  opacity: 0.45;
}

.lang a.is-current {
  opacity: 1;
}

.lang span {
  opacity: 0.3;
  font-size: 0.72rem;
}

/* Header entrance: the wordmark fades in, the menu pairs shoot in from
   the screen edges */
/* The wordmark only fades - it must not shift, because the title sequence
   measures it as the place the flying words have to land, and a target that
   is still travelling cannot be measured accurately. */
@keyframes nav-fade-in {
  from { opacity: 0; }
}

@keyframes pair-left {
  from { opacity: 0; transform: translateX(-100vw); }
}

@keyframes pair-right {
  from { opacity: 0; transform: translateX(100vw); }
}

.nav__logo {
  animation: nav-fade-in 650ms cubic-bezier(0.16, 1, 0.3, 1) 150ms both;
}

.nav__links--left {
  animation: pair-left 750ms cubic-bezier(0.16, 1, 0.3, 1) 500ms both;
}

.nav__links--right {
  animation: pair-right 750ms cubic-bezier(0.16, 1, 0.3, 1) 640ms both;
}

@media (prefers-reduced-motion: reduce) {
  .nav__logo,
  .nav__links--left,
  .nav__links--right {
    animation: none !important;
  }
}

/* Tablet */
@media (min-width: 821px) and (max-width: 1180px) {
  .nav {
    column-gap: 1.5rem;
  }

  .nav__logo {
    font-size: 1.1rem;
    letter-spacing: 0.26em;
  }

  .nav__links {
    gap: 1.4rem;
  }

  .nav__links a {
    font-size: 0.72rem;
  }
}

/* Mobile: wordmark centered on top, links in one row below it */
@media (max-width: 820px) {
  .nav {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    align-items: center;
    column-gap: 1.4rem;
    row-gap: 0.5rem;
    padding: calc(0.9rem - var(--nav-solid) * 0.2rem) 1rem;
  }

  .nav__logo {
    order: -1;
    flex-basis: 100%;
    text-align: center;
    font-size: 1.05rem;
    letter-spacing: 0.28em;
  }

  .nav__links {
    gap: 1.3rem;
  }

  .nav__links a {
    font-size: 0.7rem;
  }
}

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

.stage {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100vh;
  overflow: hidden;
  user-select: none;
}

.stage__img {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  /* slow drift so the still photograph stays alive */
  animation: hero-drift 26s ease-in-out infinite alternate;
}

@keyframes hero-drift {
  from { transform: scale(1.06) translate3d(0, 0, 0); }
  to   { transform: scale(1.13) translate3d(-1.2%, -1%, 0); }
}

/* Hero1 is bright sky top right and pale stone below, so the scrim is
   angled: it barely touches the sky and deepens towards the bottom left */
.scrim {
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom, rgba(13, 15, 18, 0.34) 0%, rgba(13, 15, 18, 0) 38%),
    linear-gradient(to top, rgba(13, 15, 18, 0.5) 0%, rgba(13, 15, 18, 0) 45%);
  pointer-events: none;
}

.section {
  position: absolute;
  inset: 0;
  display: flex;
  padding: 8rem clamp(2rem, 5vw, 4.5rem) 5rem;
  pointer-events: none;
}

/* Home: the only calm surface in hero1 is the sky, upper right */
.section--sky {
  align-items: flex-start;
  justify-content: flex-end;
  text-align: right;
}

/* Inner pages: the band photo is a backdrop, not a composition, so the
   title sits centred and the veil is heavy enough for any image */
.section--band {
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 7rem clamp(2rem, 5vw, 4.5rem) 3rem;
}

.section--band .content {
  max-width: 760px;
}

.section--band h1 {
  font-size: clamp(2.4rem, 5.4vw, 4rem);
  letter-spacing: 0.03em;
}

.section--band p {
  margin-bottom: 0;
  max-width: 560px;
  margin-left: auto;
  margin-right: auto;
}

.content {
  max-width: 560px;
  pointer-events: auto;
}

/* Home hero cascade: a staircase. The block sits in the right half of the
   picture, over the sky, and each line starts one step further right than
   the one above. The lines are left aligned for this - stepping the right
   edge instead would put every line at the mercy of its own word length,
   and the stairs stop reading as stairs. */
.section--sky {
  text-align: left;
}

.section--sky .content {
  --step: clamp(1rem, 4vw, 4rem);
  /* The block is anchored to the right, so its width is what decides where
     the top step begins - wider pushes the staircase left. It has to hold
     the longest line plus two steps: "CONTEMPORARY" is far longer than the
     Serbian "SAVREMENO", and it is the one that sets the requirement. */
  /* an explicit width, not a max: as a flex item the block would otherwise
     shrink to its longest line and sit wherever that happens to put it */
  width: min(54vw, 800px);
  max-width: 100%;
}

/* Narrow screens cannot carry both a long word and a wide staircase, so the
   steps shrink and the block takes the full column */
@media (max-width: 820px) {
  .section--sky .content {
    --step: clamp(0.4rem, 2.4vw, 1.1rem);
    width: 100%;
  }
}

.h1-line {
  display: block;
}

.section--sky .h1-line:nth-child(2) {
  margin-left: var(--step);
}

.section--sky .h1-line:nth-child(3),
.section--sky .content p,
.section--sky .content .btn {
  margin-left: calc(var(--step) * 2);
}

/* Hero entrance: letters fall from the page top one by one, the paragraph
   types itself out, then the button slides in */
.h1-word {
  display: inline-block;
  white-space: nowrap;
}

.h1-char {
  display: inline-block;
  opacity: 0;
  transform: translateY(-60vh);
  transition: opacity 180ms ease;
}

.is-visible .h1-char {
  opacity: 1;
  transform: translateY(0);
  transition:
    transform 540ms cubic-bezier(0.45, 0.45, 0.75, 1),
    opacity 420ms linear;
  transition-delay: calc(var(--ci, 0) * 55ms);
}

.section .content p {
  opacity: 0;
}

.section.is-visible .content p {
  opacity: 0.92;
}

/* The button travels the whole width of the screen from the left and
   eases into place, with a soft fade riding along */
.section .btn {
  opacity: 0;
  transform: translateX(-100vw);
}

.section .btn.is-shown {
  opacity: 1;
  transform: translateX(0);
  transition:
    transform 1250ms cubic-bezier(0.16, 1, 0.3, 1),
    opacity 900ms ease;
}

@media (prefers-reduced-motion: reduce) {
  .h1-char,
  .is-visible .h1-char,
  .section .btn {
    transition: none !important;
    transform: none !important;
    opacity: 1 !important;
  }

  .stage__img {
    animation: none;
  }
}

.content h1 {
  font-family: "Italiana", serif;
  font-size: clamp(2.1rem, 5.6vw, 4.8rem);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0;
  line-height: 0.98;
  margin-bottom: 1.1rem;
  text-shadow: 0 2px 24px rgba(13, 15, 18, 0.28);
}

.content p {
  font-size: 1.02rem;
  font-weight: 300;
  line-height: 1.75;
  letter-spacing: 0.01em;
  margin-bottom: 1.75rem;
  min-height: 1.75em;
  text-shadow: 0 1px 16px rgba(13, 15, 18, 0.35);
}

.btn {
  display: inline-block;
  padding: 0.8rem 2rem;
  border: 1px solid rgba(255, 255, 255, 0.6);
  border-radius: 999px;
  color: #fff;
  text-decoration: none;
  font-size: 0.78rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  transition: background-color 200ms ease;
}

.btn:hover {
  background-color: rgba(255, 255, 255, 0.15);
}

/* Scroll hint at the bottom of the hero */
.scroll-cue {
  position: absolute;
  left: 50%;
  bottom: 2rem;
  transform: translateX(-50%);
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.22em;
  opacity: 0;
  animation: cue-in 800ms ease 2600ms forwards;
}

.scroll-cue::after {
  content: "";
  display: block;
  width: 1px;
  height: 42px;
  margin: 0.7rem auto 0;
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0));
  animation: cue-pulse 2.4s ease-in-out infinite;
}

@keyframes cue-in {
  to { opacity: 0.7; }
}

@keyframes cue-pulse {
  0%, 100% { transform: scaleY(1); opacity: 1; }
  50% { transform: scaleY(0.55); opacity: 0.4; }
}

@media (prefers-reduced-motion: reduce) {
  .scroll-cue,
  .scroll-cue::after {
    animation: none;
    opacity: 0.7;
  }
}

/* ---------- Hero variant B: the name cut out of a white sheet ----------

   A white layer covers the photograph, and the letters are holes in it.
   The trick is `mix-blend-mode: screen` on that layer: screen keeps white
   as white and lets the backdrop through wherever the layer is black. So
   the sheet is painted white with black type on it, and the black type
   turns into the photograph underneath.
   Real HTML text, not an image or an SVG - it stays selectable, scales
   with the viewport, and keeps the Italiana spacing used everywhere else. */

.cutout {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: #fff;
  mix-blend-mode: screen;
  pointer-events: none;
  overflow: hidden;
  padding: 0 clamp(0.5rem, 2vw, 2rem);
}

/* The word width is already at the edge of the screen, so height cannot be
   won by setting the type larger - it would only spill sideways. The lines
   are stretched vertically instead, which fills the screen and gives the
   name a tall, drawn look. The stretch sits on each line rather than on the
   pair, because the two words have to be able to travel apart later.
   script.js writes both the factor and the matching margins, so the layout
   reserves the stretched height and the lines never grow into each other. */
.cutout__stack {
  display: block;
}

.cutout__type {
  font-family: "Italiana", serif;
  color: #000;
  text-transform: uppercase;
  /* driven by height as well as width, so two lines fill the screen
     rather than floating in the middle of it */
  font-size: min(24vw, 46vh);
  line-height: 0.84;
  letter-spacing: 0.03em;
  text-align: center;
  /* the trailing letter-space would otherwise push the word off centre */
  text-indent: 0.03em;
  display: block;
  white-space: nowrap;
}

/* Each letter arrives its own way - script.js deals one of these at random,
   with a random delay, so the name assembles from all directions instead of
   marching in. A letter caught mid-fade is grey rather than black, and the
   screen blend turns that into a half-developed photograph inside it. */
.cut-char {
  display: inline-block;
  opacity: 0;
}

.cutout.is-ready .cut-char {
  animation-duration: 1000ms;
  animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
  animation-fill-mode: both;
}

.cutout.is-ready .cut-char.v-fade  { animation-name: cut-fade; animation-duration: 1400ms; }
.cutout.is-ready .cut-char.v-drop  { animation-name: cut-drop; }
.cutout.is-ready .cut-char.v-rise  { animation-name: cut-rise; }
.cutout.is-ready .cut-char.v-left  { animation-name: cut-left; }
.cutout.is-ready .cut-char.v-right { animation-name: cut-right; }
.cutout.is-ready .cut-char.v-zoom  { animation-name: cut-zoom; }

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

@keyframes cut-drop {
  from { opacity: 1; transform: translateY(-115vh); }
  to   { opacity: 1; transform: none; }
}

@keyframes cut-rise {
  from { opacity: 1; transform: translateY(115vh); }
  to   { opacity: 1; transform: none; }
}

@keyframes cut-left {
  from { opacity: 1; transform: translateX(-110vw); }
  to   { opacity: 1; transform: none; }
}

@keyframes cut-right {
  from { opacity: 1; transform: translateX(110vw); }
  to   { opacity: 1; transform: none; }
}

@keyframes cut-zoom {
  from { opacity: 0; transform: scale(0.2); }
  to   { opacity: 1; transform: none; }
}

/* Handover: the sheet dissolves and hero A takes the screen. While the
   sequence runs, everything belonging to hero A is held back - the header
   animations are paused at their first frame, and the scrim is off so the
   letters carry the photograph's own colour rather than a darkened one. */
/* The name has to survive the sheet it was cut out of, or it dissolves
   halfway and never reaches the header. So the layer is not faded out.
   Instead its white ground turns transparent while the letters turn from
   black to white. Under the screen blend that reads as: the white sheet
   lifts to show the photograph, and the letters stop being holes and carry
   on as solid white type - the colour the header wordmark already is. */
.cutout {
  transition: background-color 1000ms ease;
}

.cutout.is-gone {
  background-color: transparent;
}

.cutout__type {
  transition:
    transform 1250ms cubic-bezier(0.5, 0, 0.2, 1),
    color 1000ms ease;
}

.cutout.is-gone .cutout__type {
  color: #fff;
}

/* the swap at the end is instant: the flying name is removed in the same
   frame the header wordmark appears, and they occupy the same pixels */
.cutout.is-done {
  display: none;
}

/* On the way out the two words separate and each flies to its own half of
   the header wordmark - STUDIO onto STUDIO, AUTORI onto AUTORI - so the
   stack turns itself horizontal in the air and lands letter over letter.
   script.js measures both targets and writes each transform. */

.logo-word {
  display: inline-block;
}

.scrim {
  transition: opacity 950ms ease;
}

body.has-intro .scrim {
  opacity: 0;
}

/* the photograph comes up while the name is still travelling, so the scrim
   arrives with it rather than waiting for the header */
body.has-intro.intro-out .scrim {
  opacity: 1;
}

body.has-intro .nav::before {
  opacity: 0;
}

body.has-intro .nav__logo,
body.has-intro .nav__links--left,
body.has-intro .nav__links--right,
body.has-intro .scroll-cue {
  animation-play-state: paused;
}

@media (prefers-reduced-motion: reduce) {
  .cut-char,
  .cutout.is-ready .cut-char {
    animation: none;
    opacity: 1;
    transform: none;
  }
}

/* ---------- Content scrolling over the fixed hero ---------- */

.pages {
  position: relative;
  z-index: 5;
  margin-top: 100vh;
  background: var(--ink);
}

/* Inner pages have a shorter hero band instead of a full screen */
.stage--band {
  height: 62vh;
}

/* Heavier veil: these photos are dusk shots and busy interiors, and the
   title has to hold on all of them */
.stage--band .scrim {
  background:
    linear-gradient(to bottom, rgba(13, 15, 18, 0.7) 0%, rgba(13, 15, 18, 0.34) 42%, rgba(13, 15, 18, 0.66) 100%),
    radial-gradient(ellipse 70% 60% at 50% 45%, rgba(13, 15, 18, 0.5) 0%, rgba(13, 15, 18, 0) 100%);
}

.pages--band {
  margin-top: 62vh;
}

.block {
  padding: 6rem clamp(1.5rem, 5vw, 4.5rem);
}

.block__inner {
  max-width: 680px;
  margin: 0 auto;
  text-align: center;
}

.block__inner--wide {
  max-width: 1280px;
}

.block h2 {
  font-family: "Italiana", serif;
  font-size: clamp(1.9rem, 4vw, 3rem);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  line-height: 1.05;
  margin-bottom: 1.25rem;
}

.block p {
  font-size: 1.02rem;
  line-height: 1.8;
  letter-spacing: 0.01em;
  opacity: 0.82;
}

.block p + p {
  margin-top: 1.1rem;
}

.block .btn {
  margin-top: 2rem;
}

.eyebrow {
  display: block;
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.24em;
  opacity: 0.5;
  margin-bottom: 1.1rem;
}

/* Reveal on scroll */
.reveal {
  opacity: 0;
  transform: translateY(26px);
}

.reveal.is-in {
  opacity: 1;
  transform: none;
  transition:
    opacity 620ms ease,
    transform 720ms cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: calc(var(--d, 0) * 80ms);
}

/* Project cards do not fade. Each photograph is hinged along its bottom
   edge, lying flat as if pressed down onto a table, and swings up to
   standing when its turn comes. The perspective is per card so every one
   folds the same way instead of fanning towards a shared vanishing point.
   The easing overshoots slightly past upright, which is what sells the
   release rather than a machine lifting it. */
.card.reveal {
  opacity: 1;
  transform: none;
}

.card.reveal .card__media {
  transform-origin: 50% 100%;
  transform: perspective(900px) rotateX(90deg);
}

.card.reveal.is-in .card__media {
  transform: perspective(900px) rotateX(0deg);
  transition: transform 1150ms cubic-bezier(0.22, 1.32, 0.36, 1);
  transition-delay: calc(var(--d, 0) * 110ms);
}

.card.reveal .card__body {
  opacity: 0;
  transform: translateY(16px);
}

.card.reveal.is-in .card__body {
  opacity: 1;
  transform: none;
  transition:
    opacity 500ms ease,
    transform 600ms cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: calc(var(--d, 0) * 110ms + 260ms);
}

@media (prefers-reduced-motion: reduce) {
  .reveal,
  .card.reveal,
  .card.reveal .card__media,
  .card.reveal .card__body {
    opacity: 1;
    transform: none;
  }
}

/* ---------- Project grid (rows fill left to right) ---------- */

.filters {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
  margin-bottom: 3rem;
}

.filters button {
  font-family: inherit;
  font-size: 0.72rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: #fff;
  background: none;
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 999px;
  padding: 0.55rem 1.25rem;
  cursor: pointer;
  opacity: 0.6;
  transition: opacity 200ms ease, border-color 200ms ease, background-color 200ms ease;
}

.filters button:hover {
  opacity: 1;
}

.filters button.is-on {
  opacity: 1;
  border-color: rgba(255, 255, 255, 0.75);
  background: rgba(255, 255, 255, 0.08);
}

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5rem 1.25rem;
}

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

.card {
  display: block;
  color: #fff;
  text-decoration: none;
}

/* The card markup uses spans (an <a> may not wrap block elements here),
   so they need an explicit block display or the rows collapse into each other */
.card__media {
  display: block;
  position: relative;
  overflow: hidden;
  aspect-ratio: 4 / 5;
  /* the page colour, not a lighter placeholder: an empty frame must not be
     visible before its photograph has slid in */
  background: var(--ink);
}

.card__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 900ms cubic-bezier(0.16, 1, 0.3, 1);
}

.card:hover .card__media img {
  transform: scale(1.045);
}

.card__body {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.8rem 0.1rem 0;
}

.card__title {
  font-size: 0.86rem;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.11em;
}

.card__meta {
  font-size: 0.72rem;
  letter-spacing: 0.09em;
  opacity: 0.5;
  white-space: nowrap;
}

.card.is-hidden {
  display: none;
}

@media (max-width: 900px) {
  .grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 1.1rem 0.9rem;
  }
}

@media (max-width: 560px) {
  .grid,
  .grid--2 {
    grid-template-columns: 1fr;
  }
}

/* ---------- Project page ---------- */

.project-head {
  max-width: 1280px;
  margin: 0 auto;
  padding: 0 clamp(1.5rem, 5vw, 4.5rem);
}

.project-head h1 {
  font-family: "Italiana", serif;
  font-size: clamp(2.2rem, 5vw, 3.6rem);
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  line-height: 1.05;
  margin-bottom: 2rem;
}

.project-layout {
  display: grid;
  grid-template-columns: 1fr 260px;
  gap: 3rem;
  align-items: start;
}

.project-body p {
  font-size: 1rem;
  line-height: 1.85;
  opacity: 0.84;
}

.project-body p + p {
  margin-top: 1.1rem;
}

.facts {
  display: grid;
  gap: 1.2rem;
  border-top: 1px solid var(--line);
  padding-top: 1.4rem;
}

.fact__label {
  display: block;
  font-size: 0.64rem;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  opacity: 0.45;
  margin-bottom: 0.35rem;
}

.fact__value {
  font-size: 0.92rem;
  line-height: 1.5;
  opacity: 0.9;
}

/* Facts laid across instead of down, for a page with no gallery under them */
.facts--row {
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  justify-items: center;
  text-align: center;
  gap: 1.5rem;
  border-top: 0;
  border-bottom: 1px solid var(--line);
  padding: 0 0 2rem;
  margin-bottom: 1rem;
}

@media (max-width: 700px) {
  .facts--row {
    grid-auto-flow: row;
    gap: 1.3rem;
  }
}

.quiet-link {
  color: #fff;
  opacity: 0.55;
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  text-decoration: none;
  border-bottom: 1px solid rgba(255, 255, 255, 0.25);
  padding-bottom: 2px;
  transition: opacity 200ms ease;
}

.quiet-link:hover {
  opacity: 1;
}

@media (max-width: 900px) {
  .project-layout {
    grid-template-columns: 1fr;
    gap: 2.2rem;
  }
}

/* Gallery: a real grid, rows fill left to right */
.gallery {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.9rem;
  max-width: 1280px;
  margin: 0 auto;
  padding: 0 clamp(1.5rem, 5vw, 4.5rem);
}

.gallery button {
  display: block;
  padding: 0;
  border: 0;
  background: var(--ink);
  cursor: zoom-in;
  aspect-ratio: 4 / 3;
  overflow: hidden;
}

.gallery img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  /* `translate` carries the reveal, `transform` the hover zoom, so the two
     never overwrite each other */
  transition:
    transform 900ms cubic-bezier(0.16, 1, 0.3, 1),
    translate 900ms cubic-bezier(0.16, 1, 0.3, 1);
}

/* Gallery reveal: the frame uncovers from the bottom edge upwards while the
   photograph inside travels the other way, at roughly half the speed. The
   picture is never moved as a whole - it is uncovered, and lags behind the
   opening, which is what gives it depth. Quieter and quicker than the fold
   on the work grid, because a project page runs dozens of these in a row. */
.gallery button.reveal {
  opacity: 1;
  transform: none;
  clip-path: inset(100% 0 0 0);
}

.gallery button.reveal img {
  translate: 0 -18%;
}

.gallery button.reveal.is-in {
  clip-path: inset(0 0 0 0);
  transition: clip-path 720ms cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: calc(var(--d, 0) * 70ms);
}

.gallery button.reveal.is-in img {
  translate: 0 0;
  transition-delay: 0ms, calc(var(--d, 0) * 70ms);
}

@media (prefers-reduced-motion: reduce) {
  .gallery button.reveal {
    clip-path: none;
  }

  .gallery button.reveal img {
    translate: none;
  }
}

.gallery button:hover img {
  transform: scale(1.04);
}

@media (max-width: 900px) {
  .gallery {
    grid-template-columns: repeat(2, 1fr);
    gap: 0.6rem;
  }
}

@media (max-width: 560px) {
  .gallery {
    grid-template-columns: 1fr;
  }
}

/* Prev / next */
.project-nav {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  max-width: 1280px;
  margin: 0 auto;
  padding: 4rem clamp(1.5rem, 5vw, 4.5rem) 0;
  border-top: 1px solid var(--line);
  margin-top: 5rem;
}

.project-nav a {
  color: #fff;
  text-decoration: none;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  opacity: 0.65;
  transition: opacity 200ms ease;
}

.project-nav a:hover {
  opacity: 1;
}

/* ---------- Lightbox ---------- */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 50;
  background: rgba(8, 9, 11, 0.96);
  display: none;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity 260ms ease;
}

.lightbox.is-open {
  display: flex;
  opacity: 1;
}

.lightbox img {
  max-width: 92vw;
  max-height: 88vh;
  object-fit: contain;
  display: block;
}

.lightbox__close,
.lightbox__prev,
.lightbox__next {
  position: absolute;
  background: none;
  border: 0;
  color: #fff;
  font-family: inherit;
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
  opacity: 0.55;
  padding: 1rem;
  transition: opacity 200ms ease;
}

.lightbox__close:hover,
.lightbox__prev:hover,
.lightbox__next:hover {
  opacity: 1;
}

.lightbox__close {
  top: 1rem;
  right: 1.4rem;
  font-size: 1.9rem;
}

.lightbox__prev {
  left: 0.6rem;
}

.lightbox__next {
  right: 0.6rem;
}

.lightbox__count {
  position: absolute;
  bottom: 1.3rem;
  left: 50%;
  transform: translateX(-50%);
  font-size: 0.72rem;
  letter-spacing: 0.16em;
  opacity: 0.5;
}

/* ---------- Studio page ---------- */

.team {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.6rem 2rem;
  margin-top: 2.5rem;
  text-align: left;
}

.team li {
  list-style: none;
  border-top: 1px solid var(--line);
  padding-top: 0.9rem;
}

.team strong {
  display: block;
  font-size: 0.95rem;
  font-weight: 400;
  letter-spacing: 0.03em;
}

.team span {
  font-size: 0.78rem;
  opacity: 0.55;
  letter-spacing: 0.06em;
}

@media (max-width: 760px) {
  .team {
    grid-template-columns: 1fr 1fr;
  }
}

.list {
  text-align: left;
  margin-top: 2rem;
}

.list li {
  list-style: none;
  border-top: 1px solid var(--line);
  padding: 1.1rem 0;
  display: grid;
  grid-template-columns: 110px 1fr;
  gap: 1.5rem;
}

.list li:last-child {
  border-bottom: 1px solid var(--line);
}

.list__year {
  font-size: 0.8rem;
  letter-spacing: 0.12em;
  opacity: 0.5;
}

.list__what {
  font-size: 0.95rem;
  line-height: 1.6;
  opacity: 0.85;
}

.list__what em {
  font-style: normal;
  opacity: 0.55;
  display: block;
  font-size: 0.85rem;
  margin-top: 0.2rem;
}

@media (max-width: 620px) {
  .list li {
    grid-template-columns: 1fr;
    gap: 0.3rem;
  }
}

.stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 2rem;
  margin-top: 3rem;
}

/* The four figures cross the row to reach their places - the odd ones come
   in from the right, the even ones from the left, so they pass straight
   through each other on the way. Deliberately not a count-up; every studio
   site does that one. The row clips the flight, so nothing widens the page,
   and the easing keeps speed through the middle where the crossing happens
   instead of dumping all the travel into the first instant.
   Delays are set per position rather than through --d, so the four keep
   their own rhythm whatever else comes into view alongside them. */
.stats {
  --fly: 70vw;
  overflow: hidden;
  padding: 0.4rem 0;
}

.stats .stat.reveal:nth-child(odd) {
  transform: translateX(var(--fly));
}

.stats .stat.reveal:nth-child(even) {
  transform: translateX(calc(var(--fly) * -1));
}

.stats .stat.reveal.is-in {
  opacity: 1;
  transform: none;
  transition:
    opacity 300ms ease,
    transform 1000ms cubic-bezier(0.5, 0, 0.15, 1);
}

.stats .stat.reveal:nth-child(1).is-in { transition-delay: 0ms; }
.stats .stat.reveal:nth-child(2).is-in { transition-delay: 60ms; }
.stats .stat.reveal:nth-child(3).is-in { transition-delay: 120ms; }
.stats .stat.reveal:nth-child(4).is-in { transition-delay: 180ms; }

@media (prefers-reduced-motion: reduce) {
  .stats .stat.reveal {
    transform: none;
    opacity: 1;
  }
}

.stat__num {
  display: block;
  font-family: "Italiana", serif;
  font-size: clamp(2.2rem, 5vw, 3.4rem);
  line-height: 1;
}

.stat__label {
  display: block;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  opacity: 0.55;
  margin-top: 0.6rem;
}

@media (max-width: 760px) {
  .stats {
    grid-template-columns: 1fr 1fr;
    gap: 2rem 1rem;
  }
}

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

/* The columns are sized to their own content and the group is centred, so
   the three sit together in the middle of the page. Thirds of a wide grid
   would leave each one hugging the left of its share, with the right of the
   page empty. */
.contact-grid {
  display: grid;
  grid-template-columns: repeat(3, auto);
  justify-content: center;
  gap: clamp(2rem, 7vw, 6.5rem);
  margin-top: 3rem;
  text-align: center;
}

/* The contact block sweeps in line by line: the outer columns come in from
   beyond the edges of the page, the middle one up from below, and inside a
   column each line follows the one above it.
   The columns themselves never move - they are what the scroll watcher
   measures, and a column that sits offset from its real position reports a
   false one, which on a page this short can mean it never comes in at all.
   The section clips the approach, and it runs the full width of the window,
   so the lines really do travel in from the edges of the screen. */
.block--sweep {
  overflow: hidden;
}

.contact-grid {
  --fly: 60vw;
  --drop: 40vh;
}

.contact-grid .reveal {
  opacity: 1;
  transform: none;
}

.c-row {
  display: block;
  opacity: 0;
}

.contact-grid > .reveal:nth-child(1) .c-row {
  transform: translateX(calc(var(--fly) * -1));
}

.contact-grid > .reveal:nth-child(2) .c-row {
  transform: translateY(var(--drop));
}

.contact-grid > .reveal:nth-child(3) .c-row {
  transform: translateX(var(--fly));
}

.contact-grid > .reveal.is-in .c-row {
  opacity: 1;
  transform: none;
  transition:
    opacity 620ms ease,
    transform 900ms cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: calc(var(--r, 0) * 90ms);
}

/* the two outsides start together, the middle a beat later */
.contact-grid > .reveal:nth-child(3).is-in .c-row {
  transition-delay: calc(var(--r, 0) * 90ms + 60ms);
}

.contact-grid > .reveal:nth-child(2).is-in .c-row {
  transition-delay: calc(var(--r, 0) * 90ms + 160ms);
}

@media (prefers-reduced-motion: reduce) {
  .c-row,
  .contact-grid > .reveal.is-in .c-row {
    transform: none;
    opacity: 1;
  }
}

.contact-grid a {
  color: #fff;
  text-decoration: none;
  border-bottom: 1px solid rgba(255, 255, 255, 0.28);
  padding-bottom: 1px;
  transition: border-color 200ms ease;
}

.contact-grid a:hover {
  border-color: #fff;
}

.contact-grid p {
  font-size: 0.98rem;
  line-height: 1.8;
  opacity: 0.85;
}

@media (max-width: 760px) {
  .contact-grid {
    grid-template-columns: auto;
    justify-content: center;
    gap: 2.5rem;
  }
}

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

.footer {
  position: relative;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1rem;
  padding: 1.4rem clamp(1.5rem, 5vw, 2rem);
  font-size: 0.78rem;
  /* the text is dimmed, never the element: opacity here would take the
     background with it and the fixed hero would show straight through */
  color: rgba(255, 255, 255, 0.72);
  background: var(--ink);
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}

.footer__credit {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  color: inherit;
  text-decoration: none;
  transition: color 200ms ease;
}

.footer__credit:hover {
  color: #fff;
}

.footer__credit img {
  height: 18px;
  width: auto;
  display: block;
}
