/* ==========================================================================
   PRODUCTION - design system
   Type: Syne (from the FROOHM template). Palette: monochrome, no accent hue.
   Nav grid + roll-up invert hover from FROOHM, roll-up text swap from MOSHUN.
   ========================================================================== */

:root {
  --bg:            #070707;
  --bg-raise:      #101010;
  --fg:            #ffffff;
  --muted:         rgba(255, 255, 255, .58);
  --faint:         rgba(255, 255, 255, .34);
  --line:          #262626;
  --line-soft:     rgba(255, 255, 255, .09);

  --nav-h:         47px;
  --hero-pad:      32px;
  --hero-shell:    1800px;
  --shell:         1440px;
  --gutter:        clamp(1rem, 4vw, 4rem);
  --section:       clamp(5rem, 11vw, 10.5rem);

  --radius:        4px;
  --radius-lg:     6px;

  --ease:          cubic-bezier(.16, 1, .3, 1);
  /* The default --ease is expo-out: it spends 90% of its travel in the first
     quarter of the duration. Perfect for a hover settling, useless for a
     reveal - a wipe on that curve is over before the eye registers it started.
     This curve moves at an even, readable pace instead. */
  --ease-reveal:   cubic-bezier(.62, .05, .21, 1);
}

/* ---------- reset ---------- */

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

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: "Syne", "Syne Fallback", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: 1rem;
  font-weight: 500;
  line-height: 1.55;
  overflow-x: hidden;
  font-variant-numeric: lining-nums proportional-nums;
  -webkit-font-smoothing: antialiased;
}

body.is-locked { overflow: hidden; }

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

a {
  color: inherit;
  text-decoration: none;
}

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
}

input {
  font: inherit;
  color: inherit;
}

h1, h2, h3, h4, p, figure, ul { margin: 0; }
ul { padding: 0; list-style: none; }

:focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: 3px;
}

::selection {
  background: var(--fg);
  color: var(--bg);
}

/* ---------- typography scale ---------- */

.u-display {
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -.04em;
  line-height: .84;
}

.u-title {
  font-size: clamp(1.75rem, 4.4vw, 3.5rem);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -.035em;
  line-height: .95;
}

.u-lead {
  font-size: clamp(1.0625rem, 1.9vw, 1.75rem);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: -.005em;
  line-height: 1.24;
}

.u-label {
  font-size: .6875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--faint);
}

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

.u-body {
  font-size: 1rem;
  font-weight: 500;
  color: var(--muted);
  line-height: 1.7;
  max-width: 62ch;
}

/* ---------- layout ---------- */

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

/* full-bleed shell - shares the hero's edges so the footer wordmark and the
   hero wordmark start on exactly the same line */
.wide {
  width: 100%;
  max-width: var(--hero-shell);
  margin-inline: auto;
  padding-inline: var(--hero-pad);
}

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

.page { min-height: 100dvh; }

/* ---------- grain ---------- */

.grain {
  position: fixed;
  inset: 0;
  z-index: 90;
  pointer-events: none;
  opacity: .05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ==========================================================================
   NAV - full-width cell grid, 1px rules, roll-up invert hover
   ========================================================================== */

.nav {
  position: fixed;
  inset: 0 0 auto;
  z-index: 70;
  border-bottom: 1px solid var(--line);
}

/* The 1px gap over a --line background is what draws every hairline in the
   nav, in the dropdown and between the two of them - no borders anywhere. */
.nav__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1px;
  background: var(--line);
}

@media (min-width: 860px) {
  .nav__grid { grid-template-columns: repeat(4, 1fr); }
}

/* nav cells */

.nav__cell {
  position: relative;
  display: block;
  overflow: hidden;
  isolation: isolate;
}

/* Height is set, not derived: a face sized by its own line box lands on a
   fractional height (12px * 1.15 = 13.8), which puts every row boundary on a
   different subpixel phase and makes the 1px grid gaps paint at different
   thicknesses on a 2x screen. An integer row keeps every hairline identical. */
.nav__face {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 44px;
  padding: 0 24px;
  background: var(--bg);
  color: var(--muted);
  font-size: .75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .16em;
  line-height: 1.15;
  text-align: center;
  /* The row height is a fixed 44px (see the note above), so a label that wraps
     is not just ugly, it is clipped by .nav__cell's overflow. "LANKA <<ART
     NUDE>>" is the longest label here and clears the narrowest desktop cell by
     only a few px, so pin it to one line rather than trusting the measurement
     to survive a fallback font or a different platform's metrics. */
  white-space: nowrap;
}

/* Between 860 and 1024 the four cells are at their narrowest while still being
   a four-across row, and that is where the long label runs out of room. Buy it
   back from the gutters and the tracking rather than shrinking the type. */
@media (min-width: 860px) and (max-width: 1023px) {
  .nav__face {
    padding: 0 12px;
    letter-spacing: .1em;
  }
}

/* Below 860 the burger panel splits into links + the star still, so a link
   only gets half the window - 152px on a 320px phone. Same fix, same reason. */
@media (max-width: 859px) {
  .nav__face {
    padding: 0 12px;
    letter-spacing: .1em;
  }
}

/* Half of a small phone is the tightest the long label ever gets: at 360px it
   cleared its cell by 2px and at 320px it overran by 18. Step the type down
   here rather than let it clip. Applied to every cell, not just the long one -
   the four links stack in one column on a phone, so mixed tracking between
   them would be obvious. */
@media (max-width: 420px) {
  .nav__face {
    padding: 0 8px;
    font-size: .6875rem;
    letter-spacing: .07em;
  }
}

.nav__cell[aria-current="page"] .nav__face { color: var(--fg); }

.nav__face--hover {
  position: absolute;
  inset: 0;
  background: var(--fg);
  color: var(--bg);
  transform: translateY(100%);
  transition: transform .55s var(--ease);
}

.nav__cell:hover .nav__face--hover,
.nav__cell:focus-visible .nav__face--hover { transform: translateY(0); }

/* mobile burger cell */

.burger {
  display: grid;
  place-items: center;
  background: var(--bg);
}

@media (min-width: 860px) {
  .burger { display: none; }
}

.burger__box {
  position: relative;
  width: 18px;
  height: 9px;
}

.burger__bar {
  position: absolute;
  left: 0;
  width: 100%;
  height: 1px;
  background: var(--fg);
  transition: transform .5s var(--ease);
}

.burger__bar:first-child { top: 0; }
.burger__bar:last-child  { bottom: 0; }

.burger[aria-expanded="true"] .burger__bar:first-child {
  transform: translateY(4px) rotate(45deg);
}

.burger[aria-expanded="true"] .burger__bar:last-child {
  transform: translateY(-4px) rotate(-45deg);
}

/* ---------- dropdown menu (FROOHM nav pattern) ----------
   Desktop: the panel is transparent scaffolding - its three cells simply carry
   on the nav's four-column rhythm. Mobile: it drops out of the row, becomes a
   two-column sheet under the nav (links stacked left, still on the right) and
   the page behind it is scrimmed. */

.nav__panel {
  display: grid;
  background: var(--line);
}

.nav__items {
  display: grid;
  gap: 1px;
  background: var(--line);
}

.nav__media { display: none; }

@media (min-width: 860px) {
  .nav__panel { grid-column: 2 / -1; }
  .nav__items { grid-template-columns: repeat(3, 1fr); }
}

@media (max-width: 859px) {
  .nav__panel {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    grid-template-columns: 1fr 1fr;
    gap: 1px;
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
    visibility: hidden;
    pointer-events: none;
    transform: translateY(-8px);
    opacity: 0;
    /* visibility flips instantly on open, and only after the fade on close */
    transition: opacity .38s var(--ease), transform .38s var(--ease), visibility 0s linear .38s;
  }

  .nav__panel.is-open {
    visibility: visible;
    pointer-events: auto;
    transform: none;
    opacity: 1;
    transition: opacity .38s var(--ease), transform .38s var(--ease), visibility 0s;
  }

  .nav__items { grid-template-columns: 1fr; }

  /* the still holds the right column for the full height of the four links */
  .nav__media {
    position: relative;
    display: block;
    overflow: hidden;
    background: var(--bg);
  }

  /* absolute so the mark never dictates the sheet's height - the four links
     do, exactly as on the reference. `contain` rather than `cover`: the film is
     square and the cell is landscape, so covering would cut the star's top and
     bottom points off.

     The cell is painted pure black on purpose. The film's field is true black,
     and `lighten` normally resolves that against whatever sits behind - but the
     panel animates its own opacity and transform on open, and for those 380ms
     it is a stacking context that isolates the blend. Matching the cell to the
     film means the mark reads correctly whether the blend resolves or not. */
  .nav__media { background: #000; }

  .nav__media-still {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: contain;
    mix-blend-mode: lighten;
  }

}

/* dims the page under an open menu, and closes it on tap */
.nav__scrim {
  display: none;
  position: absolute;
  inset: 0;
  z-index: -1;
  height: 100dvh;
  background: rgba(7, 7, 7, .5);
  opacity: 0;
  transition: opacity .4s var(--ease);
}

@media (max-width: 859px) {
  .nav__scrim.is-open {
    display: block;
    opacity: 1;
  }
}

/* ---------- roll-up text swap (MOSHUN pattern) ---------- */

.roll {
  position: relative;
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
}

.roll__face {
  display: block;
  transition: transform .5s var(--ease);
}

.roll__face--hover {
  position: absolute;
  inset: 0;
  transform: translateY(105%);
}

.roll:hover .roll__face,
a:hover .roll .roll__face,
.services__item:hover .roll .roll__face { transform: translateY(-105%); }

.roll:hover .roll__face--hover,
a:hover .roll .roll__face--hover,
.services__item:hover .roll .roll__face--hover { transform: translateY(0); }

/* ---------- buttons ---------- */

.btn {
  display: inline-flex;
  align-items: center;
  gap: .75rem;
  min-height: 46px;
  padding: .7rem 1.4rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: .6875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .16em;
  transition: background .35s var(--ease), color .35s var(--ease),
              border-color .35s var(--ease), transform .2s var(--ease);
}

.btn:hover {
  background: var(--fg);
  border-color: var(--fg);
  color: var(--bg);
}

.btn:active { transform: scale(.98); }

.btn--solid {
  background: var(--fg);
  border-color: var(--fg);
  color: var(--bg);
}

.btn--solid:hover {
  background: transparent;
  border-color: var(--fg);
  color: var(--fg);
}

.btn__dot {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .12);
  transition: transform .35s var(--ease), background .35s var(--ease);
}

.btn:hover .btn__dot {
  background: rgba(0, 0, 0, .12);
  transform: translate(2px, -2px);
}

.btn--solid .btn__dot { background: rgba(0, 0, 0, .12); }
.btn--solid:hover .btn__dot { background: rgba(255, 255, 255, .12); }

/* Inverted: the closing section lays type over a high-key photograph, where
   both the outline and the white fill would disappear. */
.btn--invert {
  background: var(--bg);
  border-color: var(--bg);
  color: var(--fg);
}

.btn--invert:hover {
  background: transparent;
  border-color: var(--bg);
  color: var(--bg);
}

.btn--invert .btn__dot { background: rgba(255, 255, 255, .16); }
.btn--invert:hover .btn__dot { background: rgba(0, 0, 0, .12); }

/* ==========================================================================
   BLOCK 1 - hero
   Rebuilt to the FROOHM hero spec: 100vh section, wordmark filling the
   container, a 5-up row of 16:9 reels centred in the remaining space,
   four value blocks on the baseline. "SHERSTIUK" is 6.26em wide in
   Syne 700 at -0.04em, so the type size is derived from the container.
   The divisor keeps the same ~0.7% slack the PRODUCTION wordmark had, so
   the type fills the shell without ever touching the padding. Recalibrate
   it against a real measurement if the wordmark string ever changes.
   ========================================================================== */

.hero {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;
  padding-top: calc(var(--nav-h) + 17px);
  padding-bottom: 28px;
}

.hero__shell {
  display: flex;
  flex: 1;
  flex-direction: column;
  width: 100%;
  max-width: var(--hero-shell);
  margin-inline: auto;
  padding-inline: var(--hero-pad);
}

.hero__text {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 32px;
  margin-top: clamp(1.5rem, 4.5vw, 4.5rem);
  text-align: center;
}

.hero__wordmark {
  font-size: calc((min(100vw, var(--hero-shell)) - 2 * var(--hero-pad)) / 6.3);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: -.04em;
  line-height: .8;
  color: var(--fg);
}

.hero__sub {
  max-width: 400px;
  font-size: .75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .01em;
  line-height: 1.36;
  color: var(--muted);
}

.hero__works {
  display: flex;
  flex: 1;
  flex-direction: column;
  justify-content: center;
  padding-bottom: 24px;
}

.hero__grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(2rem, 6.5vw, 120px);
}

.tile {
  position: relative;
  display: block;
  overflow: hidden;
  aspect-ratio: 16 / 9;
  border: 1px solid rgba(255, 255, 255, .15);
  border-radius: var(--radius-lg);
  opacity: 1;
  transition: opacity .35s var(--ease);
}

.tile:hover,
.tile:focus-visible { opacity: .5; }

/* a scrim so the category label holds contrast over a light frame as well
   as a dark one - the studio shots run white edge to edge */
.tile::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  z-index: 1;
  height: 48%;
  background: linear-gradient(to top, rgba(0, 0, 0, .7), rgba(0, 0, 0, 0));
  pointer-events: none;
}

.tile__tag {
  position: absolute;
  z-index: 2;
  left: 10px;
  bottom: 8px;
  font-size: .6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  line-height: 1;
  color: var(--fg);
}

.tile video,
.tile img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 120px;
  margin: 0;
}

.stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-align: center;
}

.stat__label {
  font-size: .75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .01em;
  color: var(--muted);
}

.stat__value {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .4rem;
  margin: 0;
  font-size: 2.5rem;
  font-weight: 600;
  letter-spacing: 0;
  line-height: 1.08;
  color: var(--fg);
}

/* Two of the four figures need a qualifier - "0" and "500+" mean nothing on
   their own. It sets below the number rather than beside it so the four
   numerals still land on one line across the row. */
.stat__note {
  max-width: 27ch;
  font-size: .625rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .05em;
  line-height: 1.4;
  color: var(--faint);
  text-wrap: balance;
}

@media (max-width: 991px) {
  .hero { min-height: 980px; }
  .hero__text { gap: 20px; }
  .hero__grid { grid-template-columns: repeat(2, 1fr); gap: 64px; }
  .tile__tag { left: 14px; bottom: 12px; font-size: .8125rem; }
  .stats { gap: 64px; }
}

@media (max-width: 767px) {
  :root { --hero-pad: 24px; }

  .hero { min-height: auto; padding-bottom: 24px; }
  .hero__works { padding-top: 88px; padding-bottom: 104px; }
  .hero__grid { gap: 40px; }
  .stats { grid-template-columns: repeat(2, 1fr); gap: 40px; }
  .stat__value { font-size: 1.75rem; }
}

/* ==========================================================================
   BLOCK 2 - selected work
   MOSHUN spec: full-bleed marquee at 2.25em of a .925926vw wrapper, then a
   12-column grid with zero gutters where every card spans 6 columns and is
   square. Meta sits absolutely at 1rem/1rem, no scrim - as in the template.
   ========================================================================== */

.marquee {
  display: flex;
  overflow: hidden;
  user-select: none;
}

.marquee__track {
  display: flex;
  gap: 2vw;
  width: max-content;
  animation: marquee 42s linear infinite;
}

.marquee__item {
  flex: 0 auto;
  white-space: nowrap;
  font-size: 2.0833vw;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -.025em;
  line-height: 1.2;
}

@keyframes marquee {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}

.work__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0;
  margin-top: 1.25rem;
}

.work__card {
  position: relative;
  display: block;
  overflow: hidden;
  aspect-ratio: 1;
}

.work__media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1.2s var(--ease);
}

.work__card:hover .work__media { transform: scale(1.04); }

/* The films run from near-black to near-white, so the meta needs its own
   ground. Same move the hero tiles make, inverted to the top edge. */
.work__card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  z-index: 1;
  height: 38%;
  background: linear-gradient(to bottom, rgba(0, 0, 0, .68), rgba(0, 0, 0, 0));
  pointer-events: none;
}

.work__meta {
  position: absolute;
  top: 1rem;
  left: 1rem;
  z-index: 2;
  display: flex;
  flex-direction: column;
  font-size: 1rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0;
  line-height: 1.2;
  color: var(--fg);
}

@media (min-width: 1920px) {
  .work__meta { font-size: .925926vw; }
}

@media (max-width: 991px) {
  .marquee__item { font-size: 1.5rem; }

  .work__grid {
    grid-template-columns: minmax(0, 1fr);
    margin-top: 1rem;
  }

  .work__meta {
    top: .75rem;
    left: .75rem;
    font-size: .875rem;
  }
}

@media (max-width: 767px) {
  .work__card { aspect-ratio: 3 / 2; }

  /* The square films are cropped hard at 3:2, and centred they land their
     subject right behind the meta block in the top-left corner. Pulling the
     crop window up drops the frame's content lower in the card, so the
     figure clears the four lines of type. */
  .work__media { object-position: 50% 15%; }
}

@media (max-width: 479px) {
  .marquee__item { font-size: 1.125rem; }
}

/* ==========================================================================
   BLOCK 3 - services
   ========================================================================== */

.services__grid {
  display: grid;
  justify-items: center;
  gap: clamp(3rem, 8vw, 6rem);
}

/* The figure, not the plate, carries the scroll timeline the curtain runs on.
   `overflow: hidden` makes the plate a scroll container in its own right, and
   a `view()` timeline declared on anything inside it would measure that box
   instead of the page - the element fills it exactly and never moves, so the
   progress pins at a constant 50% and the curtain freezes half open. Naming
   the timeline out here, one level above the clip, measures the plate against
   the window as intended. Descendants pick it up by name without a
   timeline-scope, since the definer is their ancestor. */
.services__figure {
  position: relative;
  display: grid;
  place-items: center;
  margin: 0;
  view-timeline-name: --services-plate;
}

/* The plate is deliberately narrower than the wordmark laid over it: SERVICES
   sets 8.12em wide in Syne 800 at -0.05em, so at the stamp's 4.25rem ceiling it
   runs about 550px and has to break past both edges of the still, as on the
   "головна сторінка3" board. One width across every breakpoint, so the plate
   does not jump size where the layout rules change over at 900px. */
.services__figure { width: min(62%, 440px); }

/* Square, to match the work cards above - the section reads as one more frame
   in that grid rather than a new shape. The file is cut square to the same
   ratio, so `cover` has nothing left to crop and the framing is fixed here
   rather than left to the box. */
.services__plate {
  position: relative;
  display: block;
  overflow: hidden;
  width: 100%;
  aspect-ratio: 1;
}

.services__image {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* eased back to rest as the curtain parts, so the still settles rather than
     simply appearing */
  transform: scale(1.16);
  transition: transform 1.5s var(--ease-reveal);
}

/* --------------------------------------------------------------------------
   The curtain.

   Two panels in the page colour sit closed over the plate and part sideways
   as the frame arrives, with a hairline down the join so the split reads as
   an edge rather than as a fade.

   Each panel is half the plate plus a sliver, so the two overlap at the centre
   and no seam of the still shows through before the move starts.
   -------------------------------------------------------------------------- */

.services__curtain {
  position: absolute;
  top: 0;
  bottom: 0;
  z-index: 2;
  width: 50.5%;
  background: var(--bg);
  pointer-events: none;
  transition: transform 1.35s var(--ease-reveal);
}

.services__curtain--a {
  left: 0;
  border-right: 1px solid rgba(255, 255, 255, .16);
}

.services__curtain--b {
  right: 0;
  border-left: 1px solid rgba(255, 255, 255, .16);
}

.services__figure.reveal {
  opacity: 1;
  transform: none;
}

.services__stamp {
  position: absolute;
  z-index: 3;
  font-size: clamp(2.1rem, 6.6vw, 4.25rem);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -.05em;
  line-height: 1;
  mix-blend-mode: difference;
  pointer-events: none;
  opacity: 0;
  transform: translateY(20px);
  transition: opacity .9s var(--ease-reveal) .95s,
              transform 1.1s var(--ease-reveal) .95s;
}

/* Timer-driven fallback, used where the browser has no scroll-driven
   animations: the panels part on a transition once the frame is properly in
   view, and the wordmark follows them. */
.services__figure.reveal.is-in .services__image { transform: scale(1); }
.services__figure.reveal.is-in .services__curtain--a { transform: translateX(-101%); }
.services__figure.reveal.is-in .services__curtain--b { transform: translateX(101%); }

.services__figure.reveal.is-in .services__stamp {
  opacity: 1;
  transform: none;
}

/* Scroll-driven where the browser has it: the panels are tied to the plate's
   own passage through the viewport, so the curtain opens at the speed the
   visitor scrolls rather than on a fixed clock. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .services__curtain,
    .services__image,
    .services__stamp { transition: none; }

    .services__curtain--a {
      animation: curtain-left linear both;
      animation-timeline: --services-plate;
      animation-range: cover 26% cover 62%;
    }

    .services__curtain--b {
      animation: curtain-right linear both;
      animation-timeline: --services-plate;
      animation-range: cover 26% cover 62%;
    }

    .services__image {
      animation: curtain-settle linear both;
      animation-timeline: --services-plate;
      animation-range: cover 26% cover 74%;
    }

    .services__stamp {
      animation: services-word linear both;
      animation-timeline: --services-plate;
      animation-range: cover 58% cover 76%;
    }
  }
}

@keyframes curtain-left {
  from { transform: translateX(0); }
  to   { transform: translateX(-101%); }
}

@keyframes curtain-right {
  from { transform: translateX(0); }
  to   { transform: translateX(101%); }
}

@keyframes curtain-settle {
  from { transform: scale(1.16); }
  to   { transform: scale(1); }
}

@keyframes services-word {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .services__image { transform: none; animation: none; }
  .services__curtain { display: none; }
  .services__stamp { opacity: 1; transform: none; animation: none; }
}

/* Mobile / tablet, per the "головна сторінка3" board: a centred plate with the
   wordmark bleeding past both of its edges. Without the flanking paragraphs the
   plate can take more of the column than the board's 37%. */
@media (max-width: 899px) {
  .services__grid { gap: clamp(2.25rem, 12vw, 5.5rem); }

  /* Sized off the content column, not the plate, so the wordmark always breaks
     past both edges of the still by roughly the same amount as on the board. */
  .services__stamp {
    font-size: calc((100vw - 2 * var(--gutter)) * .092);
    letter-spacing: -.045em;
    white-space: nowrap;
  }
}

.services__list {
  display: grid;
  margin-top: clamp(3rem, 7vw, 5.5rem);
  border-top: 1px solid var(--line);
}

.services__item {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1.5rem;
  padding-block: clamp(1rem, 2.2vw, 1.6rem);
  border-bottom: 1px solid var(--line-soft);
  transition: padding-inline .45s var(--ease);
}

.services__item:hover { padding-inline: .85rem; }

.services__name {
  font-size: clamp(1.25rem, 3.2vw, 2.25rem);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -.035em;
  line-height: 1;
}

/* Wide enough that the longest of these notes - Video Production, at 84
   characters - sets in two lines rather than three, and balanced so the two
   come out even instead of leaving one word stranded. */
.services__note {
  font-size: .75rem;
  font-weight: 500;
  color: var(--muted);
  text-align: right;
  max-width: 48ch;
  text-wrap: balance;
}

/* Below this the two halves of the row cannot both hold their min-content
   width - "Commercial Photography" alone takes most of the column - and the
   row pushed a horizontal scrollbar onto the page. Stack them instead. */
@media (max-width: 599px) {
  .services__item {
    flex-direction: column;
    align-items: flex-start;
    gap: .45rem;
  }

  .services__note {
    max-width: none;
    text-align: left;
  }
}

/* ==========================================================================
   BLOCK 4 - closing call to action
   The MOSHUN plate: a 3:2 full-bleed image with the copy pinned to its
   top-left corner, the still 120% tall inside an overflow-hidden frame so it
   can drift on scroll. The photograph is high-key, so the type here runs in
   the page colour rather than in white - the only place on the site where it
   does, and only because the picture underneath demands it.
   ========================================================================== */

/* Same reasoning as the Services figure: .cta__media clips, so it is a scroll
   container, and the timeline has to be named out here on the section. */
.cta {
  position: relative;
  width: 100%;
  aspect-ratio: 3 / 2;
  view-timeline-name: --cta-plate;
}

.cta__media {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

.cta__img {
  position: absolute;
  inset: 0 0 auto 0;
  width: 100%;
  height: 120%;
  object-fit: cover;
  transform: translateY(-8.34%);
  will-change: transform;
}

/* scroll-driven parallax where the browser supports it; static drift elsewhere */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .cta__img {
      transform: none;
      animation: cta-drift linear both;
      animation-timeline: --cta-plate;
      animation-range: cover 0% cover 100%;
    }
  }
}

@keyframes cta-drift {
  from { transform: translateY(0); }
  to   { transform: translateY(-16.67%); }
}

/* Clear of the fixed nav: this headline sets three lines deep and the section
   can be scrolled to flush with the top of the window, where the nav bar would
   otherwise cut the first of them in half. */
.cta__content {
  position: absolute;
  inset: 0 0 auto 0;
  z-index: 1;
  padding-top: calc(var(--nav-h) + 1.5rem);
}

/* "Want to see more of my work?" sets 28.56em wide in Syne 800 at -0.04em, and
   its widest sensible first line - "Want to see more of" - is 19.22em. For the
   line breaker to land on two lines rather than three, the column has to hold
   that first line, so the width and the type size are set together: 78% of the
   band against a /25.5 divisor gives just under 20em of measure. Re-derive both
   numbers together if this headline ever changes. */
.cta__inner { max-width: 78%; }

.cta__text {
  margin: 0;
  text-wrap: balance;
  font-size: calc((min(100vw, var(--hero-shell)) - 2 * var(--hero-pad)) / 25.5);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -.04em;
  line-height: .9;
  color: var(--bg);
}

.cta__lead {
  max-width: none;
  margin-top: 1.5rem;
  font-size: clamp(.8125rem, 1.15vw, 1.0625rem);
  font-weight: 600;
  line-height: 1.5;
  color: rgba(7, 7, 7, .72);
  text-wrap: pretty;
}

.cta__btn { margin-top: clamp(1.5rem, 2.6vw, 2.25rem); }

@media (max-width: 991px) {
  .cta { aspect-ratio: 1; }
  .cta__inner { max-width: 75%; }
  /* 75% of the band against /27 keeps the same just-under-20em measure the
     desktop rule has, so the headline still breaks in two here */
  .cta__text { font-size: calc((min(100vw, var(--hero-shell)) - 2 * var(--hero-pad)) / 27); }
  .cta__lead { margin-top: 1.15rem; }
}

/* Phone: the figures rise into the lower half of the plate, so the whole stack
   has to sit inside the wall above them - anything that reaches the drapes is
   dark type on black. Three moves buy the room: the plate is taller, the crop
   is pinned so the wall is a fixed share of it at any scroll position, and the
   headline gives up the shared 19.5em measure for the full gutter width at a
   smaller size, which recovers the lines the narrower column was costing. */
@media (max-width: 767px) {
  /* Taller plate, so the wall at the top of the photograph is deep enough to
     seat the whole stack - headline, lead and button - on light ground rather
     than letting the last of it slide onto the drapes. */
  .cta { aspect-ratio: 4 / 5; }

  /* No overhang and no drift here. The drift is what makes the copy unsafe on a
     phone: it sweeps the drapes up the frame, so how much wall sits under the
     headline depends on where in the scroll the section happens to be. Pinned
     at 100% the wall is a fixed share of the plate at every scroll position,
     which is what lets the block below be measured against it at all. */
  .cta__img {
    height: 100%;
    transform: none;
    animation: none;
  }

  /* Fixed to the plate, not to the viewport: the copy sits 20px off the top
     edge and stays there through the scroll. It gives up the nav clearance the
     desktop padding carries, so scrolled hard against the foot of the page the
     headline can pass under the bar - the read is worth more than that edge. */
  .cta__content { padding-top: 1.25rem; }

  .cta__inner { max-width: 100%; }

  .cta__text {
    font-size: clamp(1.25rem, calc((100vw - 2 * var(--hero-pad)) / 15.5), 1.625rem);
  }

  .cta__lead {
    max-width: 32ch;
    margin-top: .5rem;
    font-size: .75rem;
    line-height: 1.4;
  }

  .cta__btn {
    margin-top: .875rem;
    min-height: 42px;
    padding: .6rem 1.15rem;
  }
}

/* ==========================================================================
   footer
   MOSHUN spec: a 12-column band (tagline / 2 link columns / a right-aligned
   column), 20vh of air, then the wordmark locked to the full container width
   with the star breaking out above its cap-height.
   ========================================================================== */

.footer {
  padding-top: clamp(3rem, 6vw, 5rem);
  padding-bottom: clamp(1.5rem, 3vw, 2rem);
  overflow: hidden;
}

.footer__grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  column-gap: 1vw;
  row-gap: 2.5rem;
  margin-bottom: 20vh;
}

.footer__col {
  display: flex;
  flex-direction: column;
  gap: .1rem;
}

.footer__col--pages   { grid-column: 6 / span 2; }
.footer__col--social  { grid-column: 8 / span 2; }
.footer__col--legal   { grid-column: 10 / span 3; justify-self: end; }

.footer__link {
  display: block;
  font-size: .8125rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .01em;
  line-height: 1.35;
  color: var(--fg);
}

.footer__logo {
  display: flex;
  align-items: flex-end;
  gap: 1.2%;
  width: 100%;
}

.footer__wordmark {
  flex: 0 0 auto;
  font-size: calc((min(100vw, var(--hero-shell)) - 2 * var(--hero-pad)) * .855 / 6.3);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: -.04em;
  line-height: .78;
  color: var(--fg);
}

/* The lift lives on the film, not on this box. A transform here would make
   the wrapper a stacking context and isolate the film's blend mode inside it,
   at which point its black field stops resolving against the page and comes
   back as a visible square. */
.footer__star {
  flex: 1 1 auto;
  min-width: 0;
}

/* The mark is a looping film on a true-black field. `lighten` drops that field
   against the page colour exactly - every pixel darker than #070707 resolves
   to the background - so the star sits on the page with no plate behind it and
   no alpha channel to pay for. */
.footer__star img,
.footer__star-media {
  display: block;
  width: 100%;
  height: auto;
}

.footer__star-media {
  aspect-ratio: 1;
  mix-blend-mode: lighten;
  transform: translateY(-10%);
}

@media (max-width: 991px) {
  .footer__grid { row-gap: 2rem; margin-bottom: 12vh; }
  .footer__col--pages   { grid-column: 1 / span 3; }
  .footer__col--social  { grid-column: 4 / span 3; }
  .footer__col--legal   { grid-column: 7 / span 6; justify-self: start; }
}

@media (max-width: 767px) {
  .footer__grid { margin-bottom: 8vh; }
  .footer__col--pages   { grid-column: 1 / span 6; }
  .footer__col--social  { grid-column: 7 / span 6; }
  .footer__col--legal   { grid-column: 1 / span 12; }
  .footer__logo { gap: 2%; }
}

a.u-label { transition: color .3s var(--ease); }
a.u-label:hover { color: var(--fg); }

/* ==========================================================================
   page heads
   ========================================================================== */

.pagehead {
  padding-top: clamp(6rem, 12vw, 8.5rem);
  padding-bottom: clamp(2rem, 5vw, 3.5rem);
}

/* Sized off the content column rather than the raw viewport, the way the hero
   wordmark is. At 10.5vw the longest of these titles - "Sherstiuk", nine
   characters, which sets 9.34em wide in Syne 800 at -0.05em - ran past the
   shell at every width under the 1440 cap and put a horizontal scrollbar on
   the about page. The 9.6 divisor keeps about 3% slack on that measurement;
   recalibrate it if a longer title ever lands. */
.pagehead__title {
  font-size: min(8rem, (min(100vw, var(--shell)) - 2 * clamp(1rem, 4vw, 4rem)) / 9.6);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -.05em;
  line-height: .84;
}

/* ==========================================================================
   CATALOG
   ========================================================================== */

.filters {
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: .5rem .5rem .5rem 1.1rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: rgba(16, 16, 16, .9);
  backdrop-filter: blur(14px);
}

.filters__search {
  flex: 1;
  min-width: 0;
  height: 40px;
  background: none;
  border: 0;
  font-size: .875rem;
  font-weight: 500;
  letter-spacing: .01em;
}

.filters__search::placeholder { color: var(--faint); }
.filters__search:focus { outline: none; }

.filters__toggle {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  height: 40px;
  padding-inline: 1.05rem;
  border-radius: 999px;
  background: rgba(255, 255, 255, .09);
  font-size: .6875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .16em;
  transition: background .3s var(--ease), color .3s var(--ease);
}

.filters__toggle:hover,
.filters__toggle[aria-expanded="true"] {
  background: var(--fg);
  color: var(--bg);
}

.chips {
  display: none;
  flex-wrap: wrap;
  gap: .5rem;
  margin-top: 1rem;
}

.chips.is-open { display: flex; }

.chip {
  min-height: 38px;
  padding: .5rem 1rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: .6875rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--muted);
  transition: background .3s var(--ease), color .3s var(--ease), border-color .3s var(--ease);
}

.chip:hover { border-color: var(--fg); color: var(--fg); }

.chip.is-active {
  background: var(--fg);
  border-color: var(--fg);
  color: var(--bg);
}

.catalog {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(.75rem, 2vw, 1.5rem);
  margin-top: clamp(1.5rem, 4vw, 2.5rem);
}

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

.card {
  position: relative;
  display: block;
  overflow: hidden;
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: var(--radius-lg);
  background: var(--bg-raise);
  text-align: left;
}

.card[hidden] { display: none; }

.card__media {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1s var(--ease), filter .5s var(--ease);
}

.card:hover .card__media {
  transform: scale(1.06);
  filter: brightness(.62);
}

.card__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(7, 7, 7, .84), rgba(7, 7, 7, 0) 58%);
}

.card__meta {
  display: block;
  position: absolute;
  inset: auto 0 0 0;
  padding: clamp(.9rem, 2vw, 1.5rem);
}

.card__title {
  display: block;
  font-size: clamp(.9375rem, 2vw, 1.5rem);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -.03em;
  line-height: 1.04;
}

.card__sub {
  display: block;
  margin-top: .4rem;
  font-size: .625rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .16em;
  color: var(--faint);
}

.card__badge {
  position: absolute;
  top: clamp(.75rem, 1.6vw, 1.1rem);
  right: clamp(.75rem, 1.6vw, 1.1rem);
  padding: .3rem .6rem;
  border-radius: 999px;
  background: rgba(7, 7, 7, .62);
  backdrop-filter: blur(8px);
  font-size: .625rem;
  font-weight: 600;
  letter-spacing: .12em;
}

.catalog__empty {
  display: none;
  padding-block: clamp(4rem, 12vw, 8rem);
  text-align: center;
}

.catalog__empty.is-visible { display: block; }

.catalog__empty h2 {
  font-size: clamp(1.5rem, 4vw, 2.5rem);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -.035em;
}

.catalog__empty p {
  margin-top: .75rem;
  color: var(--muted);
}

/* ==========================================================================
   POPUP - project preview carousel
   One project centred with a sliver of the neighbours showing either side,
   a pager underneath. Each card carries its own still slider.
   ========================================================================== */

.modal {
  position: fixed;
  inset: 0;
  z-index: 80;
  display: grid;
  place-items: center;
  padding: clamp(.75rem, 3vw, 2.5rem);
  opacity: 0;
  visibility: hidden;
  transition: opacity .4s var(--ease), visibility .4s var(--ease);
}

.modal.is-open {
  opacity: 1;
  visibility: visible;
}

.modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(7, 7, 7, .84);
  backdrop-filter: blur(10px);
}

.modal__stage {
  --card-w: min(80%, 1060px);

  position: relative;
  display: grid;
  gap: clamp(.9rem, 2vw, 1.4rem);
  width: min(100%, 1400px);
}

.modal__viewport {
  overflow: hidden;
  transform: translateY(22px) scale(.985);
  transition: transform .5s var(--ease);
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 7%, #000 93%, transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 7%, #000 93%, transparent 100%);
}

.modal.is-open .modal__viewport { transform: none; }

.modal__track {
  display: flex;
  align-items: stretch;
  gap: clamp(.75rem, 1.6vw, 1.5rem);
  width: 100%;
  transition: transform .6s var(--ease);
}

.modal__track.is-dragging { transition: none; }

/* ---------- one project card ---------- */

.modal__card {
  flex: 0 0 var(--card-w);
  display: grid;
  max-height: calc(100dvh - clamp(8rem, 18vw, 12rem));
  overflow-y: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--bg-raise);
  opacity: .5;
  transform: scale(.945);
  transition:
    opacity .55s var(--ease),
    transform .55s var(--ease),
    grid-template-columns .55s var(--ease);
  cursor: pointer;
}

.modal__card:not(.is-active):hover { opacity: .72; }

.modal__card.is-active {
  opacity: 1;
  transform: none;
  cursor: default;
}

/* neighbours are a target for the click that brings them into the middle,
   nothing inside them is interactive */
.modal__card:not(.is-active) > * { pointer-events: none; }

/* the copy panel collapses on a neighbour, so what peeks in from either
   side is photography rather than a sentence cut in half */
.modal__card:not(.is-active) .modal__body {
  opacity: 0;
  padding-inline: 0;
  overflow: hidden;
}

@media (min-width: 900px) {
  .modal__card { grid-template-columns: 1fr 0fr; }

  .modal__card.is-active { grid-template-columns: 1.25fr 1fr; }

  /* the still holds the left column, so long copy scrolls in its own panel */
  .modal__card.is-active .modal__body {
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

/* Stacked layout: a flex column, so the still keeps its 4:3 and the copy
   keeps its natural height. The card then genuinely overflows and scrolls,
   instead of the grid squeezing the rows and clipping the copy. */
@media (max-width: 899px) {
  .modal__card {
    display: flex;
    flex-direction: column;
  }

  .modal__slider,
  .modal__body { flex: none; }
}

.modal__close {
  position: absolute;
  top: .75rem;
  right: calc((100% - var(--card-w)) / 2 + .75rem);
  z-index: 3;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 999px;
  background: rgba(7, 7, 7, .62);
  backdrop-filter: blur(8px);
  transition: background .3s var(--ease), color .3s var(--ease);
}

.modal__close:hover {
  background: var(--fg);
  color: var(--bg);
}

/* Sound switch - sits beside the close button on the same plate, because the
   two are the only chrome that belongs to the popup rather than to a card.
   One glyph carries both states: the arcs show when sound is on, the cross
   when it is off, so the button never changes size as it toggles. */
.modal__sound {
  position: absolute;
  top: .75rem;
  right: calc((100% - var(--card-w)) / 2 + 4.25rem);
  z-index: 3;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 999px;
  background: rgba(7, 7, 7, .62);
  backdrop-filter: blur(8px);
  transition: background .3s var(--ease), color .3s var(--ease);
}

.modal__sound:hover,
.modal__sound:focus-visible {
  background: var(--fg);
  color: var(--bg);
}

.modal__sound .modal__sound-off { display: none; }
.modal__sound.is-off .modal__sound-on { display: none; }
.modal__sound.is-off .modal__sound-off { display: inline; }

/* ---------- stills inside a card ---------- */

.modal__slider {
  position: relative;
  overflow: hidden;
  aspect-ratio: 4 / 3;
  background: #000;
}

@media (min-width: 900px) {
  .modal__slider { aspect-ratio: auto; }
}

.modal__slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity .55s var(--ease);
}

.modal__slide.is-active { opacity: 1; }

.modal__slide img,
.modal__slide video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* still arrows keep out of the way until the pointer is on the image, so
   only the project pager is competing for attention at rest */
.modal__arrow {
  position: absolute;
  top: 50%;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  border-radius: 999px;
  background: rgba(7, 7, 7, .58);
  backdrop-filter: blur(8px);
  opacity: 0;
  transform: translateY(-50%);
  transition: background .3s var(--ease), color .3s var(--ease), opacity .3s var(--ease);
}

.modal__slider:hover .modal__arrow,
.modal__slider:focus-within .modal__arrow { opacity: 1; }

@media (hover: none) {
  .modal__arrow { opacity: 1; }
}

.modal__arrow:hover { background: var(--fg); color: var(--bg); }
.modal__arrow--prev { left: .75rem; }
.modal__arrow--next { right: .75rem; }

.modal__dots {
  position: absolute;
  inset: auto 0 1rem 0;
  z-index: 2;
  display: flex;
  justify-content: center;
  gap: .5rem;
}

.modal__dot {
  width: 9px;
  height: 9px;
  padding: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, .32);
  transition: background .3s var(--ease), transform .3s var(--ease);
}

.modal__dot.is-active {
  background: var(--fg);
  transform: scale(1.3);
}

/* ---------- card copy ---------- */

.modal__body {
  display: grid;
  /* the single column is capped at the track, not at the widest word: without
     minmax(0,...) one long title sizes the column to its own min-content and
     pushes the summary out past the card's right edge */
  grid-template-columns: minmax(0, 1fr);
  align-content: start;
  gap: 1.25rem;
  min-width: 0;
  overflow-wrap: break-word;
  padding: clamp(1.5rem, 3.4vw, 2.75rem);
  transition: opacity .4s var(--ease), padding .55s var(--ease);
}

.modal__title {
  font-size: clamp(1.5rem, 3.2vw, 2.75rem);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -.04em;
  line-height: .96;
}

/* Keeps the first line clear of the close button in the card's corner. A
   floated spacer rather than padding-right: padding reserves the gap on every
   line, which pushed a long second word (CATALOG) into a mid-word break. A
   float only displaces the lines it actually sits beside, so line one clears
   the button and the rest of the title gets the full column. */
@media (min-width: 900px) {
  .modal__title::before {
    content: "";
    float: right;
    width: 3rem;
    /* shorter than one line box (line-height is .96) so the spacer displaces
       the first line only and never bleeds into the second */
    height: .5em;
  }
}

.modal__specs {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1.1rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--line);
}

.modal__spec dt {
  font-size: .625rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .18em;
  color: var(--faint);
}

.modal__spec dd {
  margin: .35rem 0 0;
  font-size: .875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
}

.modal__cta { margin: .5rem 0 0; }

/* ---------- project pager ---------- */

.modal__pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: clamp(.7rem, 2vw, 1.25rem);
}

.modal__nav {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: rgba(16, 16, 16, .72);
  backdrop-filter: blur(8px);
  color: var(--fg);
  transition: background .3s var(--ease), color .3s var(--ease), transform .3s var(--ease);
}

.modal__nav:hover {
  background: var(--fg);
  color: var(--bg);
}

.modal__nav--prev:hover { transform: translateX(-2px); }
.modal__nav--next:hover { transform: translateX(2px); }
.modal__nav:active { transform: scale(.94); }

.modal__counter {
  margin: 0;
  font-size: .6875rem;
  font-weight: 700;
  letter-spacing: .18em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.modal__pips {
  display: flex;
  align-items: center;
  gap: .55rem;
}

.modal__pip {
  width: 26px;
  height: 4px;
  padding: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, .24);
  transition: background .3s var(--ease), width .3s var(--ease);
}

.modal__pip:hover { background: rgba(255, 255, 255, .5); }

.modal__pip.is-active {
  width: 44px;
  background: var(--fg);
}

@media (max-width: 767px) {
  .modal__stage { --card-w: 84%; }
  .modal__card { max-height: calc(100dvh - 6rem); }
  .modal__counter { display: none; }
  .modal__nav { width: 42px; height: 42px; }
  .modal__pip { width: 20px; }
  .modal__pip.is-active { width: 34px; }
}

/* ==========================================================================
   ABOUT PAGE
   ========================================================================== */

.about__cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.5rem clamp(1.5rem, 4vw, 3rem);
  margin-top: clamp(2.5rem, 6vw, 4rem);
}


/* Portrait on the left, both paragraphs stacked against it on the right. The
   image column is capped at the file's own 640px original so the plate never
   goes soft, and the split only happens once there is width for the text to
   keep a readable measure - below that the two stack in source order, portrait
   first. */
.about__intro {
  display: grid;
  gap: clamp(2rem, 5vw, 3rem);
}

@media (min-width: 900px) {
  .about__intro {
    grid-template-columns: clamp(240px, 30vw, 440px) minmax(0, 1fr);
    column-gap: clamp(2.5rem, 5vw, 4.5rem);
    align-items: start;
  }
}

.about__copy {
  display: grid;
  gap: clamp(1.75rem, 4vw, 3rem);
}

/* full-measure paragraphs - the leading opens up to keep the long lines
   trackable now that they run the whole width of the container */
.about__para {
  max-width: none;
  font-size: clamp(.9375rem, 1.7vw, 1.3rem);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0;
  line-height: 1.45;
  text-wrap: pretty;
}

/* Fills its column on the split layout; capped at the file's own size when it
   is stacked above the text, since the supplied headshot is only 640px square
   and stretched across the shell it would be visibly soft. Raise the cap once
   a larger original lands. */
.about__portrait {
  display: block;
  width: min(100%, 440px);
  /* the width/height attributes stay on the tag for CLS, so the mapped
     height:640px has to be cleared or it wins over the ratio below */
  height: auto;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: var(--radius-lg);
}

@media (min-width: 900px) {
  .about__portrait { width: 100%; }
}

/* The project accounts. Same grid rhythm the fact rows carried - hairline
   above, small tracked-out label, display name under it - so the block still
   reads as the page's closing data row rather than as a new component. Three
   columns rather than four, since there are three accounts and an empty
   fourth cell would read as a missing one. */
.about__projects {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 2rem 1.5rem;
  margin: 0;
  padding: clamp(2rem, 4vw, 3rem) 0 0;
  list-style: none;
  border-top: 1px solid var(--line);
}

@media (min-width: 760px) {
  .about__projects { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* the whole cell is the target, not just the two words of the name */
.about__project {
  display: block;
  color: inherit;
  text-decoration: none;
}

.about__project-handle {
  display: block;
  font-size: .625rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .18em;
  color: var(--faint);
  transition: color .35s var(--ease);
}

.about__project-name {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-top: .5rem;
  font-size: clamp(.9375rem, 1.7vw, 1.3rem);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: -.025em;
  line-height: 1.12;
}

.about__project-arrow {
  flex: none;
  transition: transform .35s var(--ease);
}

.about__project:hover .about__project-handle,
.about__project:focus-visible .about__project-handle { color: var(--fg); }

.about__project:hover .about__project-arrow,
.about__project:focus-visible .about__project-arrow { transform: translate(3px, -3px); }

/* ==========================================================================
   CONTACT PAGE
   The identity-card row: the portrait is the anchor in the middle, the reach
   column settles on its bottom edge, and the two fact blocks pin to its top
   and bottom. Everything is driven off one grid row so the three columns
   share the plate's height instead of each guessing at it.
   ========================================================================== */

/* The contact page runs a flat 90px side margin measured from the window
   edge, not from a centred container. Dropping .shell's 1440px cap is the
   half that matters: above that width the cap's auto margins, not the
   padding, are what set the visible gap, so raising the padding alone moves
   nothing on a wide display. Scoped to this page's main element so the
   shared .shell keeps its centred 1440 / 4vw behaviour everywhere else. */
.page--contact .shell {
  max-width: none;
  padding-inline: clamp(1.25rem, 7vw, 90px);
}

.contact {
  /* one plate width drives both the image and the middle grid track, so the
     track can never fall back to the file's 640px intrinsic width and starve
     the two text columns */
  --plate: clamp(220px, 25vw, 320px);
  padding-top: clamp(1.5rem, 5vw, 4rem);
  padding-bottom: var(--section);
}

.contact__grid {
  display: grid;
  gap: clamp(2.5rem, 6vw, 4rem);
}

/* The supplied headshot is a 640px square, so the plate is capped well below
   the column width - stretched wider it would go visibly soft. Raise the cap
   once a larger original lands. */
.contact__portrait {
  display: block;
  width: min(100%, var(--plate));
  /* the width/height attributes stay on the tag for CLS, so the mapped
     height:640px has to be cleared or it wins over the ratio below */
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  object-position: 50% 22%;
  border-radius: var(--radius-lg);
}

/* ---------- fact blocks ---------- */

.contact__meta {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: clamp(2rem, 5vw, 3.5rem);
}

.contact__facts { display: grid; }

.contact__row {
  font-size: .8125rem;
  line-height: 1.6;
  text-wrap: pretty;
}

/* dt and dd run inline so a long value wraps flush under the label, the way
   a printed credit line does, rather than hanging off a flex column */
.contact__row dt,
.contact__row dd {
  display: inline;
  margin: 0;
}

.contact__row dt {
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .1em;
  font-size: .6875rem;
  color: var(--faint);
}

.contact__row dt::after { content: ":"; }

.contact__row dd { color: var(--fg); font-weight: 500; }

/* ---------- reach column ---------- */

.contact__reach {
  display: grid;
  gap: clamp(2rem, 4vw, 3rem);
  align-content: end;
}

.contact__label {
  font-size: .6875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .14em;
  color: var(--fg);
}

.contact__socials {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem;
  margin-top: 1.1rem;
}

.contact__social {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--fg);
  transition: background .35s var(--ease), color .35s var(--ease),
              border-color .35s var(--ease), transform .2s var(--ease);
}

.contact__social svg { width: 17px; height: 17px; }

.contact__social:hover {
  background: var(--fg);
  border-color: var(--fg);
  color: var(--bg);
}

.contact__social:active { transform: scale(.94); }

.contact__mail {
  display: inline-block;
  margin-top: 1.1rem;
  font-size: clamp(.9375rem, 1.5vw, 1.125rem);
  font-weight: 700;
  letter-spacing: -.01em;
  color: var(--muted);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 5px;
  transition: color .3s var(--ease), text-decoration-color .3s var(--ease);
}

.contact__mail:hover { color: var(--fg); }

/* Tablet: the three-column row has nowhere near the width it needs, but a
   flat stack leaves the right half of the shell empty. Pair the plate with
   the facts instead and drop the reach block underneath, full width. */
@media (min-width: 640px) and (max-width: 999px) {
  .contact__grid {
    --plate: clamp(240px, 30vw, 300px);
    grid-template-columns: var(--plate) minmax(0, 1fr);
    align-items: start;
    column-gap: clamp(2rem, 5vw, 3.5rem);
  }

  .contact__portrait-col { grid-column: 1; grid-row: 1; }

  .contact__meta {
    grid-column: 2;
    grid-row: 1;
    justify-content: flex-start;
    gap: 2rem;
  }

  .contact__reach {
    grid-column: 1 / -1;
    grid-row: 2;
    align-content: start;
  }
}

@media (min-width: 1000px) {
  .contact__grid {
    /* the facts column keeps a floor so "Current base: Berlin, Germany" stays
       on one line before the fr split gets a say */
    grid-template-columns: minmax(0, 1.5fr) var(--plate) minmax(230px, 1fr);
    align-items: stretch;
    gap: clamp(2rem, 5vw, 4.5rem);
  }

  /* DOM order stays portrait / facts / reach so the stacked view reads
     top-down; the desktop row puts the reach column back on the left */
  .contact__portrait-col { grid-column: 2; grid-row: 1; }
  .contact__meta         { grid-column: 3; grid-row: 1; }
  .contact__reach        { grid-column: 1; grid-row: 1; }
}

/* ==========================================================================
   LANKA - the art nude series, laid out as the client's board is

   Twelve 4:3 plates four across, then the wordmark and the three marks
   sharing the line beneath them, then the copy hanging off the same left edge
   as the first plate. Plates, wordmark and copy all share one column, so
   every block starts and ends on the same two vertical lines.

   THE BOARD IS SIZED BY THE WINDOW'S HEIGHT, NOT ITS WIDTH. The client's
   board is 1024x1280 - taller than it is wide - and a laptop screen is the
   other way round, so a full-width grid of 4:3 plates plus this much copy
   cannot both fit one landscape viewport. The copy has to stay on the first
   screen, so the width follows the height: the board takes the smaller of
   what the window is wide and what its height can carry, and everything
   inside is expressed in `cqw` against that one width. Scale the board and
   the whole composition scales with it, proportions intact.

   --lanka-k is the stack's own height/width ratio, measured off the rendered
   page rather than derived - the copy's line count is what makes it awkward
   to compute. Re-measure it if the copy, the wordmark share or any of the
   cqw gaps below change, or the board will overshoot the fold again. It went
   1.13/1.12 -> 1.19/1.165 on 2026-09-14 when the @lanka.magazine line was
   added under the copy: one extra line plus its gap costs about 5% of the
   stack's height, and the whole board pays for it by getting that much
   narrower. Measured as the fixed point of stackHeight(shellWidth)/shellWidth
   across 1280x720 through 2560x1440, not eyeballed.
   ========================================================================== */

.page--lanka {
  --lanka-red:    #c02a17;
  --lanka-side:   clamp(1.25rem, 5.4vw, 96px);
  --lanka-top:    clamp(1.25rem, 3vh, 2.75rem);
  --lanka-bottom: clamp(1.5rem, 4vh, 4rem);
  /* On a short window the copy hits its rem floor and stops shrinking with
     the board, so the stack runs proportionally taller and k has to rise with
     it. Both values are measured off the rendered page, not derived. */
  --lanka-k:      1.20;
}

@media (min-height: 880px) {
  .page--lanka { --lanka-k: 1.175; }
}

/* The nav is fixed, so its height is dead space the board has to start below.
   It is subtracted in the fit formula too - the two have to agree or the
   first plate row hides under the nav. */
.lanka-board {
  padding-block: calc(var(--nav-h) + var(--lanka-top)) var(--lanka-bottom);
  padding-inline: var(--lanka-side);
}

.lanka-shell {
  width: 100%;
  max-width: 1704px;
  margin-inline: auto;
  container-type: inline-size;
}

/* Only fit-to-height where the four-column grid actually lives. Below that
   the board is already width-starved and shrinking it further to chase the
   fold would leave postage stamps - narrow screens scroll instead.

   The 560px floor is what the formula may shrink to before it gives up and
   lets the page scroll; it only binds on windows under about 730px tall.
   DO NOT lower it to buy those windows back - tried on 2026-09-14 and it makes
   things worse, not better. k is not really a constant: below about 560px the
   copy hits its rem floor and stops scaling with the board, so the stack grows
   proportionally TALLER as the board narrows. Dropping the floor to 516px
   pushed 1366x768 and 1024x768 from fitting to overflowing by 13px while
   1280x720 still overshot. Short windows scroll; that is the deal. */
@media (min-width: 1000px) {
  .lanka-shell {
    width: min(
      100%,
      1704px,
      max(560px, calc(
        (100dvh - var(--nav-h) - var(--lanka-top) - var(--lanka-bottom)) / var(--lanka-k)
      ))
    );
  }
}

/* ---------- the plate grid ---------- */

.lanka-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1.4cqw;
}

.lanka-plate {
  margin: 0;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  /* the plates carry their own saturated backdrops, so the placeholder is the
     raised grey rather than page black - a plate that has not decoded yet
     still reads as a plate and the grid never collapses */
  background: var(--bg-raise);

  /* THE CURTAIN. Each plate is a blind rolled up to its own top edge and
     unrolls downward, one after the next across the grid. It waits on two
     signals at once: .is-in from the scroll-reveal observer, and .is-entered
     on the root, which the preloader sets when its own panels part - without
     the second the whole cascade would play out behind the preloader and the
     visitor would only ever meet the finished state. The inline script in the
     head sets .is-entered on a 6s dead-man switch, so a failed main.js delays
     the mosaic, it never loses it. */
  clip-path: inset(0 0 100% 0);
  transition: clip-path .95s var(--ease-reveal);
  transition-delay: calc(180ms + var(--i, 0) * 55ms);
}

:root.is-entered .lanka-grid.is-in .lanka-plate { clip-path: inset(0 0 0 0); }

.lanka-plate img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* per-plate framing lives in a style attribute on the img: the sources are
     square, portrait and landscape and each one has its own subject to hold,
     so the crop is chosen per file rather than by one rule */
  object-position: 50% 50%;
  transition: transform 1.1s var(--ease);
}

@media (hover: hover) {
  .lanka-plate:hover img { transform: scale(1.035); }
}

/* The grid carries .reveal only so main.js hands it .is-in; the plates do the
   actual entrance, so the wrapper must not fade or slide on its own. */
.lanka-grid.reveal {
  opacity: 1;
  transform: none;
  transition: none;
}

/* ---------- wordmark + marks ---------- */

.lanka-mark {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 4cqw;
  padding-top: max(1.5rem, 6.4cqw);
}

/* 34% is the wordmark's share of the board width on the client's sheet. The
   cap matters as much as the share: the source art is 734px wide, so past
   about 620 CSS px it starts to soften. */
.lanka-mark__word {
  flex: 0 1 34cqw;
  min-width: 96px;
  max-width: 620px;
  margin: 0;
  line-height: 0;
}

.lanka-mark__word img {
  width: 100%;
  height: auto;
}

.lanka-mark__signs {
  display: flex;
  align-items: center;
  gap: 4.6cqw;
  color: var(--lanka-red);
}

.lanka-sign {
  flex: none;
  display: block;
  width: 6.2cqw;
  min-width: 19px;
  max-width: 78px;
  height: auto;
}

/* ---------- the copy ---------- */

/* Two measures, the way the board sets them: the opening paragraph runs short
   and the second one runs wide. Both are a share of the board rather than a
   ch count, so the line count holds as the board scales and --lanka-k above
   stays true. The rem floor on the type is the one thing allowed to break
   proportion - below it the copy stops being readable. */
.lanka-copy {
  display: grid;
  gap: 1.9cqw;
  padding-top: max(1.25rem, 6.4cqw);
  font-size: max(.9375rem, 2cqw);
  font-weight: 700;
  line-height: 1.45;
  letter-spacing: -.01em;
}

.lanka-copy p { max-width: 100%; }
.lanka-copy p.lanka-copy__lead { max-width: 58%; }

/* The magazine's own handle, under the copy. It sits in the board's red so it
   reads as part of the Lanka identity rather than as site chrome, and it is a
   grid child of .lanka-copy so it inherits the same 1.9cqw rhythm - which also
   means its height is inside what --lanka-k was measured against. Re-measure k
   if this line's size or spacing changes. */
.lanka-link {
  justify-self: start;
  color: var(--lanka-red);
  text-decoration: none;
}

.lanka-link .roll { padding-bottom: .08em; }

.lanka-link:hover,
.lanka-link:focus-visible { color: var(--lanka-red); }

/* On a short window the copy is what pushes the board past the fold: its type
   hits the rem floor and stops scaling, so the same words cost proportionally
   more height and the plates would have to shrink to thumbnails to pay for
   it. Cheaper to buy the height back out of the copy - a hair less leading, a
   slightly smaller floor, and a wider opening measure so it sets in fewer
   lines - than out of the photographs. Must sit after the base .lanka-copy
   rules: same specificity, so source order is what decides it. */
@media (max-height: 879px) {
  .lanka-copy {
    font-size: max(.875rem, 2cqw);
    line-height: 1.38;
  }

  .lanka-copy p.lanka-copy__lead { max-width: 68%; }
}

/* ---------- narrow ---------- */

/* Three across from here down, phones included - the client wants the mosaic
   to stay a mosaic on a phone rather than break into a two-wide column. */
@media (max-width: 999px) {
  .lanka-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .lanka-copy { font-size: max(1rem, 2.4cqw); line-height: 1.5; }
  .lanka-copy p.lanka-copy__lead { max-width: 74%; }

  /* Below 1000px the board is not fitted to the fold any more, so the handle
     can take a full 44px tap target without costing the plates height. */
  .lanka-link {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }

  /* Below the desktop board the row has width to spare - the three marks are
     floored at 19px and stop shrinking, so holding the wordmark at the sheet's
     34% left it reading tiny next to them. Give it half the row instead; the
     measured gap to the marks is still 45px at 320px wide. */
  .lanka-mark__word { flex-basis: 52cqw; }
}

@media (max-width: 719px) {
  .lanka-copy { font-size: 1rem; }
  .lanka-copy p.lanka-copy__lead { max-width: 100%; }
}

/* Reduced motion gets the finished mosaic, never the curtain. This has to sit
   after the .lanka-plate rule above - same specificity, so source order is
   what decides it. */
@media (prefers-reduced-motion: reduce) {
  .lanka-plate {
    clip-path: none;
    transition: none;
  }
}

/* ==========================================================================
   PRELOADER
   The mark holds the screen while the page settles, then the two panels part
   sideways - the same move the Services plate makes further down, so the site
   opens the way its content opens.

   It is display:none by default and only switched on by .is-preload, which a
   short inline script in the head sets before first paint. A visitor who has
   already seen it this session, or who asked for reduced motion, never gets
   the class and so never sees a frame of it.
   ========================================================================== */

.preloader { display: none; }

.is-preload .preloader {
  position: fixed;
  inset: 0;
  z-index: 120;
  display: grid;
  place-items: center;
}

/* everything under the curtain waits for it to open */
.is-preload .nav,
.is-preload .page,
.is-preload .footer { opacity: 0; }

.is-preload.is-entered .nav,
.is-preload.is-entered .page,
.is-preload.is-entered .footer {
  opacity: 1;
  transition: opacity .85s var(--ease);
}

.preloader__panel {
  position: absolute;
  top: 0;
  bottom: 0;
  /* a sliver over half, so the two overlap at the centre and no hairline of
     the page shows through before the split starts */
  width: 50.5%;
  background: var(--bg);
  transition: transform 1.15s var(--ease-reveal);
}

.preloader__panel--a { left: 0; }
.preloader__panel--b { right: 0; }

/* Positioned, but deliberately with no z-index of its own. A z-index here
   would make this a stacking context and isolate the mark's blend mode inside
   it, at which point the film's black field stops resolving against the panels
   and comes back as a visible square. Paint order is handled by DOM order
   instead: both the panels and this box are positioned with z-index:auto, and
   this one comes last. */
.preloader__stage {
  position: relative;
  display: grid;
  justify-items: center;
  gap: clamp(1.1rem, 2.6vw, 1.75rem);
}

/* The mark is a looping film on a true-black field; `lighten` resolves that
   field to the panel colour behind it. The fade-out below is applied to this
   element rather than to .preloader__stage on purpose - an ancestor carrying
   opacity would isolate the blend and the black field would turn back into a
   visible square. */
.preloader__mark {
  width: clamp(96px, 14vw, 168px);
  aspect-ratio: 1;
  mix-blend-mode: lighten;
}

.preloader__word {
  display: flex;
  font-size: clamp(.6875rem, 1.5vw, .875rem);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .44em;
  /* the tracking hangs off the last letter, so pull the row back by one step
     to keep it optically centred under the mark */
  text-indent: .44em;
  color: var(--fg);
}

.preloader__word span {
  opacity: 0;
  transform: translateY(10px);
  animation: preload-letter .75s var(--ease-reveal) forwards;
}

.preloader__word span:nth-child(1) { animation-delay: .10s; }
.preloader__word span:nth-child(2) { animation-delay: .16s; }
.preloader__word span:nth-child(3) { animation-delay: .22s; }
.preloader__word span:nth-child(4) { animation-delay: .28s; }
.preloader__word span:nth-child(5) { animation-delay: .34s; }
.preloader__word span:nth-child(6) { animation-delay: .40s; }
.preloader__word span:nth-child(7) { animation-delay: .46s; }
.preloader__word span:nth-child(8) { animation-delay: .52s; }
.preloader__word span:nth-child(9) { animation-delay: .58s; }

@keyframes preload-letter {
  to { opacity: 1; transform: none; }
}

.preloader__count {
  position: absolute;
  z-index: 2;
  left: var(--hero-pad);
  bottom: calc(30px + 1.35rem);
  font-size: .6875rem;
  font-weight: 700;
  letter-spacing: .2em;
  line-height: 1;
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}

.preloader__rule {
  position: absolute;
  z-index: 2;
  left: var(--hero-pad);
  right: var(--hero-pad);
  bottom: 30px;
  height: 1px;
  background: rgba(255, 255, 255, .13);
  overflow: hidden;
}

.preloader__fill {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--fg);
  transform: scaleX(0);
  transform-origin: left center;
}

/* ---- exit: the readout clears first, then the panels part ---- */

.preloader.is-done .preloader__mark,
.preloader.is-done .preloader__word,
.preloader.is-done .preloader__count,
.preloader.is-done .preloader__rule {
  opacity: 0;
  transform: translateY(-12px);
  transition: opacity .42s var(--ease), transform .62s var(--ease-reveal);
}

.preloader.is-done .preloader__word span { animation: none; opacity: 1; }

.preloader.is-done .preloader__panel--a {
  transform: translateX(-101%);
  transition-delay: .3s;
}

.preloader.is-done .preloader__panel--b {
  transform: translateX(101%);
  transition-delay: .3s;
}

.preloader.is-gone { display: none; }

@media (max-width: 767px) {
  .preloader__count { bottom: calc(24px + 1.35rem); }
  .preloader__rule  { bottom: 24px; }
}

@media (prefers-reduced-motion: reduce) {
  .preloader { display: none !important; }
  .is-preload .nav,
  .is-preload .page,
  .is-preload .footer { opacity: 1; }
}

/* ==========================================================================
   scroll reveal
   ========================================================================== */

.reveal {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity .9s var(--ease), transform .9s var(--ease);
}

.reveal.is-in {
  opacity: 1;
  transform: none;
}

/* ==========================================================================
   reduced motion
   ========================================================================== */

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

  *,
  *::before,
  *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }

  .reveal { opacity: 1; transform: none; }
  .nav__panel { transform: none; }
  .marquee__track { animation: none; }
  .tile:nth-child(2n) { transform: none; }
}
