/* loader.css, 2026-10-10 (v8). Base styles for the intro (loader.js drives the
   motion with WAAPI): a dot fills, stretches to a bar, the bar emerges as a row
   of project frames, and the row flows into the real centred filmstrip column.
   It ends ON the live view, which is revealed instantly underneath and the loader
   is hard-cut, so there is no fade/cap at the end. Reduced-motion safe. */
.fo-loader {
  position: fixed; inset: 0; z-index: 300;
  background: var(--fo-bg);
  overflow: hidden;
}
.fo-ld { position: absolute; top: 50%; left: 50%; width: 0; height: 0; transform-origin: center; }

.fo-ld-frame {
  position: absolute; left: 0; top: 0; width: 14px; height: 14px;
  transform: translate(-50%, -50%);
  border: 1px solid var(--fo-ink); background: transparent;
}

.fo-ld-tile {
  position: absolute; left: 0; top: 0; transform: translate(-50%, -50%);
  overflow: hidden; opacity: 0; background: var(--fo-line);
}
.fo-ld-tile img { width: 100%; height: 100%; object-fit: cover; display: block; filter: grayscale(1) contrast(1.02); }

.fo-loader.done { display: none; }

/* The filmstrip images stay live under the loader, so the hard-cut lands on the
   real slider with no fade. Only the chrome/text is held back during the intro
   and then fades in smoothly at the end. */
.fo-anim .fo-nav, .fo-anim .fo-index, .fo-anim .fo-marker, .fo-anim .fo-contact,
.fo-anim .fo-list { opacity: 0; }
body.fo-text-in .fo-nav, body.fo-text-in .fo-index, body.fo-text-in .fo-marker,
body.fo-text-in .fo-contact, body.fo-text-in .fo-list { opacity: 1; transition: opacity .9s ease; }
@media (prefers-reduced-motion: reduce) {
  .fo-anim .fo-nav, .fo-anim .fo-index, .fo-anim .fo-marker, .fo-anim .fo-contact,
  .fo-anim .fo-list { opacity: 1; }
}
