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

:root {
  --gap: 20px;
  --font: "Julius Sans One", sans-serif;
  --track: 0.3em;
}

body {
  background: #000;
  color: #fff;
  font-family: var(--font);
  letter-spacing: var(--track);
  text-transform: uppercase;
}

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

/* ---------- Tile grid ---------- */
.tiles {
  height: 100dvh;
  min-height: 560px;
  display: grid;
  grid-template-rows: minmax(0, 65fr) minmax(0, 35fr);
  gap: var(--gap);
}
.row { display: grid; gap: var(--gap); min-height: 0; }
.row--top    { grid-template-columns: minmax(0, 55fr) minmax(0, 45fr); }
.row--bottom { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.tile { position: relative; display: block; overflow: hidden; }

.tile img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;               /* fills the tile, never distorts */
  object-position: var(--pos);
  transform-origin: var(--pos);
  transition: transform 0.6s ease-out;
}
.tile:hover img,
.tile:focus-visible img { transform: scale(1.05); }
.tile:focus-visible { outline: 2px solid #fff; outline-offset: -4px; }

/* ---------- Captions ---------- */
.caption {
  position: absolute;
  bottom: 40px;
  display: inline-block;
  text-shadow: 0 0 12px rgba(0, 0, 0, 0.55);
}
.row--bottom .caption { bottom: 33px; }
.caption-top .caption { bottom: auto; top: 52px; }

.caption .title {
  position: relative;
  display: block;
  font-size: 16px;
  line-height: 1;
  padding-bottom: 7px;
  margin-right: calc(var(--track) * -1);   /* cancel trailing letter-space */
}
/* The line runs from the text to the nearest tile edge only. */
.caption .title::after {
  content: "";
  position: absolute;
  bottom: -1px;
  height: 1px;
  background: rgba(255, 255, 255, 0.55);
  transition: background-color 0.4s;
}
/* Caption on the left: line goes from the tile's left edge to the end of the text */
.caption-left .title::after  { left: calc(var(--pad) * -1); right: var(--track); }
/* Caption on the right: line goes from the start of the text to the tile's right edge */
.caption-right .title::after { left: 0; right: calc(var(--pad) * -1); }
.caption .tagline {
  display: block;
  font-size: 9px;
  line-height: 1;
  margin-top: 6px;
  margin-right: calc(var(--track) * -1);
  opacity: 0.8;
  transition: opacity 0.4s;
}
.tile:hover .title::after,
.tile:focus-visible .title::after { background: #fff; }
.tile:hover .tagline,
.tile:focus-visible .tagline { opacity: 1; }

/* Line runs to the tile's left edge, text right-aligned */
.caption-left .caption { left: 0; padding-left: var(--pad); text-align: right; }
/* Line runs to the tile's right edge, text left-aligned */
.caption-right .caption { right: 0; padding-right: var(--pad); text-align: left; }
.caption-right .title,
.caption-right .tagline { margin-right: 0; }

/* ---------- Logo + menu (right edge of the top-left tile) ---------- */
.site-header {
  position: fixed;
  top: 75px;
  right: 48px;                                     /* default (inner pages) */
  z-index: 10;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}
.home .site-header {
  right: calc(100vw - (100vw - var(--gap)) * 0.55 + 48px);
}
.logo img { display: block; width: 230px; height: auto; }
body.home .logo img { width: 260px; }

.site-header nav {
  margin-top: 29px;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
}
.site-header nav a {
  font-size: 11px;
  line-height: 19px;
  margin-right: calc(var(--track) * -1);
  text-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
}
/* Julius Sans One has no bold, so a thin text stroke thickens the letters instead.
   Change --bold to make it lighter or heavier. */
.site-header nav a {
  --bold: 0.7px;
  -webkit-text-stroke: 0 currentColor;
  transition: -webkit-text-stroke-width 0.25s;
}
.site-header nav a:hover,
.site-header nav a:focus-visible,
.site-header nav a[aria-current] { -webkit-text-stroke-width: var(--bold); }

/* ---------- Language switch (ENG / ESP), top-left on every page; added by script.js ---------- */
.lang-switch {
  position: fixed;
  top: 11px;
  left: 10px;
  z-index: 30;
  display: flex;
  gap: 5px;
}
.lang-switch button {
  padding: 2px 0;
  margin-right: -0.18em;                /* cancel trailing letter-space */
  border: 0;
  background: none;
  color: rgba(255, 255, 255, 0.75);
  font: 9.5px/1 var(--font);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  text-shadow: 0 0 6px rgba(0, 0, 0, 0.45);
  cursor: pointer;
  -webkit-text-stroke: 0 currentColor;
  transition: -webkit-text-stroke-width 0.25s, color 0.25s;
}
/* The language in use reads bold (same text stroke as the selected menu item) */
.lang-switch button[aria-pressed="true"] { color: #fff; -webkit-text-stroke-width: 0.7px; cursor: default; }
.lang-switch button:hover,
.lang-switch button:focus-visible { color: #fff; outline: none; }
.lang-switch button:focus-visible { text-decoration: underline; text-underline-offset: 3px; }

/* ---------- About page (desk scene) ---------- */
.sr-only {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

body.desk-page { height: 100dvh; overflow: hidden; background: #3a2416; }

/* The desk photo (3000x1900) always covers the screen, centred. Positions on the sheet
   below are % of this box, measured on the 3000x1900 artwork, and type sizes are in
   cqw (1cqw = 1% of the box width) so the writing scales with the notebook. */
.desk {
  position: fixed;
  left: 50%;
  top: 50%;
  width: max(100vw, 100dvh * 3000 / 1900);
  aspect-ratio: 3000 / 1900;
  transform: translate(-50%, -50%);
  container-type: inline-size;
}
.desk-bg { display: block; width: 100%; height: 100%; }

/* About page: the About text is handwritten on the empty notebook sheet of
   desk-blank.webp. If it ever runs longer than the sheet, it scrolls inside the sheet
   instead of spilling onto the desk. */
.sheet-text {
  position: absolute;
  left: 40.91%; top: 24.9474%; width: 20.9567%; height: 48%;        /* x 1227.3-1856, y 474-1386 */
  overflow-y: auto;
  scrollbar-width: none;
  color: #637080;
  font: var(--sheet-size, max(13px, 1.11cqw))/1.18 "Caveat", cursive;
  letter-spacing: 0.02em;
  text-transform: none;
}
.sheet-text::-webkit-scrollbar { display: none; }
.sheet-text h1 {
  font: inherit;
  font-size: 1.25em;
  line-height: 1;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: underline;
  text-decoration-thickness: 0.06em;
  text-underline-offset: 0.18em;
  margin-bottom: 0.45em;
}
.sheet-text p + p { margin-top: 0.4em; }
.sheet-text a {
  color: inherit;
  text-decoration: underline;
  text-decoration-thickness: 0.05em;
  text-underline-offset: 0.15em;
  transition: color 0.2s;
}
.sheet-text a:hover,
.sheet-text a:focus-visible { color: #a3271f; outline: none; }

/* Pocket watch seconds hand: ticks once a second, set to the visitor's clock (script.js).
   The box is centred on the watch's pin at (2301.5, 542.5) of the 3000x1900 artwork,
   so the hand scales with the photo. */
.watch-seconds {
  position: absolute;
  left: 73%; top: 22.6842%; width: 7.4333%;
  aspect-ratio: 1;
  overflow: visible;
  pointer-events: none;
}
.watch-seconds-hand {
  fill: #2b2520;
  transform-origin: 0 0;
  animation: watch-tick 60s steps(60, end) infinite;
}
@keyframes watch-tick { to { transform: rotate(360deg); } }

/* The Ramones, the coaster and Corto Maltese sit on the desk but are placed against the screen corners,
   independent of the photo, so they can be moved freely. */
.desk-decor { position: fixed; z-index: 1; height: auto; pointer-events: none; }
.decor-ramones { left: 7.87vw; top: 2.95vh;    width: 22.8vw; }
.decor-coaster { left: 8.23vw; bottom: 7.74vh; width: 17.23vw; }
.decor-pick    { right: 18.97vw; bottom: 10.16vh; width: 4.57vw; }

.decor-ramones img { display: block; width: 100%; height: auto; }

/* Count-in bubble and sound waves: drawings laid over the Ramones by script.js, placed
   in % of the image (the bubble sticks out past its right edge). */
.decor-ramones .band-layer {
  position: absolute;
  height: auto;
  opacity: 0;
  pointer-events: none;
}

.decor-corto   { right: 0;    bottom: 3.74vh; width: 18.34vw; }

/* Corto blinks: a small eyes-closed patch (cut from the same photo) sits over his eyes
   and is shown for a moment at a steady interval (timing in script.js). */
.decor-corto > img:first-child { display: block; width: 100%; height: auto; }
.corto-blink {
  position: absolute;
  left: 28.7273%; top: 28.1609%; width: 28.5455%;
  height: auto;
  opacity: 0;
  transition: opacity 80ms ease-in-out;    /* just enough to soften the swap */
}
.decor-corto.is-blinking .corto-blink { opacity: 1; }

/* ---------- Inner pages ---------- */
.page { min-height: 100dvh; padding: 56px 48px; display: flex; align-items: center; }
.page .text { max-width: 560px; text-transform: none; letter-spacing: 0.08em; line-height: 1.8; font-size: 15px; }
.page h1 { font-size: 20px; font-weight: normal; letter-spacing: var(--track); text-transform: uppercase; margin-bottom: 24px; }

/* ---------- Contact page (waterfall) ---------- */
body.falls-page { min-height: 100dvh; background: #010205; }
.falls-bg { position: fixed; inset: 0; z-index: -1; overflow: hidden; }
/* The photo (3017x1900) always covers the screen, centred. */
.falls-stage {
  position: absolute;
  left: 50%;
  top: 50%;
  width: max(100vw, 100dvh * 3017 / 1900);
  aspect-ratio: 3017 / 1900;
  transform: translate(-50%, -50%);
}
.falls-stage > img { display: block; width: 100%; height: 100%; }

/* --- Moving waterfall ---
   Two scrolling streak textures are blended ("overlay") onto the photo. Overlay barely
   touches dark areas, so only the bright water shows the effect. The box (left/top/
   width/height, as % of the image) frames the waterfall; its edges are feathered by the mask.
   Speeds: each layer moves one tile height (in vw) per animation cycle. */
.water {
  position: absolute;
  left: 39%; top: 30%; width: 22%; height: 46%;
  overflow: hidden;
  mix-blend-mode: overlay;
  opacity: 0.2;                          /* strength of the effect */
  -webkit-mask-image: radial-gradient(ellipse closest-side at 50% 50%, #000 55%, transparent 100%);
          mask-image: radial-gradient(ellipse closest-side at 50% 50%, #000 55%, transparent 100%);
}
.flow {
  position: absolute;
  left: 0; right: 0; top: 0;
  background: url("img/about-streaks.webp") repeat;
  will-change: transform;
}
.flow-a { height: calc(100% + 32vw); background-size: 8vw 32vw;   animation: fall-a 14s linear infinite; }
.flow-b { height: calc(100% + 18vw); background-size: 4.5vw 18vw; animation: fall-b 7s linear infinite; opacity: 0.5; }
@keyframes fall-a { from { transform: translate3d(0, -32vw, 0); } to { transform: translate3d(0, 0, 0); } }
@keyframes fall-b { from { transform: translate3d(0, -18vw, 0); } to { transform: translate3d(0, 0, 0); } }

/* Slow drifting mist over the pool at the base */
.mist {
  position: absolute;
  left: 36%; top: 62%; width: 30%; height: 20%;
  background:
    radial-gradient(ellipse at 25% 60%, rgba(190, 215, 255, 0.45), transparent 60%),
    radial-gradient(ellipse at 70% 45%, rgba(190, 215, 255, 0.40), transparent 62%),
    radial-gradient(ellipse at 50% 75%, rgba(190, 215, 255, 0.35), transparent 55%);
  mix-blend-mode: screen;
  -webkit-mask-image: radial-gradient(ellipse closest-side at 50% 50%, #000 55%, transparent 100%);
          mask-image: radial-gradient(ellipse closest-side at 50% 50%, #000 55%, transparent 100%);
  animation: mist 10s ease-in-out infinite alternate;
}
@keyframes mist {
  from { transform: translateX(-4%) scale(1);    opacity: 0.35; }
  to   { transform: translateX(4%)  scale(1.08); opacity: 0.6; }
}
.falls-page .site-header { top: 43vh; right: 63px; }

/* Text block on the left, with the thin white line, centred vertically. If the screen
   is too short for it, the page scrolls instead of cutting it off. */
.contact-main {
  min-height: 100dvh;
  display: flex;
  align-items: center;
  padding: 24px 24px 24px 11vw;
}
.contact-text {
  width: min(420px, 100%);
  padding: 42px 0 3px 20px;
  border-left: 1px solid rgba(255, 255, 255, 0.5);
}
.contact-text h1 {
  font-size: 20px;
  font-weight: normal;
  line-height: 1;
  margin-bottom: 9px;
}
.contact-intro {
  font-size: 11px;
  line-height: 20px;
  letter-spacing: 0.06em;
  color: rgba(255, 255, 255, 0.85);
}
/* A tight dark halo hugging the letters of the intro line and SEND, so the photo's
   frame lines don't cut through them. The same small shadow is stacked several times,
   because one layer is too faint on this thin font. */
.contact-intro,
.cf-send {
  text-shadow:
    0 0 1px #010205, 0 0 1px #010205,
    0 0 2px #010205, 0 0 2px #010205, 0 0 2px #010205,
    0 0 3px #010205, 0 0 3px #010205,
    0 0 4px rgba(1, 2, 5, 0.8);
}
.contact-intro a { color: #fff; transition: -webkit-text-stroke-width 0.25s; -webkit-text-stroke: 0 currentColor; }
.contact-intro a:hover,
.contact-intro a:focus-visible { -webkit-text-stroke-width: 0.7px; outline: none; }

/* --- The form: no boxes, just thin rules, in the site font --- */
.contact-form { margin-left: -4px; transition: opacity 0.35s ease, visibility 0s linear 0s; }
.contact-form label {
  font-size: 11px;
  line-height: 1;
  letter-spacing: 0.1em;
  color: rgba(255, 255, 255, 0.85);
}
.contact-form label[for="c-message"] { text-transform: none; }
.cf-head {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 0 0 9px 4px;
}
.cf-message,
.cf-email {
  display: block;
  margin: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: #81bbf9;
  caret-color: #81bbf9;
  font: 12px/20px var(--font);
  letter-spacing: 0.08em;
  text-transform: none;           /* Julius Sans One's lowercase gives the small-caps look */
  outline: none;
  -webkit-appearance: none;
          appearance: none;
}
.cf-message {
  width: 100%;
  height: clamp(110px, 22.8vh, 300px);
  padding: 12px 4px;
  resize: none;
  border-top: 1px solid rgba(255, 255, 255, 0.2);
  border-bottom: 1px solid rgba(255, 255, 255, 0.2);
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.25) transparent;
  transition: border-color 0.25s;
}
.cf-row {
  display: flex;
  align-items: center;
  gap: 8px;
  height: 32px;
  padding-left: 4px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.2);
  transition: border-color 0.25s;
}
.cf-email { flex: 1; min-width: 0; height: 100%; padding: 0; }
/* The rules brighten around the field being typed in */
.cf-message:focus { border-color: rgba(255, 255, 255, 0.75); }
.cf-row:focus-within { border-bottom-color: rgba(255, 255, 255, 0.75); }

.cf-actions {
  display: flex;
  justify-content: flex-end;
  align-items: center;             /* CLEAR, SEND and the note share one centre line */
  gap: 28px;
  padding: 8px 10px 0 0;
}
.cf-clear,
.cf-send {
  padding: 4px 0;
  margin-right: calc(var(--track) * -1);
  border: 0;
  background: none;
  color: rgba(255, 255, 255, 0.85);
  font-family: var(--font);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  line-height: 1;
  cursor: pointer;
  -webkit-text-stroke: 0 currentColor;
  transition: -webkit-text-stroke-width 0.25s, color 0.25s;
}
.cf-clear { font-size: 11px; margin-right: 0; }
.cf-send  { font-size: 18px; color: #fff; letter-spacing: 0.12em; }
.cf-clear:hover, .cf-clear:focus-visible,
.cf-send:hover,  .cf-send:focus-visible { color: #fff; -webkit-text-stroke-width: 0.7px; outline: none; }
.contact-form.is-sending .cf-send { cursor: progress; opacity: 0.6; }

/* Short notes for a missing field or a failed send */
.cf-hint {
  margin: 0 0 0 auto;
  font-size: 11px;
  line-height: 1;
  letter-spacing: 0.1em;
  text-transform: none;
  color: #ff3887;
  -webkit-text-stroke: 0.45px currentColor;   /* Julius has no bold: a thin stroke thickens the letters */
  pointer-events: none;
}
.cf-hint:empty { display: none; }
.cf-actions .cf-hint { margin: 0 auto 0 4px; line-height: 1.3; }
.cf-actions button { flex-shrink: 0; }     /* a long note wraps instead of pushing the buttons out */

/* After sending: the form gives way to "Message sent" and "< Back". Both share one
   grid cell, so the block keeps its size and nothing jumps. */
.cf-stack { display: grid; margin-top: 38px; }
.cf-stack > * { grid-area: 1 / 1; }
.cf-sent { padding-left: 4px; opacity: 0; visibility: hidden; transition: opacity 0.5s ease, visibility 0s linear 0.5s; }
.cf-stack.is-sent .contact-form { opacity: 0; visibility: hidden; transition: opacity 0.35s ease, visibility 0s linear 0.35s; }
.cf-stack.is-sent .cf-sent { opacity: 1; visibility: visible; transition: opacity 0.5s ease 0.2s, visibility 0s; }
.cf-sent-title { font-size: 16px; line-height: 1; outline: none; -webkit-text-stroke: 0.6px currentColor; }
.cf-back {
  margin-top: 26px;
  padding: 4px 0;
  border: 0;
  background: none;
  color: rgba(255, 255, 255, 0.85);
  font: 11px/1 var(--font);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  cursor: pointer;
  -webkit-text-stroke: 0 currentColor;
  transition: -webkit-text-stroke-width 0.25s, color 0.25s;
}
.cf-back:hover, .cf-back:focus-visible { color: #fff; -webkit-text-stroke-width: 0.7px; outline: none; }

/* Phones: the menu moves to the top and the text block takes the width */
@media (max-width: 760px) {
  .falls-page .site-header { top: 28px; right: 24px; }
  .falls-page .logo img { width: 170px; }
  .contact-main { padding: 190px 20px 32px; align-items: flex-start; }
  .contact-text {
    width: 100%;
    padding-top: 28px;
    padding-right: 12px;
    background: linear-gradient(90deg, rgba(1, 2, 5, 0.6), rgba(1, 2, 5, 0.35));   /* keeps the text readable over the bright water */
  }
}

/* ---------- Project pages ---------- */
/* Everything scales with the screen width, like the background images. */
.project .site-header { position: fixed; top: 57px; right: 63px; }

.project-title { position: fixed; top: 0; left: 0; width: 100%; z-index: 5; }
.project-title .caption { top: 60px; bottom: auto; }
.caption .title, .caption .tagline { font-weight: normal; }

.shots { display: flex; flex-direction: column; gap: var(--gap); background: #000; }
.shots.split { flex-direction: row; align-items: flex-start; }   /* two side-by-side columns, same black gap */
.shots.split > * { flex: 1 1 0; min-width: 0; }
.shots.split img { display: block; width: 100%; height: auto; }
.shots.split .img-wrap { position: relative; overflow: hidden; }

/* Steam over the coffee cup on the My Bookcase spread. Shot from directly above, so
   real steam wouldn't visibly rise in the image plane — it'd swirl in place and drift
   toward camera (i.e. dissipate) rather than travel upward. Each wisp slowly orbits a
   shared pivot (very slow, no net drift) while independently growing/fading in place
   to read as dispersing rather than moving in any one direction. */
.steam {
  position: absolute;
  left: 85.469%; top: 3.107%;
  width: 32.5%; height: 8.503%;
  pointer-events: none;
  mix-blend-mode: screen;
  container-type: inline-size;
}
.steam-orbit {
  position: absolute;
  top: 50%; left: 50%;
  width: 0; height: 0;
  animation: steam-orbit linear infinite;
}
.steam-orbit-a { animation-duration: 71.5s; }
.steam-orbit-b { animation-duration: 97.5s; animation-direction: reverse; animation-delay: -22.5s; }
.steam-orbit-c { animation-duration: 85.8s; animation-delay: -46.7s; }
@keyframes steam-orbit {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}
.steam-wisp {
  position: absolute;
  top: 0; left: 0;
  width: 95cqw; height: 95cqw;
  transform: translate(-50%, -50%) translateX(13cqw);
}
.steam-orbit-b .steam-wisp { width: 78cqw; height: 78cqw; transform: translate(-50%, -50%) translateX(10cqw); }
.steam-orbit-c .steam-wisp { width: 65cqw; height: 65cqw; transform: translate(-50%, -50%) translateX(8cqw); }
/* Continuous slow self-rotation (the actual "curling" look), kept on its own element
   so it doesn't fight with the breathe pulse below over the transform property. */
.steam-spin {
  display: block;
  width: 100%; height: 100%;
  animation: steam-spin 49.4s linear infinite;
}
.steam-orbit-b .steam-spin { animation-duration: 62.4s; animation-direction: reverse; }
.steam-orbit-c .steam-spin { animation-duration: 37.7s; }
@keyframes steam-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}
.steam-wisp i {
  display: block;
  width: 100%; height: 100%;
  background-image: url(img/mybookcase/steam-wisp.webp);
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
  animation: steam-breathe 13s ease-in-out infinite backwards;
}
.steam-orbit-b .steam-wisp i { animation-delay: 4.3s; }
.steam-orbit-c .steam-wisp i { animation-delay: 8.7s; }
@keyframes steam-breathe {
  0%, 100% { opacity: 0;    transform: scale(0.7); }
  50%      { opacity: 0.85; transform: scale(1.1); }
}

/* Ping/ripple on the teal circular icons (heart, lamp) in the My Bookcase spread:
   expands from the icon's own size while fading out, then repeats. */
.icon-pulse {
  position: absolute;
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  background: rgb(29, 181, 187);
  transform: translate(-50%, -50%) scale(1);
  opacity: 0.3;
  pointer-events: none;
  animation: icon-pulse 6.5s cubic-bezier(0.25, 0.6, 0.4, 1) infinite;
}
@keyframes icon-pulse {
  0%   { transform: translate(-50%, -50%) scale(1);   opacity: 0.3; }
  100% { transform: translate(-50%, -50%) scale(2.3);  opacity: 0; }
}

/* Hand-drawn sketch notes on the My Bookcase spread: hidden until scrolled into view,
   then wipe on left-to-right like a pencil moving across the page. */
.sketch-reveal {
  position: absolute;
  display: block;
  opacity: 0;
  clip-path: inset(0 100% 0 0);
  transition: opacity 1.1s ease-out, clip-path 1.1s ease-out;
  pointer-events: none;
}
.sketch-reveal.is-revealed { opacity: 1; clip-path: inset(0 0 0 0); }

.shots .panel { position: relative; display: block; width: 100%; overflow: hidden; }   /* full-width illustrated panels, 20px black strip between */
.shots .panel-bg { display: block; width: 100%; height: 100%; object-fit: cover; }
.shots .panel-clouds {
  position: absolute;
  inset: 0;
  display: flex;
  width: 200%;
  will-change: transform;
  animation-name: drift-clouds;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
.shots .panel-clouds.depth-1 { animation-duration: 120s; }  /* front, fastest */
.shots .panel-clouds.depth-2 { animation-duration: 240s; }  /* middle, half that speed */
.shots .panel-clouds.depth-3 { animation-duration: 480s; }  /* back, quarter speed */
.shots .panel-clouds img {
  display: block;
  flex: 0 0 50%;
  width: 50%;
  height: 100%;
  object-fit: cover;
}
@keyframes drift-clouds {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}
.shot {
  position: relative;
  aspect-ratio: 2380 / 1547;
  overflow: hidden;
}
.shot:first-child { min-height: 100dvh; }   /* first section always fills the opening screen */
.shot .bg {
  position: absolute;
  left: 0;
  top: -10%;                              /* extra height so the parallax has room to move */
  width: 100%;
  height: 120%;
  object-fit: cover;                     /* fills the box, never distorts */
  will-change: transform;
}
.shot .phone {
  position: absolute;
  top: var(--top);
  left: var(--x);
  transform: translateX(-50%);
  width: 20.6vw;                         /* scales with the screen; files are 1080px wide (3x of 360px) */
  height: auto;
  will-change: transform;
}

/* ---------- Masonry gallery (project pages made of many same-family stills) ---------- */
.shots.masonry {
  display: block;
  column-count: 2;
  column-gap: var(--gap);
}
.shots.masonry figure {
  position: relative;
  margin: 0 0 var(--gap);
  break-inside: avoid;
  overflow: hidden;
  cursor: zoom-in;
  container-type: inline-size;    /* lets the caption's bottom offset key off width, not each tile's own height */
}
.shots.masonry img {
  display: block;
  width: 100%;
  height: auto;
  transition: transform 0.6s ease-out;
}
.shots.masonry figure:hover img,
.shots.masonry figure:focus-visible img { transform: scale(1.03); }
.shots.masonry figure:focus-visible { outline: 2px solid #fff; outline-offset: -4px; }
.shots.masonry figcaption {
  position: absolute;
  right: 4%;
  bottom: 3%;      /* fallback for browsers without container query units */
  bottom: 1.68cqw; /* same absolute padding on every tile, regardless of its own aspect ratio */
  max-width: 92%;
  font-size: clamp(10px, 1.05vw, 14px);
  line-height: 1.4;
  letter-spacing: 0.1em;
  text-transform: none;              /* Julius Sans One's lowercase gives the small-caps look */
  text-align: right;
  color: rgba(255, 255, 255, 0.9);
  text-shadow: 0 0 12px rgba(0, 0, 0, 0.6);
  pointer-events: none;
  opacity: 0.5;
  transition: opacity 0.3s ease;
}
.shots.masonry figure:hover figcaption,
.shots.masonry figure:focus-visible figcaption { opacity: 1; }
@media (max-width: 720px) {
  .shots.masonry { column-count: 1; }
  .shots.split { flex-direction: column; }
}

/* ---------- Lightbox (click a masonry image to enlarge) ---------- */
/* A plain overlay (not <dialog>) so the clicked image can FLIP-animate from its
   thumbnail position/size up to full size — see the lightbox script in script.js.
   Kept in sync with script.js: LIGHTBOX_DURATION there must match the transition
   durations below (500ms). */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0);
  transition: background-color 0.5s ease;
}
.lightbox[hidden] { display: none; }
.lightbox.is-open { background: rgba(0, 0, 0, 0.95); }
.lightbox img {
  display: block;
  max-width: min(90vw, 1700px);
  max-height: 90dvh;
  width: auto;
  height: auto;
  object-fit: contain;
  will-change: transform;
}
.lightbox .close {
  position: fixed;
  top: 28px;
  right: 36px;
  border: 0;
  background: none;
  color: #fff;
  font-size: 32px;
  line-height: 1;
  cursor: pointer;
  text-shadow: 0 0 10px rgba(0, 0, 0, 0.6);
  opacity: 0;
  transition: opacity 0.3s ease;
}
.lightbox.is-open .close { opacity: 1; transition-delay: 0.15s; }

/* ---------- Page transitions (cross blur) ---------- */
/* The blur is applied to the page's top-level pieces, not to <body>, so the
   fixed logo/menu/title keep working while the effect runs. The lightbox handles
   its own transition (above), so it's excluded here the same way dialogs are. */
body > :not(dialog):not(script):not(#lightbox) { animation: page-in 0.5s ease-out backwards; }
body.leaving > :not(dialog):not(script):not(#lightbox) { animation: page-out 0.3s ease-in forwards; }
@keyframes page-in  { from { opacity: 0; filter: blur(14px); } to { opacity: 1; filter: blur(0); } }
@keyframes page-out { from { opacity: 1; filter: blur(0); } to { opacity: 0; filter: blur(14px); } }

/* ---------- Immensity (long parallax scene) ---------- */
/* The whole scene is one aspect-ratio-locked box, so every child's top/left/width,
   given as %, is expressed against the exact same 2800×23895 coordinate space the
   assets were authored in — it scales as one unit at any viewport width. */
.im-scene {
  position: relative;
  width: 100%;
  aspect-ratio: 2800 / 23895;
  background: #05070c;
  overflow: hidden;
}
.im-bg, .im-fg { position: absolute; inset: 0; }
.im-bg-layer, .im-item {
  position: absolute;
  display: block;
  height: auto;
}
.im-bg-layer { left: 0; }

/* Fondo 1/2/3 (the space-a group): no reveal, no settle — always visible in their
   resting position. Fondo 2 (the drifting middle layer) instead gets a continuous
   scroll-linked transform in JS, layered on top of this. */
.im-bg-layer:not([data-reveal-tier]) { opacity: 1; }
.im-bg-drift { will-change: transform; }

/* Fondo 1: a continuous one-directional downward drift, looping forever, entirely
   independent of scroll. The mask clips to Fondo 1's original footprint so the loop
   never spills into the space Fondo 2 covers below it. Inside, two stacked copies of
   the same image (one request, reused — not a taller/heavier file) scroll together
   as a pair; the pair moves by exactly one image-height (translateY(-50%) of the
   *pair's* own height) before snapping back to 0. That reset is invisible because
   the second copy is, at that instant, standing exactly where the first began. */
.im-bg-lead-mask { overflow: hidden; }

/* Fondo 3 (bg-space-a3, behind the Bonus & Upgrades section), the space bg
   behind the dune shore (bg-space-b-loop, revealed through the dune's own
   fade-out), and the space bg behind the snow/Radar section (bg-space-c-loop,
   revealed through the snow's own fade-out) all reuse this same continuous
   downward loop as the hero starfield: .im-bg-lead-wrap and its im-lead-loop
   keyframe. The screenshots on top stay static — only the page's own space
   background behind/around them drifts, exactly like the hero: content on
   top stays put, the starfield behind it moves. */
.im-loop-mask { overflow: hidden; }
.im-bg-lead-wrap {
  animation: im-lead-loop 180s linear infinite;
}
.im-bg-lead-wrap img { display: block; width: 100%; height: auto; }
@keyframes im-lead-loop {
  from { transform: translateY(-50%); }
  to   { transform: translateY(0); }
}
/* Three stacked copies instead of two, for a mask taller than one image (the
   space-c loop now runs from under the snow to the end of the page). Moving by
   one third of the stack is still exactly one image height per cycle, so the
   speed matches the two-copy loops. */
.im-bg-lead-wrap.im-loop-x3 { animation-name: im-lead-loop-x3; }
@keyframes im-lead-loop-x3 {
  from { transform: translateY(-33.3333%); }
  to   { transform: translateY(0); }
}

/* Everything else with data-reveal-tier: starts offset below its resting spot and
   eases up into place the first time it scrolls into view — background settles later
   and takes longer than foreground, which is what reads as "background moves slower."
   Once settled, every layer sits exactly where the mockup shows it. */
.im-item, .im-bg-layer[data-reveal-tier] {
  opacity: 0;
  transform: translateY(60px);
  transition: opacity 0.9s ease-out, transform 0.9s ease-out;
}
.im-item[style*="translateX"] { transform: translateY(60px) translateX(-50%); }
.im-item.is-revealed { opacity: 1; transform: translateY(0); }
.im-item.is-revealed[style*="translateX"] { transform: translateY(0) translateX(-50%); }

/* Thruster glow. Each ship (the four in The Ships plus the hero icon) is a wrapper
   holding the flat ship image and, stacked exactly over its exhaust, a second image
   that contains only the glow. That copy is softened and brightened into a bloom and
   screened over the ship, so fading it in and out makes the exhaust flare and spill a
   little light onto the hull; a faster stretch anchored at the nozzle lengthens the
   flame. Periods, offsets and stretch are set per ship inline (--breathe, --flutter,
   --phase, --stretch, --peak) so no two ships pulse together. Blur is in vw because the ships
   scale with the viewport. */
.im-ship > img:first-child { display: block; width: 100%; height: auto; }
.im-glow {
  position: absolute;
  height: auto;
  pointer-events: none;
  transform-origin: 50% 0;
  mix-blend-mode: screen;
  filter: blur(0.28vw) brightness(1.25);
  will-change: opacity;
  animation:
    im-thrust-breathe var(--breathe, 2s) ease-in-out var(--phase, 0s) infinite alternate,
    im-thrust-flutter var(--flutter, 0.65s) ease-in-out var(--phase, 0s) infinite alternate;
}
@keyframes im-thrust-breathe { from { opacity: 0.05; } to { opacity: var(--peak, 1); } }
@keyframes im-thrust-flutter { from { transform: scaleY(1); } to { transform: scaleY(var(--stretch, 1.06)); } }

.im-bg-layer[data-reveal-tier] { transform: translateY(140px); transition: opacity 1.6s ease-out, transform 1.6s ease-out; }
.im-bg-layer[data-reveal-tier].is-revealed { opacity: 1; transform: translateY(0); }

@media (prefers-reduced-motion: reduce) {
  body > :not(dialog):not(script) { animation: none !important; }
  .water { display: none; }
  .mist { animation: none; opacity: 0.45; }
  .tile img, .caption .title, .caption .tagline { transition: none; }
  .tile:hover img, .tile:focus-visible img { transform: none; }
  .shots.masonry img { transition: none; }
  .shots.masonry figure:hover img, .shots.masonry figure:focus-visible img { transform: none; }
  .shots.masonry figcaption { transition: none; }
  .shots .panel-clouds { animation: none; }
  .sketch-reveal { transition: none; opacity: 1; clip-path: inset(0 0 0 0); }
  .icon-pulse { display: none; }
  .steam { display: none; }
  .lightbox { transition: none; }
  .lightbox .close { transition: none; }
  .contact-form, .cf-message, .cf-row, .cf-clear, .cf-send, .cf-back, .contact-intro a { transition: none; }
  .cf-sent, .cf-stack.is-sent .contact-form, .cf-stack.is-sent .cf-sent { transition: none; }
  .lang-switch button { transition: none; }
  .watch-seconds-hand { animation: none; transform: rotate(150deg); }
  .corto-blink { transition: none; }
  .im-item, .im-bg-layer { transition: none; opacity: 1; transform: translateY(0); }
  .im-item[style*="translateX"] { transform: translateX(-50%); }
  .im-bg-lead-wrap { animation: none; transform: translateY(0); }
  .im-glow { animation: none; opacity: 0; }
}
