/* Yurei Studio — page d'attente.
   Implémentation de l'artboard Figma « Landing » (node 3:2, 1440 × 1080).
   Toutes les valeurs ci-dessous sont lues dans le fichier, pas estimées :
   tailles, graisses, couleurs, gaps et line-heights viennent du nœud Figma
   correspondant, rappelé en commentaire. */

/* Familjen Grotesk est une fonte variable (axe wght 400→700) : un seul
   fichier sert le 400 du corps et le 600 des titres/liens. */
@font-face {
  font-family: "Familjen Grotesk";
  src: url("/fonts/familjen-grotesk.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Reddit Mono";
  src: url("/fonts/reddit-mono.woff2") format("woff2");
  /* Fonte variable restreinte aux graisses 400→600 : un seul fichier couvre
     le corps du colophon (400) et le semibold de la chute (600). */
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
}

/* Palette — une seule déclaration par token grâce à `light-dark()`, qui suit
   le `color-scheme` de la racine. Ça évite le piège du bloc sombre dupliqué
   entre `@media (prefers-color-scheme)` et `[data-theme]`, où toute
   modification doit être reportée deux fois sous peine de divergence
   silencieuse.

   Chaque token est écrit DEUX FOIS : la première ligne est le repli pour un
   moteur sans `light-dark()` (il ignore la seconde et reste en clair), la
   seconde est la valeur réelle. */
:root {
  color-scheme: light dark;

  --bg: #ffffff;
  --bg: light-dark(#ffffff, #0a0a0a);

  --text: #000000;
  --text: light-dark(#000000, #fafafa);

  /* 4,56:1 en clair, 4,59:1 en sombre — le même retrait perçu des deux côtés,
     et non le même gris, qui n'aurait pas le même contraste. */
  --muted: #75757e;
  --muted: light-dark(#75757e, #797982);

  /* Teinte 24,2° et saturation 100 % conservées dans les deux thèmes ; seule
     la clarté change. 4,91:1 en clair, 7,01:1 en sombre — sur fond noir un
     orange sombre virerait au brun, il faut le monter. */
  --accent: #bf4d00;
  --accent: light-dark(#bf4d00, #ff6d0a);

  /* Encre appuyée de la chute — 14,28:1 en clair, 14,21:1 en sombre : la même
     marche d'emphase depuis le muet des deux côtés. */
  --ink: #2a2a2f;
  --ink: light-dark(#2a2a2f, #dadadf);

  --sans: "Familjen Grotesk", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: "Reddit Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* Le bouton force le thème en fixant `color-scheme` : `light-dark()` suit
   tout seul, aucun token n'est redéclaré nulle part. */
:root[data-theme="light"] { color-scheme: light; }
:root[data-theme="dark"]  { color-scheme: dark; }


*,
*::before,
*::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* — Cadre ————————————————————————————————————— */

/* Landing 3:2 : flex column, items-center, px-10 py-120.
   `justify-content: center` retiré — la colonne ne flotte plus au milieu d'une
   boîte figée, elle occupe la hauteur disponible (cf. .column).

   Inset vertical : 120 px est la valeur de l'artboard, mais figée elle mange
   30 % d'un écran de 800 px. Exprimée en 11,2vh elle vaut exactement 120 px à
   la hauteur de l'artboard (1080) et se resserre proportionnellement en
   dessous, avec un plancher à 48 px.
   Inset horizontal : 10 px vient de l'artboard ; il ne devient gênant qu'en
   dessous de 560 px, d'où le passage à 24 px (décision dérivée). */
.landing {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Haut 64, BAS 96. L'asymétrie est une réserve, pas un choix esthétique :
     sur iPhone, la barre Safari est une pastille FLOTTANTE qui recouvre la
     soixantaine de pixels du bas de l'écran. Selon l'état de la barre, `svh`
     ne l'exclut pas toujours — et un colophon calé à 64 px du bord se retrouve
     alors dessous, coupé. 96 px passent sous une barre de 66 px avec 30 px de
     marge. Coût mesuré : 32 px repris à l'écart central, qui reste de 96 à
     190 px selon l'appareil. */
  padding-block: 64px 96px;
  padding-inline: 24px;
}

@media (min-width: 560px) {
  .landing { padding-inline: 10px; }
}

/* Sur téléphone le ratio de l'artboard (11,2vh, soit ~88 px) dépensait en
   marges hautes et basses le budget qui doit aller à l'écart central : mesuré,
   le passer à 64 px fait gagner 30 à 60 px d'écart selon l'appareil. */
@media (min-width: 768px) {
  .landing { padding-block: clamp(48px, 11.2vh, 120px); }
}


/* Frame 3:4 — 520 de large, space-between.
   `flex: 1` et non `height: 680px` : la hauteur de l'artboard décrivait un
   viewport de 1080 px et un seul. La colonne prend maintenant toute la
   hauteur restante, donc le colophon s'ancre au bas de l'écran quelle que
   soit sa taille au lieu de flotter à 680 px du haut.
   `min-height` sert de plancher : sous 480 px de hauteur utile la page défile
   plutôt que d'écraser la composition l'un sur l'autre. */
.column {
  width: 100%;
  max-width: 520px;
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  /* Blanc de séparation entre le bloc du haut et le colophon — l'espace que
     tu as entouré. Deux mécanismes, pas un :

     1. Il S'ADAPTE tout seul. `space-between` sur une colonne en `flex: 1`
        donne déjà TOUT l'espace restant : mesuré, 180 px sur un iPhone 14 en
        Safari, 268 sur un MacBook 13", 512 en 1440, 872 sur un 27 pouces.
     2. `gap` ne sert que de PLANCHER, pour le jour où le texte s'allonge.

     150 px est la plus haute valeur qui ne morde sur aucun appareil courant.
     Au-dessus, le plancher n'agrandit plus le blanc VISIBLE : il pousse
     simplement le colophon sous la ligne de flottaison. Un écart de 360 forcé
     sur un MacBook 13" ne sépare rien du tout — il cache le bloc du bas.

     Recalé deux fois : de 180 à 150 quand la bio s'est allongée d'une ligne,
     puis de 150 à 90 quand la réserve de 96 px est apparue en bas de page.
     Cette valeur dépend donc de la copy ET des marges : à revérifier dès que
     l'une des deux bouge. */
  gap: 90px;
  justify-content: space-between;
}

/* Frame 3:7 — flex column, gap 20 */
.top {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

/* — Statut ——————————————————————————————————— */

/* La rangée porte le statut à gauche et la bascule de thème à droite, calée
   sur le bord de la colonne. Sa hauteur reste celle du statut — 15 px, la
   valeur Figma — grâce au décrochage d'un demi-pixel appliqué au bouton. */
.status-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  width: 100%;
}

/* Frame 3:46 — flex row, gap 10, items-center.
   Texte 3:48 : Familjen Grotesk SemiBold 12 px, #6bbd2c, leading normal. */
.status {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0;
  font-size: 12px;
  font-weight: 600;
  line-height: normal;
  color: var(--accent);
  white-space: nowrap;
}

/* Ellipse 3:47 — 6 × 6, fill #6BBD2C. Rendue en CSS plutôt qu'en <img> SVG :
   rigoureusement le même disque, une requête de moins. */
.status__dot {
  position: relative;
  flex: none;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
  /* Correction optique — écart assumé avec l'artboard, à ta demande.
     Figma centre la pastille sur la boîte de ligne : centre à 7,5 px, ce qui
     la fait flotter au-dessus des lettres. Mesuré dans Familjen Grotesk à
     12 px : la hauteur d'x vaut exactement 6 px — le diamètre de la pastille —
     et sa bande va de 6 à 12 px (centre 9). Ces 1,5 px posent donc la pastille
     pile dans la bande de hauteur d'x, largeur pour largeur. */
  transform: translateY(1.5px);
}

/* Le noyau ne bouge plus et ne s'éteint jamais : c'est ce qui rendait le
   clignotement désagréable — l'œil accroche sur une disparition, pas sur un
   mouvement. Le mouvement passe donc dans une onde émise AUTOUR du point,
   qui grandit et se dissout. Rien ne disparaît, il y a toujours un point net
   à regarder, et la lecture « c'est vivant » est plus forte qu'avec un
   clignotement.

   Rythme : émission sur les 65 premiers % puis 35 % de repos. C'est ce silence
   qui en fait une pulsation plutôt qu'un radar en boucle. Easing sortant fort
   (départ rapide, fin très amortie) pour que l'onde s'éteigne au lieu de
   s'arrêter net. `transform` et `opacity` seules : composité, zéro reflow,
   et aucun impact sur la géométrie de la ligne. */
.status__dot::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: var(--accent);
  animation: ping 2200ms cubic-bezier(0.3, 0, 0.2, 1) infinite;
}

/* Amplitude 2,5× (15 px autour d'un point de 6) : au-delà l'onde cesse d'être
   une onde et devient une tache pâle. L'opacité tombe plus vite que l'échelle
   ne finit — la vague se dissout avant d'avoir fini de grandir, au lieu de
   s'arrêter net à son maximum. Émission sur 72 %, puis 28 % de silence : c'est
   ce repos qui en fait une pulsation et pas un radar. */
@keyframes ping {
  0%        { transform: scale(1);   opacity: 0.4; }
  55%       {                        opacity: 0.1; }
  72%, 100% { transform: scale(2.5); opacity: 0; }
}

/* — Identité et bio ——————————————————————————— */

/* Frame 3:24 — flex column, gap 4 */
.intro {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Frame 3:9 — flex row, gap 8, items-center */
.identity {
  display: flex;
  align-items: center;
  gap: 8px;
}

/* Ellipse 3:5 — 38 × 38. Source exportée en 76 px (densité 2×). */
.avatar {
  flex: none;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  object-fit: cover;
}

/* Texte 3:6 — SemiBold 16 px, #000, leading normal */
.name {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  line-height: normal;
  white-space: nowrap;
}

/* line-height en PIXELS et non en ratio — c'est ce que Figma dessine
   réellement. Il déclare 1.6, mais arrondit chaque boîte de ligne à
   l'entier : 16 x 1.6 = 25,6 -> il trace 26 (bio h=104 pour 4 lignes),
   et 12 x 1.6 = 19,2 -> il trace 19. Appliquer le ratio littéralement
   décalait la page de 1,6 px par bloc, soit 2 px cumulés sur la rangée
   de liens. Reprendre ses entiers rend la géométrie exacte. */

/* Texte 3:14 — Regular 16 px, #75757e, boîte de ligne 26 px, largeur pleine (520) */
.bio {
  margin: 0;
  font-size: 16px;
  font-weight: 400;
  line-height: 26px;
  color: var(--muted);
}

/* Texte 3:22 — mêmes valeurs que la bio */
.rebuild {
  margin: 0;
  font-size: 16px;
  font-weight: 400;
  line-height: 26px;
  color: var(--muted);
}

/* Le lien de téléchargement, inséré DANS le paragraphe. Il reprend mot pour
   mot la mécanique de la rangée de contact (soulignement dessiné en
   `background`, donc zéro layout shift au survol), moins son `white-space:
   nowrap` : celui-là fait quatre mots et doit pouvoir se couper en fin de
   ligne.

   Distingué du texte qui l'entoure par la seule couleur au repos, comme les
   liens de contact juste en dessous : c'est cohérent sur cette page, et
   mesuré, l'encre pleine sur le gris du paragraphe donne 4,60:1 en clair et
   4,13:1 en sombre. C'est au-dessus des 3:1 qu'exige WCAG 1.4.1 (technique
   G183) pour un lien en corps de texte, le second indice non coloré étant
   fourni au survol (soulignement) et au clavier (anneau de focus). */
.rebuild a {
  color: var(--text);
  text-decoration: none;
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 1px;
  transition: background-size 160ms cubic-bezier(0.2, 0, 0, 1);
}

.rebuild a:hover { background-size: 100% 1px; }
.rebuild a:active { opacity: 0.6; }

.rebuild a:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 4px;
  border-radius: 2px;
}

/* — Liens ————————————————————————————————————— */

/* Frame 3:28 — flex row, gap 20, items-center.
   Textes 3:35 / 3:39 / 3:40 : SemiBold 16 px, #000, leading normal. */
.links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 20px;
}

.links a {
  font-size: 16px;
  font-weight: 600;
  line-height: normal;
  color: var(--text);
  text-decoration: none;
  white-space: nowrap;
  /* Soulignement dessiné en `background` et non en `text-decoration` : il
     apparaît sans jamais changer la hauteur de ligne, donc zéro layout shift
     au survol. */
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 1px;
  transition: background-size 160ms cubic-bezier(0.2, 0, 0, 1);
}

/* L'artboard ne contient qu'un état. Le survol, le focus et l'appui sont
   dérivés : un lien sans état focus n'est pas navigable au clavier (WCAG
   2.4.7), donc ce n'est pas une liberté prise sur le design mais une
   condition de livraison. Traitement volontairement minimal. */
.links a:hover { background-size: 100% 1px; }
.links a:active { opacity: 0.6; }

.links a:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 4px;
  border-radius: 2px;
}

/* Annonce « ouvre un nouvel onglet » aux lecteurs d'écran. Hors flux :
   la largeur du lien, et donc la géométrie de la rangée, ne bougent pas. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* — Colophon —————————————————————————————————— */

/* Frame 3:32 — flex column, gap 4.
   Textes 3:31 / 3:33 : Reddit Mono Regular 12 px, #75757e, boîte de ligne 19 px. */
.colophon {
  position: relative;
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-family: var(--mono);
  font-size: 12px;
  font-weight: 400;
  line-height: 19px;
  color: var(--muted);
}

.colophon p { margin: 0; }

/* La chute qui défile : medium et encre appuyée, elle porte la phrase.
   `pre` sur les espaces — pendant le brouillage un caractère intermédiaire
   peut être une espace, et un `normal` la ferait s'effondrer, ce qui
   décalerait la ligne à chaque image. */
.likes {
  font-weight: 600;
  color: var(--ink);
  white-space: pre;
}

/* Easter egg — Blue passe la tête au-dessus du texte.

   Le masque est la pièce maîtresse : son bord BAS est exactement le haut du
   colophon (`bottom: 100%`). La photo y est posée entièrement en dessous
   (`bottom: -54px`), donc invisible ; la faire monter la fait dépasser de
   derrière le bloc de texte. Rien de tout ça ne touche à la mise en page :
   le masque est hors flux et se coupe lui-même, aucun débordement à gérer
   ailleurs sur la page. */
.peek {
  position: absolute;
  left: -12px;
  bottom: 100%;
  /* Le masque ne doit couper QUE par le bas. À la largeur exacte de la photo,
     la bascule la faisait sortir sur les côtés et on voyait le disque tranché
     net par un bord droit — le carré apparaissait. Calcul : le disque pivote
     autour d'un point situé 27 px sous son centre, donc à 8° son centre se
     déplace de 27·sin(8°) ≈ 3,8 px et il dépasse d'autant. 16 px de marge de
     chaque côté couvrent largement. En hauteur, 116 px : la photo n'atteint
     jamais le bord haut, donc il ne coupe jamais non plus. */
  width: 110px;
  height: 130px;
  overflow: hidden;
  pointer-events: none;
}

.blue {
  position: absolute;
  left: 20px;
  bottom: -56px;
  /* TEST détourés — largeur laissée libre, seule la hauteur est fixée : ces
     photos ne sont pas carrées (ratio ~1,19) et un cadrage forcé leur
     couperait les oreilles. Pas de `border-radius` non plus : elles sont déjà
     détourées, l'arrondir mangerait le museau.
     Pour revenir aux ronds : width/height 54px, border-radius 50%,
     object-fit cover, bottom -54px, left 16px — et le masque à 86 × 116, -8px. */
  height: 56px;
  width: auto;
  /* Pivot au bas du disque : la bascule fait tourner la tête sur son « cou »,
     au ras du texte, au lieu de faire pivoter tout le disque sur son centre. */
  transform-origin: 50% 100%;
  will-change: transform;
}

/* Un seul axe et une bascule d'angle qui revient à zéro. Pas de gravité, pas
   de rotation continue — donc jamais de contradiction entre « c'est une balle »
   et « c'est une photo qu'on doit pouvoir regarder ». Entièrement composée par
   le navigateur, aucun calcul par image.

   44 des 56 px dépassent : assez pour bien voir Blue, pas assez pour perdre la
   lecture « il passe la tête par-dessus » — un disque entièrement sorti ne
   dépasse plus de rien. */
.blue.is-peeking {
  animation: peek 2900ms both;
}

@keyframes peek {
  0%   { transform: translateY(0) rotate(0deg);
         animation-timing-function: cubic-bezier(0.18, 1.2, 0.32, 1); }
  18%  { transform: translateY(-44px) rotate(0deg);
         animation-timing-function: linear; }
  42%  { transform: translateY(-44px) rotate(0deg);
         animation-timing-function: ease-in-out; }
  51%  { transform: translateY(-44px) rotate(-8deg);
         animation-timing-function: ease-in-out; }
  60%  { transform: translateY(-44px) rotate(7deg);
         animation-timing-function: ease-in-out; }
  69%  { transform: translateY(-44px) rotate(0deg);
         animation-timing-function: linear; }
  86%  { transform: translateY(-44px) rotate(0deg);
         animation-timing-function: cubic-bezier(0.5, 0, 0.75, 0); }
  100% { transform: translateY(0) rotate(0deg); }
}

/* Mouvement réduit : Blue se montre quand même, posé sans bouger. L'easter egg
   reste, la gesticulation part. */
.blue.is-still {
  transform: translateY(-44px);
  transition: transform 320ms ease;
}

/* Caché tant que le script n'a pas confirmé qu'il tourne : un bouton mort
   est pire qu'un bouton absent (règle des 4 états). */
.theme { display: none; }

:root.js .theme {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  position: relative;
  flex: none;
  width: 16px;
  height: 16px;
  /* L'icône fait 16 px, la rangée du statut 15. Ce demi-pixel de chaque côté
     ramène sa boîte de marge à 15 px : l'icône garde la taille demandée sans
     décaler d'un pixel tout ce qui suit dans la colonne. */
  margin-block: -0.5px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
  transition: color 150ms cubic-bezier(0.2, 0, 0, 1);
}

/* Cible tactile portée par un pseudo-élément : 40 × 40 px sans que la boîte
   du bouton grandisse, donc sans toucher à la géométrie de la rangée. */
.theme::before {
  content: "";
  position: absolute;
  inset: -12px;
}

.theme:hover { color: var(--text); }
.theme:active { opacity: 0.6; }

.theme:focus-visible {
  outline: 2px solid var(--text);
  outline-offset: 4px;
  border-radius: 2px;
}

/* Une seule des deux icônes est montée à la fois : la lune en clair — elle
   annonce la destination, pas l'état — et le soleil en sombre. */
.theme__sun { display: none; }

:root[data-theme="dark"] .theme__moon { display: none; }
:root[data-theme="dark"] .theme__sun  { display: block; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme__moon { display: none; }
  :root:not([data-theme="light"]) .theme__sun  { display: block; }
}

/* `tabular-nums` : les chiffres de l'horloge gardent la même chasse d'une
   minute à l'autre, sinon la ligne se décale toute seule à chaque tick. */
.clock { font-variant-numeric: tabular-nums; }

/* — Très petits écrans ————————————————————————— */

/* iPhone SE et assimilés. À 375 px de large la bio passe à 7 lignes : le
   contenu fait 432 px, contre 406 sur un téléphone plus large. Sur 555 px de
   hauteur utile (Safari, barre affichée) il ne reste que 123 px pour les
   marges ET l'écart. Les valeurs nominales y imposaient 185 px de défilement.
   Ces deux paliers rendent la page entière visible sans défiler, au prix
   d'une séparation plus courte — c'est le seul arbitrage possible à cette
   taille sans toucher à ta typographie. */
@media (max-width: 767px) and (max-height: 680px) {
  /* Même asymétrie qu'en pleine hauteur : la réserve du bas est ce qui garde le
     colophon hors de la barre flottante. 72 px au lieu de 96, faute de place. */
  .landing { padding-block: 32px 72px; }
  .column  { gap: 72px; }
}

/* Plancher recalé de 72 à 48 le jour où le lien de téléchargement a été
   ajouté : à 375 px de large le paragraphe passe de 2 à 3 lignes, et les
   26 px gagnés par le texte faisaient déborder la page de 23 px sur un
   iPhone SE en Safari (555 px utiles, mesuré). 48 px rend les 23 px et garde
   une marge de 1 px. Comme le plancher du dessus, cette valeur est à
   revérifier à chaque changement de copy. */
@media (max-width: 767px) and (max-height: 580px) {
  /* 555 px de hauteur utile pour 432 px de contenu : il ne reste presque rien à
     réserver. 40 px en bas, et l'écart tombe à 32 — c'est le seul format où la
     séparation devient courte, faute d'alternative sans toucher à ta typo. */
  .landing { padding-block: 24px 40px; }
  .column  { gap: 32px; }
}

@media (prefers-reduced-motion: reduce) {
  .rebuild a,
  .links a,
  .theme { transition: none; }
  .status__dot { animation: none; }
}
