/* ==========================================================================
   Florian Juri
   Media is never cropped, so it letterboxes itself. The black around it is
   where the interface lives — no gradients anywhere on the site.
   ========================================================================== */

:root {
  --bg:      #0a0a0a;
  --fg:      #f4f2ef;
  --dim:     #7d7873;
  --line:    rgba(244, 242, 239, 0.11);

  /* Two accents, two jobs.
     Neon lime-yellow marks state — what's open, what's playing, where you are.
     Electric violet is the machine talking — read-outs, counts, labels.
     Lime specifically because it barely occurs in the renders (which run warm:
     oranges, creams, pinks), so it always reads as interface, never as work. */
  --accent:   #eaff00;
  --accent-2: #a99cff;

  /* How the accent is *used*, which has to differ by ground. On black, neon
     lime is ink. On paper it can't be — 1.1:1 — so it becomes what neon
     actually is off-screen: a highlighter, with the ink laid over it. Same
     colour, same meaning, opposite application. */
  --live-fg:   var(--accent);
  --live-bg:   transparent;
  --live-icon: var(--accent);

  --sans: "Helvetica Neue", Helvetica, -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;

  --pad:   clamp(16px, 2.2vw, 30px);
  --micro: 10px;
  --barh:  42px;   /* sticky bar: 13px padding x2 + 15px mark + hairline */
  --row:   clamp(11.5px, 0.9vw, 13px);
  --body:  clamp(13px, 1vw, 15px);

  /* Fixed regardless of theme: this one sits on the artwork, not on the page,
     and the frames skew high-key either way. */
  --frame-num: #0a0a0a;
  --scrim:     rgba(10, 10, 10, 0.92);

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* Light theme. The palette inverts, but the lime does not change: it stops
   being ink and becomes a fill, which is the only way neon reads on paper.
   The violet does have to darken — it's ink in both themes. */
:root[data-theme="light"] {
  --bg:       #f2f0ec;
  --fg:       #111110;
  --dim:      #5f5b56;
  --line:     rgba(17, 17, 16, 0.16);
  --accent:   #eaff00;          /* unchanged — it's a fill here, not ink */
  --accent-2: #4b3ad1;
  --scrim:    rgba(242, 240, 236, 0.94);

  --live-fg:   #111110;
  --live-bg:   var(--accent);
  --live-icon: #111110;
}
/* The wordmark is black artwork, inverted to white for the dark ground. */
:root[data-theme="light"] .mark { filter: none; }

*, *::before, *::after { box-sizing: border-box; }
/* The scrollbar's lane is reserved even when nothing scrolls, so switching
   between a tall view and a short one (Flow / Index) never shifts the chrome,
   and the lightbox's scroll lock doesn't either. Zero on overlay-scrollbar
   platforms, where the shift never existed. */
html { -webkit-text-size-adjust: 100%; scrollbar-gutter: stable; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--sans);
  font-size: var(--body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

img, video { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
h1, h2, h3, p, figure, ul, dl, dd { margin: 0; padding: 0; }
ul { list-style: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; padding: 0; }
::selection { background: var(--fg); color: var(--bg); }

.micro {
  font-family: var(--mono);
  font-size: var(--micro);
  line-height: 1;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--dim);
  font-weight: 400;
}
a.micro { transition: color 0.25s var(--ease); }
a.micro:hover { color: var(--fg); }

.mark {
  display: block;
  height: 15px;
  width: auto;
  filter: invert(1);
  transition: opacity 0.25s var(--ease);
}
.mark:hover { opacity: 0.55; }

/* --------------------------------------------------------------------------
   Top bar
   -------------------------------------------------------------------------- */
.top {
  position: sticky;
  top: 0;
  z-index: 60;
  height: var(--barh);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 22px;
  padding: 0 var(--pad);
  background: var(--bg);
}
.top__nav { display: flex; align-items: center; gap: 18px; }

/* Theme switch. Half-filled circle — the usual contrast glyph, drawn in the
   same 14px box as the gallery-view icons. */
.theme {
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  color: var(--dim);
  transition: color 0.25s var(--ease), transform 0.4s var(--ease);
}
.theme svg { width: 13px; height: 13px; }
.theme:hover { color: var(--fg); }
:root[data-theme="light"] .theme { transform: rotate(180deg); }
.top__nav a[aria-current="page"] {
  color: var(--live-fg);
  background: var(--live-bg);
  box-shadow: 0 0 0 3px var(--live-bg);   /* pads the mark without moving it */
}

.index__head {
  display: flex;
  flex-wrap: wrap;              /* safety only — name and toggle fit a phone */
  justify-content: space-between;
  align-items: center;
  gap: 10px 16px;
  padding: clamp(14px, 2.4vh, 22px) var(--pad) 10px;
}
/* The view toggle — the gallery size toggle's icon grammar, sized to the
   theme button's 20px box so its glyph column lines up under it exactly. */
.mode { display: flex; gap: 12px; }
.mode__btn {
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  opacity: 0.4;
  transition: opacity 0.25s var(--ease);
}
.mode__btn svg { width: 13px; height: 13px; fill: var(--fg); }
.mode__btn:hover { opacity: 0.75; }
.mode__btn[aria-pressed="true"] { opacity: 1; }
.mode__btn[aria-pressed="true"] svg { fill: var(--live-icon); }

/* --------------------------------------------------------------------------
   Home — two views, one page. The wall (freeflow cascade) is the default
   with JS; the list (spill) is one toggle away. The view is a data-mode
   attribute on <html>, set before first paint; without JS there is no
   attribute and the list is the page, which works unscripted.
   -------------------------------------------------------------------------- */
.wall { display: none; padding: 24px var(--pad) 60px; }
:root[data-mode="flow"] .wall { display: block; }
:root[data-mode="flow"] .spill { display: none; }

/* Freeform cascade on the grammar of the old flojuri.com front page: two
   loose columns, four width tiers, positions from site.js on a fixed cycle.
   Tiles stay 16:9; frames letterbox inside, and the cursor blends through
   each project's opening shots with a fast crossfade. */
.wall__sheet { position: relative; }
.wall__item {
  position: absolute;
  aspect-ratio: 16 / 9;
  opacity: 1;
  transition: opacity 0.25s var(--ease);
}
/* Dim keyed to a hovered TILE, not the sheet — the cascade is mostly gap,
   and parking the cursor between projects should read as rest. */
.wall__sheet:has(.wall__item:hover) .wall__item { opacity: 0.45; }
.wall__sheet:has(.wall__item:hover) .wall__item:hover { opacity: 1; }
.wall__item img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
  opacity: 0;
  transition: opacity 0.25s var(--ease);
}
.wall__item img.is-on { opacity: 1; }
.wall__tag {
  position: absolute;
  left: 0;
  bottom: 0;
  background: var(--bg);
  color: var(--fg);
  padding: 6px 8px 5px;
  font-family: var(--mono);
  font-size: var(--micro);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  opacity: 0;
  transition: opacity 0.2s var(--ease);
}
.wall__tag i { font-style: normal; color: var(--dim); margin-left: 8px; }
.wall__tag em { font-style: normal; color: var(--accent-2); margin-left: 10px; }
.wall__item:hover .wall__tag { opacity: 1; }

/* Row typography — shared by the home list and the project-page headers. */
.row__num {
  font-family: var(--mono);
  font-size: var(--micro);
  letter-spacing: 0.08em;
  color: var(--dim);
}
.row__client {
  font-size: var(--row);
  font-weight: 500;
  letter-spacing: 0.045em;
  text-transform: uppercase;
  white-space: nowrap;
}
.row__title {
  font-size: var(--row);
  color: var(--dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The list: quiet rows that paint that project's first frames into the
   field beside them. */
.spill { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); }
.spill__list { padding: clamp(10px, 2vh, 24px) var(--pad) clamp(30px, 7vh, 80px); }
.spill__row {
  display: grid;
  grid-template-columns: 30px minmax(0, auto) minmax(0, 1fr);
  gap: 14px;
  align-items: baseline;
  padding: clamp(10px, 1.5vh, 15px) 0;
  border-bottom: 1px solid var(--line);
  opacity: 0.62;
  transition: opacity 0.25s var(--ease);
}
.spill__row:first-child { border-top: 1px solid var(--line); }
.spill__list:hover .spill__row { opacity: 0.3; }
.spill__list:hover .spill__row:hover { opacity: 1; }
.spill__field { position: relative; }
.spill__img {
  position: absolute;
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.35s var(--ease), transform 0.35s var(--ease);
}
.spill__img.is-in { opacity: 1; transform: none; }
.spill__img img { width: 100%; height: auto; display: block; }

.pane { padding: calc(var(--barh) + var(--pad)) var(--pad) clamp(30px, 6vh, 70px); }
.item__head { padding-bottom: clamp(16px, 3vh, 30px); }

/* Hero: the full frame, uncropped, letterboxed by its own aspect. Sized to
   what's left of the screen once the bar and the row above it are taken out,
   so an opened project frames in one view. */
.hero {
  position: relative;
  height: min(calc(100svh - var(--barh) - 118px), 62vw);
  margin: 0 var(--pad) 14px;
}
.hero__media {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
}

.cap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 18px;
  padding: 0 var(--pad) clamp(24px, 4vh, 44px);
}
.cap__client {
  font-size: var(--row);
  font-weight: 500;
  letter-spacing: 0.045em;
  text-transform: uppercase;
}
.cap__title { font-size: var(--row); color: var(--dim); }
.cap__spacer { flex: 1 1 auto; }
.sound { letter-spacing: 0.16em; }
.sound:hover { color: var(--fg); }

.p-body {
  display: grid;
  grid-template-columns: minmax(0, 7fr) minmax(0, 4fr);
  gap: clamp(26px, 4vw, 70px);
  padding: clamp(34px, 6vh, 70px) var(--pad);
}
.p-text p { max-width: 58ch; }
.p-text p + p { margin-top: 1.1em; }

.p-credits h3 { margin-bottom: 12px; color: var(--accent-2); }
.cr {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.5fr);
  gap: 14px;
  padding: 7px 0;
  border-top: 1px solid var(--line);
  font-size: 12px;
  line-height: 1.45;
}
.cr dt { color: var(--dim); }

/* --------------------------------------------------------------------------
   Gallery — two views, one DOM. Nothing is cropped in either.
   -------------------------------------------------------------------------- */
.gallery { padding: clamp(24px, 4vh, 48px) var(--pad) clamp(34px, 6vh, 70px); }
/* Full width on the standalone pages, where the pane is already inset. */
.pane .gallery { margin: 0 calc(var(--pad) * -1); }
.gallery > .micro { color: var(--accent-2); }
.gal figure { margin: 0; }

/* Frame numbers, set into the bottom-left corner of the frame itself. CSS
   counters, so there's nothing to keep in sync — reorder or add frames and the
   numbering follows. Bare black type, no plate behind it — these frames skew
   high-key, so black reads on more of them than white would. Hidden in the
   small view, where 83 of them at thumbnail size would be noise. */
.gal { counter-reset: frame; }
.gal figure { counter-increment: frame; position: relative; }
.gal[data-view="lg"] figure::after {
  content: counter(frame, decimal-leading-zero);
  position: absolute;
  left: 11px;
  bottom: 10px;
  color: var(--frame-num);
  font-family: var(--mono);
  font-size: var(--micro);
  line-height: 1;
  letter-spacing: 0.16em;
}
/* A square at full width would stand taller than the screen. Rather than
   letterbox it inside a full-width box — which would leave the number floating
   away from the image's edge — the *figure* is capped by width, derived from
   the height limit and the frame's own aspect. The media then fills its figure
   exactly, so the frame's edges are the media's edges. */
.gal figure { max-width: calc((100svh - var(--barh) - 60px) * var(--ar, 1.5)); }
.gal img, .gal video { width: 100%; height: auto; }

/* Large: an editorial rhythm rather than an even grid. Three widths only —
   full bleed, two thirds centred, and halves that pair up — cycling on an
   eight-frame cadence, with generous air between blocks. Varied, but a system:
   the sizes repeat, so it reads as pacing instead of noise. */
.gal[data-view="lg"] {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: flex-start;
  gap: clamp(38px, 8vh, 110px) var(--pad);
}
/* Sizes mirror flojuri.com: each frame keeps the width tier it had on the
   original page. Fulls take the line; halves pair two-up; thirds run
   three-up. Flex wraps and centres every line — including a partial one, so
   a frame with no partner sits centred instead of stranded left, with no
   special-casing needed. */
.gal[data-view="lg"] figure { width: 100%; }
.gal[data-view="lg"] .g-mid { width: 66%; }
.gal[data-view="lg"] .g-half { width: calc(50% - var(--pad) / 2); }
.gal[data-view="lg"] .g-third { width: calc(33.33% - var(--pad) * 2 / 3); }

/* Small: freeflow masonry. Frames take different widths and keep their own
   height, so it stays loose rather than gridded — but every gutter is the
   same, so it still reads as ordered. Row spans come from site.js. */
.gal[data-view="sm"] {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 0 10px;
  /* 1px rows so the computed vertical gutter lands exactly on the column
     gutter instead of rounding up to the next row unit. */
  grid-auto-rows: 1px;
  grid-auto-flow: row dense;
  align-items: start;
}
.gal[data-view="sm"] figure { grid-column: span 3; }
.gal[data-view="sm"] .f-s { grid-column: span 2; }
.gal[data-view="sm"] .f-m { grid-column: span 3; }
.gal[data-view="sm"] .f-l { grid-column: span 4; }

/* --------------------------------------------------------------------------
   Lightbox — the frame centred and uncropped, the gallery dimmed behind it.
   Flat scrim, not a gradient: the work stays faintly readable underneath.
   -------------------------------------------------------------------------- */
.gal figure { cursor: zoom-in; }

.lb {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
  padding: calc(var(--pad) * 1.5) var(--pad) calc(var(--pad) * 2.6);
  background: var(--scrim);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.28s var(--ease), visibility 0.28s var(--ease);
}
.lb.is-open { opacity: 1; visibility: visible; }
.lb-open { overflow: hidden; }

.lb__stage { display: grid; place-items: center; min-width: 0; min-height: 0; }
/* Capped against the viewport, not the parent: a percentage here resolves
   against a content-sized grid track, which the media itself defines. */
.lb__media {
  max-width: calc(100vw - var(--pad) * 2);
  max-height: calc(100svh - var(--pad) * 4.2);
  width: auto;
  height: auto;
  object-fit: contain;
  cursor: zoom-out;
}

.lb__bar {
  position: absolute;
  left: var(--pad);
  right: var(--pad);
  bottom: calc(var(--pad) - 2px);
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
}
[data-lb-read] { color: var(--accent-2); }
.lb__acts { display: flex; gap: 18px; }
.lb__acts button { letter-spacing: 0.16em; transition: color 0.25s var(--ease); }
.lb__acts button:hover { color: var(--fg); }

/* View toggle */
.views { display: flex; gap: 10px; align-items: center; }
.views__btn {
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  opacity: 0.4;
  transition: opacity 0.25s var(--ease);
}
.views__btn svg { width: 13px; height: 13px; fill: var(--fg); }
.views__btn:hover { opacity: 0.75; }
.views__btn[aria-pressed="true"] { opacity: 1; }
.views__btn[aria-pressed="true"] svg { fill: var(--live-icon); }

.foot {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 26px;
  justify-content: space-between;
  align-items: baseline;
  padding: clamp(26px, 5vh, 54px) var(--pad) clamp(20px, 3vh, 32px);
  border-top: 1px solid var(--line);
}
/* The platforms are the footer's headline… */
.foot__links { display: flex; gap: 18px; }
.foot__links .micro { font-size: var(--row); color: var(--fg); }
.foot__links .micro:hover { color: var(--accent-2); }
/* …and the legal layer is fine print. */
.foot__fine { opacity: 0.55; transition: opacity 0.25s var(--ease); }
.foot__fine:hover { opacity: 1; }
.foot__fine a { transition: color 0.25s var(--ease); }
.foot__fine a:hover { color: var(--fg); }

/* Info */
.info { padding-top: clamp(30px, 7vh, 80px); }
.info__lead {
  font-size: clamp(17px, 1.85vw, 25px);
  font-weight: 400;
  line-height: 1.22;
  letter-spacing: -0.014em;
  max-width: 22ch;
}
.info__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(26px, 4vw, 70px);
  margin-top: clamp(32px, 7vh, 80px);
}
.info__body p { max-width: 56ch; }
.info__body p + p { margin-top: 1.1em; }
.fact {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  gap: 16px;
  padding: 10px 0;
  border-top: 1px solid var(--line);
  font-size: 12px;
  line-height: 1.45;
}
.fact dt { color: var(--dim); }
.fact a:hover { color: var(--accent-2); }

/* Imprint — the quietest page on the site. */
.imprint h1, .imprint h2 { color: var(--accent-2); margin-bottom: 14px; }
.imprint h2 { margin-top: clamp(26px, 5vh, 44px); }
.imprint__block p { max-width: 62ch; font-size: 12px; line-height: 1.6; }
.imprint__block p + p { margin-top: 0.5em; }

/* --------------------------------------------------------------------------
   Responsive
   -------------------------------------------------------------------------- */
@media (max-width: 900px) {
  .top { gap: 14px; }
  .spill { grid-template-columns: minmax(0, 1fr); }
  .spill__field { display: none; }
  .p-body, .info__grid { grid-template-columns: minmax(0, 1fr); }
  .gal[data-view="lg"] { row-gap: clamp(24px, 5vh, 50px); }
  .gal[data-view="lg"] figure { width: 100% !important; }
  .gal[data-view="sm"] { grid-template-columns: repeat(6, 1fr); gap: 0 6px; }
  .hero { height: min(calc(100svh - var(--barh) - 110px), 84vw); }
  .foot { flex-direction: column; align-items: flex-start; }
}

@media (hover: none) {
  /* No cursor, so nothing to dim against. */
  .spill__list:hover .spill__row { opacity: 0.62; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
  }
}
