/* ==========================================================================
   Components — one block per Figma component
   Nav (Counter · Terminal · Info) · EdgeFade · ProjectList · ProjectTile · Info page
   ========================================================================== */

/* Nav -------------------------------------------------------------------- */
/* Figma: Nav (Breakpoint = Desktop / Tablet / Mobile).
   Left fills the width; Right hugs. Everything is top-aligned, so a title
   that wraps keeps its first line level with the counter and Info. */
.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 30;
  display: flex;
  align-items: flex-start;
  gap: var(--gutter);                     /* min space between title and Info */
  padding: var(--nav-pad-top) var(--gutter) var(--nav-pad-bottom);
  font-size: var(--nav-size);
  line-height: var(--nav-leading);
  font-weight: var(--nav-weight);
}
.nav__left {
  flex: 1 1 auto;
  min-width: 0;                           /* lets the terminal shrink and wrap */
  display: flex;
  align-items: flex-start;
  gap: var(--gutter);
}
.nav__right { flex: none; }
.nav__counter,
.terminal { margin: 0; }
/* Boot: counter hidden, so the terminal starts at the far left */
html.is-booting-nav .nav__counter { display: none; }

/* Counter: Mono, so every digit is the same width. The current number is
   sized to the total's digit count and right-aligned, so nothing shifts. */
.nav__counter {
  flex: none;
  display: flex;
  font-family: var(--font-mono);
  white-space: nowrap;
}
.nav__counter [data-counter-current] {
  min-width: var(--counter-digits, 1ch);
  text-align: right;
}

/* Info link: "Information" on desktop/tablet, "Info" on mobile */
.nav__link { display: block; white-space: nowrap; }
/* Hover: the text gets "marked", like the terminal cursor stretched over
   it: a white block behind, the text turns black. Instant, no fade. */
.nav__link:hover,
.nav__link:focus-visible,
.info__contact a:hover,
.info__contact a:focus-visible {
  background: var(--cursor);
  color: var(--bg);
  outline: none;
}
.nav__label-short { display: none; }
@media (max-width: 599px) {
  .nav__label-long  { display: none; }
  .nav__label-short { display: inline; }
}


/* Terminal --------------------------------------------------------------- */
/* Typed project title + block cursor. main.js decides where lines break
   (after the comma, else at a space) and inserts the breaks itself, so the
   text never re-wraps while it is being typed. */
.terminal {
  flex: 1 1 auto;
  min-width: 0;
  white-space: pre;                       /* only our own line breaks */
}
.terminal__cursor {
  display: inline-block;
  width: var(--cursor-w);
  height: var(--cursor-h);
  margin-left: var(--cursor-gap);
  margin-top: var(--cursor-top);
  vertical-align: top;
  background: var(--cursor);
}
/* Empty line: the cursor sits exactly where the first letter will land */
.terminal__text:empty + .terminal__cursor { margin-left: 0; }
/* At rest: hard on/off blink. While typing: solid. */
.terminal:not(.is-typing) .terminal__cursor {
  animation: terminal-blink var(--blink) step-end infinite;
}
@keyframes terminal-blink { 50% { opacity: 0; } }

/* Hidden copy used to measure where a title wraps */
.terminal__measure {
  position: absolute;
  visibility: hidden;
  pointer-events: none;
  white-space: normal;
}
.terminal__measure .seg { white-space: nowrap; }


/* EdgeFade --------------------------------------------------------------- */
/* Progressive blur at the top/bottom edge, built from backdrop-filter bands. */
.edge-fade {
  position: fixed;
  left: 0;
  right: 0;
  z-index: 20;
  height: var(--fade-height);
  pointer-events: none;
}
.edge-fade--top    { top: 0; }
.edge-fade--bottom { bottom: 0; transform: scaleY(-1); }

.edge-fade__blur {
  position: absolute;
  inset: 0;
  -webkit-backdrop-filter: blur(var(--b));
          backdrop-filter: blur(var(--b));
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, transparent var(--to));
          mask-image: linear-gradient(to bottom, #000 0%, transparent var(--to));
}
.edge-fade__blur:nth-child(1) { --b: 4px;  --to: 100%; }
.edge-fade__blur:nth-child(2) { --b: 8px;  --to: 88%;  }
.edge-fade__blur:nth-child(3) { --b: 12px; --to: 74%;  }
.edge-fade__blur:nth-child(4) { --b: 16px; --to: 60%;  }
.edge-fade__blur:nth-child(5) { --b: 20px; --to: 45%;  }
.edge-fade__blur:nth-child(6) { --b: 24px; --to: 30%;  }

[data-blur="smear"] .edge-fade__blur:nth-child(1) {
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, #000 calc(100% - var(--edge-soft)), transparent 100%);
          mask-image: linear-gradient(to bottom, #000 0%, #000 calc(100% - var(--edge-soft)), transparent 100%);
}
[data-blur="smear"] .edge-fade__blur:nth-child(2) { --to: 95%; }
[data-blur="smear"] .edge-fade__blur:nth-child(3) { --to: 80%; }
[data-blur="smear"] .edge-fade__blur:nth-child(4) { --to: 65%; }
[data-blur="smear"] .edge-fade__blur:nth-child(5) { --to: 50%; }
[data-blur="smear"] .edge-fade__blur:nth-child(6) { --to: 35%; }
[data-blur="smear"] .edge-fade__blur:nth-child(1) { -webkit-backdrop-filter: url(#edge-smear-1); backdrop-filter: url(#edge-smear-1); }
[data-blur="smear"] .edge-fade__blur:nth-child(2) { -webkit-backdrop-filter: url(#edge-smear-2); backdrop-filter: url(#edge-smear-2); }
[data-blur="smear"] .edge-fade__blur:nth-child(3) { -webkit-backdrop-filter: url(#edge-smear-3); backdrop-filter: url(#edge-smear-3); }
[data-blur="smear"] .edge-fade__blur:nth-child(4) { -webkit-backdrop-filter: url(#edge-smear-4); backdrop-filter: url(#edge-smear-4); }
[data-blur="smear"] .edge-fade__blur:nth-child(5) { -webkit-backdrop-filter: url(#edge-smear-5); backdrop-filter: url(#edge-smear-5); }
[data-blur="smear"] .edge-fade__blur:nth-child(6) { -webkit-backdrop-filter: url(#edge-smear-6); backdrop-filter: url(#edge-smear-6); }

.edge-fade__tint {
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, color-mix(in srgb, var(--bg) var(--fade-tint), transparent), transparent);
}


/* ProjectList ------------------------------------------------------------ */
/* While the terminal boots: no projects, no scrolling. Once the first title
   is fully typed, the first project fades in, already in the centre. */
html.is-booting { overflow: hidden; }
html.is-booting .project-tile__media img { opacity: 0; }
.project-tile__media img { transition: opacity var(--reveal-fade) var(--reveal-ease); }

/* Image area while booting: filled with ASCII that gets denser as the
   terminal loads ( . : | I # ). The image then fades in on top of it, and
   the ASCII is removed once the fade is done. */
.ascii-load {
  position: absolute;
  inset: 0;
  margin: 0;
  overflow: hidden;
  font-family: var(--font-mono);
  font-size: var(--load-size);
  line-height: var(--load-leading);
  white-space: pre;
  color: var(--text);
}
html:not(.is-booting) .ascii-load {
  opacity: 0;
  transition: opacity 0s linear var(--reveal-fade);
}
.project-tile__media img { position: relative; z-index: 1; }

.project-list { margin: 0; padding: 0; list-style: none; }
.project-list__slot {
  height: calc(100vh + var(--dwell));
  height: calc(100dvh + var(--dwell));   /* the screen as it is right now (phone toolbars in or out) */
}
.project-list__stage {
  position: sticky;
  top: 0;
  display: grid;
  place-items: center;
  height: 100vh;
  height: 100dvh;                        /* so each project centres in what you actually see */
}

/* Native snap (same as east.co): each project snaps to the centre */
html[data-snap="native"] { scroll-snap-type: y mandatory; --dwell: 0vh; }
html[data-snap="native"] .project-list__slot {
  scroll-snap-align: center;
  scroll-snap-stop: always;   /* one swipe = one project, even with a fast flick on a phone */
}

.dwell-css .project-list__slot  { view-timeline: --slot block; }
.dwell-css .project-list__stage {
  animation: dwell-mid linear both;
  animation-timeline: --slot;
  animation-range: cover 0% cover 100%;
}
.dwell-css .project-list__slot:first-child .project-list__stage { animation-name: dwell-first; }
.dwell-css .project-list__slot:last-child  .project-list__stage { animation-name: dwell-last; }


/* ProjectTile ------------------------------------------------------------ */
/* Every image has the same height (--tile-height); its width follows its
   own ratio, set per tile with --ratio (16/9 landscape by default). */
.project-tile { margin: 0; }
.project-tile__media {
  position: relative;
  height: var(--tile-height);
  aspect-ratio: var(--ratio, 16 / 9);
  max-width: calc(100vw - 2 * var(--gutter));
  overflow: hidden;
  border-radius: var(--media-radius);
}

/* Portraits: a touch taller than landscapes (--portrait-scale) */
.project-tile__media--portrait { height: var(--portrait-height); }

/* Mobile: one column, every image full width */
@media (max-width: 599px) {
  .project-tile__media,
  .project-tile__media--portrait {
    /* full width; on a short phone it shrinks so the whole image still fits */
    height: auto;
    aspect-ratio: var(--ratio-mobile, var(--ratio, 16 / 9));
    width: min(100vw - 2 * var(--gutter), (100svh - 128px) * (var(--ratio-mobile, var(--ratio, 16 / 9))));
  }
}

/* One slide, two crops: the desktop file on desktop/tablet, the mobile file
   on phones (its own ratio via --ratio-mobile). Only the visible one plays. */
.media-mobile { display: none !important; }
@media (max-width: 599px) {
  .media-desktop { display: none !important; }
  .media-mobile  { display: block !important; }
}

/* Zoom: click an image and it grows: one the same way round as the screen
   fills it, the other way round fits inside (see main.js). Click, Esc or scroll to zoom out.
   The edge blur fades away while zoomed. */
.project-tile__media {
  cursor: zoom-in;
  transition: transform var(--zoom-dur) var(--zoom-ease);
}
.project-tile__media.is-zoomed {
  position: relative;
  z-index: 25;                       /* above the edge blur, below the nav */
  cursor: zoom-out;
}
html.is-zoomed { overflow: hidden; }
.edge-fade { transition: opacity var(--zoom-dur) var(--zoom-ease); }
html.is-zoomed .edge-fade { opacity: 0; }
/* Zoomed: the nav inverts against the image, so it stays readable on light
   images (white text over white turns black) */
html.is-zoomed .nav { mix-blend-mode: difference; }

/* Grid overlay for checking alignment: press G */
.grid-overlay {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: none;
  grid-template-columns: repeat(var(--cols), 1fr);
  gap: var(--gutter);
  padding: 0 var(--gutter);
  pointer-events: none;
}
.grid-overlay span { background: rgba(255, 0, 80, 0.12); }
html.show-grid .grid-overlay { display: grid; }
@media (max-width: 1023px) { .grid-overlay span:nth-child(n + 13) { display: none; } }
@media (max-width: 599px)  { .grid-overlay span:nth-child(n + 2)  { display: none; } }
.project-tile__media img,
.project-tile__media video { display: block; width: 100%; height: 100%; object-fit: cover; }
.project-tile__caption {           /* title lives in the nav; kept for screen readers */
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}


/* End slide (ASCII art) -------------------------------------------------- */
/* Figma: Areal Mono 13/18. Scales down on narrow screens so all 67 columns
   always fit (one column = 0.61em in Areal Mono). */
.project-tile--end {
  --ascii-fs: min(var(--ascii-size), calc((100vw - 2 * var(--gutter)) / (var(--ascii-cols) * 0.61)));
  display: flex;
  flex-direction: column;
  align-items: center;
}
.ascii {
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--ascii-fs);
  line-height: calc(18 / 13);   /* 18px at 13px, keeps the ratio when it scales */
  white-space: pre;
  color: var(--text);
}
.project-tile--end { width: auto; }
.ascii__line { display: block; }
/* The end slide sits above the edge blur, so the art stays crisp while it
   scrolls in and out; every other slide is blurred at the edges as usual */
.project-list__slot--end .project-list__stage { z-index: 25; }

/* "Back to top": two empty art lines under the castle, centred. Always
   visible; the art prints above it. */
.end__top {
  margin-top: calc(var(--ascii-fs) * 18 / 13 * 2);
  font-size: var(--nav-size);
  line-height: var(--nav-leading);
}


/* Information page ------------------------------------------------------- */
.info {
  padding: calc(var(--nav-pad-top) + var(--nav-leading) + var(--nav-pad-bottom)) var(--gutter) var(--gutter);
  max-width: calc(var(--info-width) + 2 * var(--gutter));
}
.info__text {
  margin: 0 0 var(--nav-leading);
  font-size: var(--nav-size);
  line-height: var(--nav-leading);
}
.info__contact { list-style: none; padding: 0; }



/* Role label --------------------------------------------------------------- */
/* What Filip did on a project (e.g. "Retouch"), typed out next to the cursor
   while it is over an image with data-roles. Plain text, #fafafa with blend
   mode difference, so it reads on light and dark images. Semi Mono 13/17. */
.role-label {
  position: fixed;
  left: 0;
  top: 0;
  z-index: 50;
  pointer-events: none;
  mix-blend-mode: difference;
}
.role-label__text {
  position: absolute;
  /* The mouse position is the arrow's tip; the text sits just right of the
     arrow, its centre 7px below the tip (tuned by eye) */
  left: 18px;
  top: calc(7px - var(--nav-leading) / 2);
  white-space: nowrap;
  color: #fafafa;
  font-family: var(--font-semi-mono);
  font-size: var(--nav-size);
  line-height: var(--nav-leading);
  font-weight: var(--nav-weight);
}
@media (hover: none) { .role-label { display: none; } }   /* touch screens: no cursor */


/* Touch screens (phones, tablets) ------------------------------------------ */
/* iOS Safari often drops the backdrop blur on these layered, masked bands,
   so phones also get a black fade at the edges: the image fades into the
   page there whether or not the blur is drawn. */
@media (hover: none) {
  :root { --fade-tint: 85%; }
}
