/* Project page nav — Figma component set `Device=Desktop, Page=Project`.

   Two states, and the page swaps between them on scroll:

   - `Subcategory=Default` (151:21791), 112 tall. Name left, the OTHER projects'
     thumbnails centred, About/Approach/Contact right.
   - `Subcategory=On scroll` (174:27449), 80 tall. The thumbnails give way to
     this project's own section anchors (Nav-center, 174:27489).

   So the bar also loses 32px of height on the swap, because the 64px cards are
   replaced by 32px buttons inside the same 24px padding. Both changes are
   driven by one class on the element, added by project-nav.js. */

:root {
  /* Written as its parts so it can never drift from them: the bar's padding,
     the cards, the bar's padding again. The page's top offset reads this, so
     24 above the cards and 24 below them falls out of one number. */
  --project-nav-h: calc(var(--space-24) + 80px + var(--space-24));
  /* What the bar becomes once you scroll. An anchor has to clear exactly this,
     so the two are written from one value rather than from a number repeated in
     two places that then stop agreeing. */
  --project-nav-scrolled: 80px;
}

.project-nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 10;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  padding: var(--space-24);
  max-width: 1600px;
  margin: 0 auto;
  /* 128 with the cards, 80 with the anchors — the bar animates between them.
     128 is 24 + the cards' 80 + 24. The page reads --project-nav-h for its own
     top offset, so the two cannot drift apart when the cards change size. */
  height: var(--project-nav-h);
  /* The two centres slide through each other, so the bar clips them
     VERTICALLY. Sideways it must not: the rails run edge to edge and carry
     their own inset, and clipping x here would cut the very cards that are
     supposed to scroll past. `clip` allows the two to differ where `hidden`
     does not. */
  overflow-x: visible;
  overflow-y: clip;
  transition: height 380ms cubic-bezier(0.16, 1, 0.3, 1);
}

.project-nav.is-scrolled { height: 80px; }

/* The buttons themselves are the shared Button/Nav-item, so this file styles
   none of them — the page loads nav.css for that, exactly as every other page
   does. Only the geometry differs here. */

/* The two side blocks are 240 wide in both states, which is what keeps the
   centre optically centred whatever it contains. */
.project-nav .nav-left,
.project-nav .nav-right { width: 240px; flex: none; }
.project-nav .nav-right { display: flex; gap: var(--space-6); justify-content: flex-end; }

/* nav.css stretches the name button to fill Nav-left, which is right on the
   other pages where that block is 120 wide. Here it is 240 and the button is
   still 120 (151:21793), so the stretch has to be undone. */
.project-nav .nav-left .nav-item { width: 120px; }

/* ── The centre, in both states ─────────────────────────────────────────── */

.project-nav-center {
  display: flex;
  align-items: center;
  /* Both are absolute and share the same top, so the bar's height is its own
     business and neither reflows the other as it moves. 24 is the nav padding:
     the 64-tall cards make 112, the 32-tall anchors make 80. */
  position: absolute;
  top: var(--space-24);
  left: 50%;
  transition: transform 420ms cubic-bezier(0.16, 1, 0.3, 1),
              opacity 260ms ease;
}

.project-nav-cards { gap: var(--space-24); }
.project-nav-anchors { gap: 6px; }

/* The whole effect comes from where each one rests when hidden: the anchors
   wait ABOVE the bar, the cards wait BELOW it. Scrolling down then moves both
   downward — anchors in, cards out — and scrolling up moves both upward. No
   direction flag anywhere; it falls out of the two resting positions. */
.project-nav-cards   { transform: translate(-50%, 0);     opacity: 1; }
.project-nav-anchors { transform: translate(-50%, -100%); opacity: 0; pointer-events: none; }

.project-nav.is-scrolled .project-nav-cards   { transform: translate(-50%, 100%); opacity: 0; pointer-events: none; }
.project-nav.is-scrolled .project-nav-anchors { transform: translate(-50%, 0);    opacity: 1; pointer-events: auto; }

/* ── Nav-projects-cards (151:21883): the other projects ─────────────────── */

/* 12 between cards, the same value as the project body's own side gutters —
   one spacing decision showing up in two places rather than two decisions. */
/* Le groupe épouse ses cartes, il ne leur impose pas de hauteur.

   Il était tenu à 80px. Sur téléphone les cartes passent à 104 : elles
   débordaient de 24, et comme le rail porte `overflow-x: auto` — ce qui force
   le navigateur à mettre `overflow-y` à `auto` aussi, les deux valeurs ne
   pouvant pas rester dépareillées — le débordement était rogné. La carte
   mesurait bien 104 pour qui l'interroge, mais on n'en voyait que 80 : un
   139x80, soit le rectangle plat au lieu du 4/3.

   C'est pour ça que le bug a survécu à trois corrections. On vérifiait la
   taille de la carte, qui était juste, au lieu de ce qui en restait à l'écran.

   Plus de hauteur écrite ici : le groupe fait celle de ses cartes, quel que
   soit le palier, et il n'y a plus rien à tenir en accord. La hauteur fixe
   servait à faire grandir une carte autour de la ligne médiane au survol —
   ce survol n'existe plus. */
.nav-projects-group {
  display: flex;
  gap: var(--space-project-gap);
  align-items: center;
}

.nav-project {
  display: block;
  border-radius: var(--radius-12);
  background: var(--primary-black);
  overflow: hidden;
  flex: none;
  position: relative;
}

/* 80 tall rather than the Figma's 64, on request. The big one takes its width
   from the shared card ratio, written out so it can be animated: `auto` cannot
   be transitioned, and this hover moves exactly that. 106.667 is 80 x 4/3, the
   same shape as the home card to the pixel — which the flight between them
   needs. The small one is square. */
/* Each card's size comes from one height, so growing it is one number and the
   shape cannot drift. 4/3 for the big one — the home card's shape to the pixel,
   which the flight between them needs — and square for the small. */
/* Both dimensions written out, at every size, with nothing in between.

   They used to come from one custom property: a height of `var(--card-h)` and
   a width calculated from it. Any single point where that failed to resolve
   left the card with no height at all, and a block with no height takes it
   from the image inside — so the card landed on whatever proportion that file
   happened to have. A 212x126 thumbnail turned a 138x104 card into a 138x83
   one, and re-exporting the thumbnails hid it rather than fixing it.

   106.67 is 80 x 4/3 and 138.67 is 104 x 4/3: the same shared ratio the home
   card uses, kept exact so the card does not reshape in flight, but resolved
   here instead of at paint time. There is nothing left to fail. */
/* A card that leads somewhere says so under the pointer; one that does not
   stays an image. Only the projects with a page are links in the markup, so
   this needs no class of its own. */
a.nav-project { cursor: pointer; }

.nav-project--big   { width: 106.67px; height: 80px; }
.nav-project--small { width: 80px;     height: 80px; }

/* Hover: the card grows by a fifth, in both dimensions, keeping its shape.
   Measured off their row: the box goes from 48x124 to about 58x150.

   Real size and not a scale, because the gap between cards has to stay exactly
   what it is. A scaled card keeps its layout box and simply covers its
   neighbours, which eats the 12px between them and is what looked wrong. A
   card that actually grows pushes them apart instead, and since the row is
   centred by a transform they part evenly on both sides while every gap in it
   stays 12.

   It still grows over the page below — the row is the only thing whose spacing
   is being protected here. */


/* Only where motion is welcome. Cancelling it afterwards meant writing the
   resting height a second time, in a rule that then had to be kept in step
   with every breakpoint — and was not: it still said 80 after the phone's
   cards grew to 104, so reduced motion quietly shrank them. Not applying a
   thing needs no undoing. */
/* No size change on hover, and that is a fix rather than a simplification.

   These cards really grew — width and height, so the gap between them held —
   which widened the row. The row is centred by a transform, so widening it
   moved every card sideways, including the one under the pointer. It slid out
   from under the cursor, lost the hover, shrank back, regained it: a flicker
   that ate the first click and sometimes the second.

   A `scale` would not move anything, but it covers the neighbours and eats the
   gap, which is exactly what Flora rejected. So the answer is neither: these
   are navigation, and the image zoom below already answers the pointer without
   touching a single box. */

.nav-project img { width: 100%; height: 100%; object-fit: cover; }

/* Navproject-overlay (151:21716): the eye badge marking the current project */
.nav-project--current::after {
  content: "";
  position: absolute;
  inset: 0;
  /* 20 x 13.31 is the icon's own size (Union, 151:21591). It was being drawn at
     20 x 20, and the file carries preserveAspectRatio="none", so it stretched
     rather than letterboxed — that is what read as a deformed thumbnail. */
  background: var(--primary-black) url("../icon-eye.svg") center / 20px 13.31px no-repeat;
  opacity: 0.8;
}

.nav-sep {
  width: 1px;
  /* 55 against the old 64 cards, so 69 against 80 */
  height: 69px;
  flex: none;
  background: var(--glassy-dark-400);
}

/* ── Nav-center (174:27489): this project's section anchors ─────────────── */

.nav-anchor {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-6) var(--space-12);
  border-radius: var(--radius-6);
  /* The gauge itself lives in nav.css, shared with every other button in the
     bar. These only say which two colours it runs between — and they are the
     component's own two, Default at 0.4 and Active at 0.6, each kept exact
     rather than approximated by one painted over the other. */
  --gauge-off: var(--glassy-black-400);
  --gauge-on: var(--glassy-black-300);
  -webkit-backdrop-filter: blur(var(--blur-16));
  backdrop-filter: blur(var(--blur-16));
  font-family: var(--font-body);
  font-weight: var(--weight-book);
  font-size: var(--body-14-size);
  line-height: var(--body-14-line);
  /* full white, not the component's 80%: at 80 over a translucent black it
     read grey and was hard to make out. Default and Active now share the
     label colour, and the filled background is what separates them. */
  color: var(--primary-white);
  white-space: nowrap;
  flex: none;
  /* Same timing as every other button in the bar, for the same reason: it is
     the same gesture. */
  transition: background-size 320ms cubic-bezier(0.16, 1, 0.3, 1),
              color 200ms ease,
              border-radius 250ms cubic-bezier(0.45, 0, 0.55, 1);
}

/* Hover runs the gauge, so the button previews where a click lands, and rounds
   off with it exactly as About, Approach and Contact do — 6 to 12, the same
   move on the same timing. These are buttons in the same bar and there is no
   reason for one row to answer the pointer differently from the other.

   Unlike those, the active one still rounds: Active here says where you are in
   the page, not where you cannot go, so it has no quarrel with hover. */
.nav-anchor:hover,
.nav-anchor:focus-visible {
  background-size: 100% 100%, 0% 100%;
  border-radius: var(--radius-12);
}

/* property1=Active (174:27440): the gauge full */
.nav-anchor.is-active { background-size: 100% 100%, 0% 100%; }

@media (prefers-reduced-motion: reduce) {
  /* everything still swaps, it just arrives at once */
  .project-nav,
  .project-nav-center,
  .nav-anchor { transition: none; }
}

/* ── Between a phone and a wide screen ──────────────────────────────────

   The centre is absolutely positioned and centred on the bar, so it overlaps
   the side blocks the moment the screen is narrower than 240 + itself + 240.
   The cards need 1045 to clear them and the anchors 1132 — which means the bar
   was broken on every tablet, and on any desktop window under about 1100.

   Below that it stacks, the way the mobile component does. The arrangement is
   the component's; only the sizes stay desktop's, since there is room for them
   here. 184 = 24 + a 32-tall row of buttons + 24 + 80 of cards + 24. */

@media (max-width: 1140px) {
  :root {
    --project-nav-h: calc(var(--space-24) + 32px + var(--space-24) + 80px + var(--space-24));
    --project-nav-scrolled: calc(var(--space-24) + 32px + var(--space-24) + 32px + var(--space-24));
  }

  .project-nav { height: var(--project-nav-h); }
  .project-nav.is-scrolled { height: var(--project-nav-scrolled); }
  .project-nav .nav-left,
  .project-nav .nav-right { width: auto; }

  /* Edge to edge, with the inset carried INSIDE as padding. Stopping the rail
     at the page's own margin meant the last card was sliced off at 24 and the
     first one had nowhere to scroll back to — the row was being cut by the
     body's width instead of running past it. */
  .project-nav-center {
    top: calc(var(--space-24) + 32px + var(--space-24));
    left: 0;
    right: 0;
    width: auto;
    transform: none;
    justify-content: flex-start;
    padding: 0 var(--space-24);
    scroll-padding: 0 var(--space-24);
  }

  .project-nav-cards   { transform: translateY(0);     opacity: 1; }
  .project-nav-anchors { transform: translateY(-100%); opacity: 0; }
  .project-nav.is-scrolled .project-nav-cards   { transform: translateY(100%); opacity: 0; }
  .project-nav.is-scrolled .project-nav-anchors { transform: translateY(0);    opacity: 1; }

  /* Both rails scroll sideways rather than being squeezed. */
  .project-nav-cards,
  .project-nav-anchors {
    overflow-x: auto;
    overscroll-behavior-x: contain;
    touch-action: pan-x;
    scrollbar-width: none;
  }
  .project-nav-cards::-webkit-scrollbar,
  .project-nav-anchors::-webkit-scrollbar { display: none; }
}

/* ── Mobile ─────────────────────────────────────────────────────────────

   `Device=Mobile, Page=Project, Subcategory=Default` (174:27596) is 393 x 144
   and it STACKS: 16 of padding, a 32-tall row of buttons, 16 of gap, a 64-tall
   row of cards, 16 of padding. Desktop is one row with the cards beside the
   buttons; at 393 there is no room for that, so the component puts them one
   under the other.

   Keeping the desktop arrangement here is what broke the bar: the cards were
   absolutely positioned at the top of it, which on a phone means directly on
   top of the name and the burger. */

@media (max-width: 700px) {
  :root {
    /* 174:27596 is 393 x 184: 16 of padding, a 32-tall row of buttons, 16 of
       gap, a 104-tall row of cards, 16 of padding. The cards are larger on a
       phone than on a wide screen now — 138 x 104 and 104 square against
       106 x 80 and 80 — which is the sensible way round for a thumbnail meant
       to be recognised at arm's length. */
    --project-nav-h: 184px;
    --project-nav-scrolled: 112px;
  }

  .project-nav { padding: var(--space-16); height: var(--project-nav-h); }
  .project-nav .nav-left { width: 120px; }
  .project-nav .nav-right { width: auto; }

  /* The bar's buttons are the same 32 tall here as on desktop — 6 and 12 of
     padding around 14/20 text — which is what makes the 144 come out. The
     larger touch target mobile.css gives every other nav item would make this
     bar 4px taller than the component it is copying. */
  .project-nav .nav-item {
    padding: var(--space-6) var(--space-12);
    font-size: var(--body-14-size);
    line-height: var(--body-14-line);
  }

  /* Square, and 32 like the row it is in — the main pages' bar is 36 tall and
     its burger follows that instead (174:27601 against 1:2017). */
  .project-nav .nav-burger { width: 32px; height: 32px; padding: 0; }

  /* Both centres move below the button row: 16 of padding, 32 of buttons, 16
     of gap. Full width from one inset to the other, so no centring transform
     is needed and the row can start at the left edge the way the component
     draws it. */
  /* Same here: the rail runs the full width of the screen and holds its 16 as
     padding, so a card can scroll all the way out instead of being sliced at
     the edge of the sheet. */
  .project-nav-center {
    top: calc(var(--space-16) + 32px + var(--space-16));
    left: 0;
    right: 0;
    width: auto;
    transform: none;
    padding: 0 var(--space-16);
    scroll-padding: 0 var(--space-16);
  }

  .project-nav-cards   { transform: translateY(0);     opacity: 1; }
  .project-nav-anchors { transform: translateY(-100%); opacity: 0; }
  .project-nav.is-scrolled .project-nav-cards   { transform: translateY(100%); opacity: 0; }
  .project-nav.is-scrolled .project-nav-anchors { transform: translateY(0);    opacity: 1; }

  /* Same bar with a 32-tall rail where the 64-tall cards were: 16 + 32 + 16 +
     32 + 16. Derived from her own Default rather than guessed at. */
  .project-nav.is-scrolled { height: var(--project-nav-scrolled); }

  /* 104 tall, and everything else falls out of it: the big card is 4:3 off
     that height — 138.67 where the frame rounds to 138 — and the small one is
     square at 104. One number, the same shared ratio as everywhere else, so
     the card keeps the shape it has on the home and does not reshape in
     flight. The gap stays the phone's 8 against a wide screen's 12. */
  .nav-cards-inner { gap: var(--space-8); }
  .nav-projects-group { gap: var(--space-8); }
  /* 174:27599: 138 x 104 and 104 square. 138.67 is 104 x 4/3 — the frame rounds
     it, this does not, so the card keeps the home card's shape exactly. */
  .nav-project--big   { width: 138.67px; height: 104px; }
  .nav-project--small { width: 104px;    height: 104px; }

  /* No rule between the groups on a phone: the component has none, the two
     groups are simply 8 apart like everything else in the row. */
  .nav-sep { display: none; }

  /* Three 85s, two 64s and four gaps come to 415 against the 361 available, so
     the row runs off the edge exactly as it does in the component. It scrolls
     rather than shrinking any card, and nothing grows on touch — there is no
     pointer to answer and the room is already short. */
  .project-nav-cards {
    justify-content: flex-start;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    touch-action: pan-x;
    scrollbar-width: none;
  }
  .project-nav-cards::-webkit-scrollbar { display: none; }

  /* The anchor rail scrolls sideways rather than wrapping: seven labels do not
     fit across 393. */
  .project-nav-anchors {
    justify-content: flex-start;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    touch-action: pan-x;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .project-nav-anchors::-webkit-scrollbar { display: none; }
}

/* ── Landing on an anchor ────────────────────────────────────────────────── */

/* The nav is fixed, so scrolling a section to the top of the viewport puts its
   first pixels underneath it. Every anchor target reserves the scrolled nav's
   height plus a little air, which is also the line project-nav.js measures the
   active anchor against — the two have to agree or the highlight fights the
   scroll. 88 = the 80 bar + 8. */
#anchor-overview, #anchor-brand, #anchor-extension, #anchor-dashboard,
#anchor-mobile, #anchor-ai-engine, #anchor-archives,
#anchor-marketplace, #anchor-property, #anchor-tokenized, #anchor-flow,
.project-header#anchor-overview {
  scroll-margin-top: calc(var(--project-nav-scrolled) + 8px);
}

html { scroll-behavior: smooth; }

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

/* The last section needs room below it to reach the top of the viewport, or its
   anchor looks dead. The crosslink footer's own 128 of padding provides it. */

/* ── Arrival ──────────────────────────────────────────────────────────────

   La barre apparaît en fondu, et rien dans elle ne se déplace.

   Elle descendait de 160% de sa hauteur, soit 128px au-dessus de la barre. Ce
   qui voulait dire que pendant la première seconde de chaque arrivée, les
   vignettes n'étaient pas là où on les voyait : mesuré au navigateur, la carte
   Iteration X est à -104 à 156ms, encore à -104 à 312ms, et ne se pose qu'à
   936ms. Cliquer dessus pendant ce temps touche le fond de page. D'où les deux
   ou trois clics avant d'ouvrir un projet.

   L'animation était censée être éteinte par `is-vt` — mais `is-vt` vient de
   `pagereveal`, qui n'existe que là où les transitions entre documents sont
   gérées. Partout ailleurs — Safari, Firefox, et selon la version Chrome — elle
   tournait à chaque chargement. Une navigation ne peut pas dépendre de ça.

   Donc un fondu : les cartes sont à leur place dès la première image et n'en
   bougent plus. Le mouvement, lui, passe dans l'image à l'intérieur du cadre,
   que le cadre découpe — la boîte du lien, elle, ne bouge jamais d'un pixel.
   `backwards` et non `both`, pour que le survol reprenne la main à la fin. */

@keyframes nav-centre-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes nav-vignette-pose {
  from { transform: translateY(-9%) scale(1.05); }
  to   { transform: none; }
}

.nav-cards-inner {
  display: flex;
  gap: var(--space-24);
  align-items: center;
  animation: nav-centre-fade 440ms cubic-bezier(0.16, 1, 0.3, 1) both;
}

.nav-project img {
  animation: nav-vignette-pose 720ms cubic-bezier(0.16, 1, 0.3, 1) backwards;
}

@media (prefers-reduced-motion: reduce) {
  .nav-cards-inner,
  .nav-project img { animation: none; }
}

/* The image zoom, kept alongside the widening on request. It rides inside the
   frame, so the corners keep cutting it however wide the frame has opened. */
.nav-project img {
  transition: transform var(--hover-travel) var(--hover-ease);
  backface-visibility: hidden;
}

.nav-project:not(.nav-project--current):hover img,
.nav-project:not(.nav-project--current):focus-visible img { transform: scale(1.075); }

@media (prefers-reduced-motion: reduce) {
  .nav-project,
  .nav-project img { transition: none; }
  .nav-project:hover img { transform: none; }
}
