/* =========================================================
   EPOQUE EDITIONS — feuille de style de la maison
   Noir et blanc purs. Aucune couleur dans l'interface : elle
   n'appartient qu'aux visuels. Pas de grilles a bordures, pas
   de boites — des objets qui flottent dans le blanc.
   ========================================================= */

/* =========================================================
   LES FONTES — chez elles, et bloquantes

   Elles etaient servies par Google, avec `display=swap` : la
   page se composait dans la fonte de repli, puis basculait. Sur
   un texte courant, un swap ne se voit qu'a peine ; sur l'etage
   du haut, il se voit entierement — l'onglet Chat-Man se compose
   en Mono avec `letter-spacing: .14em` et un lisere, donc c'est
   sa LARGEUR qui changeait (mesure : 119,6 px une fois la Mono
   appliquee, contre l'approximation de `ui-monospace` avant).
   A chaque changement d'onglet, la rangee se recomposait sous
   l'oeil, et c'est une bonne part de ce qui se lisait comme un
   rechargement.

   Deux changements, et le meme raisonnement que pour la maille :
   ce qui doit etre juste a la premiere image ne s'attend pas.

   1. ELLES VIENNENT D'ICI. Meme origine que la page, pas de
      resolution DNS ni de poignee de main vers un tiers, et pas
      la feuille intermediaire que Google renvoyait avant de
      laisser partir le moindre fichier. Chaque page les
      precharge (cf. les entetes) : elles partent avec le HTML.
   2. `block` ET NON `swap`. Le texte attend sa fonte au lieu de
      se composer deux fois. Ce que ca coute : sur une premiere
      visite tres lente, jusqu'a trois secondes de texte invisible
      — sur 55 ko preacharges depuis la meme origine, et jamais
      au retour, ou tout est en reserve. Ce que ca rend : il n'y a
      plus d'instant ou la maison n'a pas sa voix.

   Le sous-ensemble est `latin`, et il porte tout le francais.
   Voir FONTES.md, a la racine du projet.
   ========================================================= */

@font-face {
  font-family: "Space Grotesk";
  /* Un seul fichier pour les deux graisses : c'est une fonte variable, et
     c'est deja ce que Google servait — deux declarations vers la meme URL. */
  font-weight: 400 500;
  font-style: normal;
  font-display: block;
  src: url("/assets/fonts/space-grotesk-v22-latin.woff2") format("woff2");
}

@font-face {
  font-family: "Space Mono";
  font-weight: 400;
  font-style: normal;
  font-display: block;
  src: url("/assets/fonts/space-mono-v17-latin-400.woff2") format("woff2");
}

@font-face {
  font-family: "Space Mono";
  font-weight: 700;
  font-style: normal;
  font-display: block;
  src: url("/assets/fonts/space-mono-v17-latin-700.woff2") format("woff2");
}

:root {
  /* Deux valeurs, pas une palette. Si une troisieme couleur
     apparait ici un jour, c'est qu'on s'est trompe. */
  --noir:  #000000;
  --blanc: #ffffff;

  /* Et deux ROLES, qui sont ce que la maison lit : l'encre et le papier.
     Le jour, encre noire sur papier blanc ; la nuit (plus bas), ils
     s'echangent. `--noir` et `--blanc` restent ce qu'ils disent, pour ce
     qui ne s'inverse pas — le vide d'un ciel, une banderole, une oeuvre. */
  --encre:  var(--noir);
  --papier: var(--blanc);

  --mono:    "Space Mono", ui-monospace, monospace;      /* logo, prix, noms */
  --grotesk: "Space Grotesk", system-ui, sans-serif;     /* textes */

  /* Direction de la lumiere, fixee une fois pour tout le site : source a 2h,
     l'ombre tombe vers 8h. Meme convention que le setup Blender — les objets
     detoures et l'ombre CSS doivent raconter le meme eclairage. */
  --light-x: -0.30;
  --light-y:  0.55;

  /* Distance de reference de l'ombre portee. Le flou suit la distance au lieu
     d'etre fixe : au-dela, l'ombre remonterait au-dessus du point de contact,
     la ou aucune ombre ne peut se trouver. */
  --sh-dist: 26px;
  --sh-blur: 44px;

  /* LE CHARGEMENT QU'ON EMBRASSE (cf. epoque.js, les bandes arrivent dans
     l'ordre). Plus court que le seuil, rien ne se voit ; au-dela, l'anneau
     reste au moins le minimum — sinon c'est un clignotement de plus. */
  --chargement-seuil: .1s;
  --chargement-min: .5s;
  /* Ce qu'on a attendu ENTRE, il ne surgit pas : l'objet et sa couverture
     montent en fondu quand la bande quitte l'attente (`data-arrive`). Ce qui
     etait deja la n'attend pas, donc ne fond pas. */
  --arrivee-fondu: .4s;
  /* Un tour d'anneau : tranquille, il dit qu'on attend, il ne presse pas. */
  --chargement-tour: 1.8s;

  /* Le flanc d'une encoche. Serre, pour que la rangee tienne dans une
     fenetre ordinaire (neuf onglets dans 1009 px). */
  --onglet-flanc: clamp(8px, 1vw, 24px);
  /* Devant un logo, le flanc gauche se resserre : le signe a deja son air
     a lui (son carre declare), un flanc entier l'ecartait du bord. */
  --onglet-flanc-logo: calc(var(--onglet-flanc) * .75);
  /* Le haut d'une encoche est arrondi, le bas ne l'est pas : une feuille
     glissee dans une chemise s'emousse par ou elle sort, et reste droite
     par ou elle se soude au panneau. */
  --onglet-arrondi: 10px;
  /* L'epaisseur de la bande que pose un onglet colore sous lui (essai,
     reglee au banc). 0 = pas de bande. */
  --onglet-bande-ep: 8;
  /* AU REPOS, LES LANGUETTES SE CHEVAUCHENT (reglees au banc). Ce qu'une
     languette rangee montre de son nom, en `em` (sans unite) : le signe,
     lui, se montre toujours. Et de combien la suivante la recouvre, en px
     (sans unite) : assez pour cacher la coupe du nom, jamais plus que le
     flanc, sans quoi le signe de la suivante passerait dessous. */
  --onglet-texte: 3.25;
  --onglet-recouvre: 8;
  /* SUR UN ECRAN ETROIT, ELLES SE SERRENT : chaque languette rangee montre
     moins de son nom, juste assez pour que la rangee tienne entiere entre
     ses deux marges (`--onglet-serre`, mesure par epoque.js, `replie`).
     Jamais moins que `--onglet-texte-min` (em, sans unite) : c'est le
     chevauchement maximal. Au-dela, la rangee ne se serre plus, elle
     defile — et l'onglet ouvert s'y centre. La marge, en px (sans unite),
     garde un peu d'air entre la rangee et le bord de l'ecran. */
  --onglet-texte-min: 1;
  --onglet-marge: 8;
  /* L'OMBRE QU'UNE LANGUETTE PORTE SUR CELLE QU'ELLE COUVRE (reglee au
     banc) : son flou en px (sans unite, 0 = pas d'ombre) et sa force, de 0
     a 1. Elle ne tombe que de cote, jamais sur le panneau. */
  --onglet-ombre: 8;
  --onglet-ombre-force: 0.1;
  /* De combien l'ombre remonte sur la languette couverte (0 = centree). */
  --onglet-ombre-haut: 0.5;
  /* De combien monte l'ombre que l'onglet ouvert porte sur le pied des
     autres languettes, en px (sans unite) : une translation de son bord,
     dont le flou reste `--onglet-ombre`. */
  --onglet-ombre-pied: 8;

  /* ---------------------------------------------------------
     L'ECHELLE DU MONDE — combien de pixels vaut un millimetre

     Elle se negociait entre les objets de la page : chacun disait
     l'echelle a laquelle il tiendrait encore dans sa bande, et le
     monde retenait la plus serree. C'etait juste, et c'etait une
     boucle — la mise en page donnait sa place a l'objet, l'objet
     en deduisait l'unite de la page. Mesure sur le seul rayon
     Chat-Man : 0,6173 px/mm sur un porteur monte contre 0,5746
     sur huit. Un millimetre qui change de longueur selon ce qui
     est arrive n'est pas un millimetre, et rien ne pouvait se
     peindre avant que le dernier objet n'ait repondu.

     Elle ne disait pourtant qu'une formule. Mesure a deux
     fenetres que tout separe — 0,6993 px/mm pour une bande
     nominale de 208 px, 0,7875 pour 234 — le rapport vaut 0,003362
     et 0,003365 : un millieme d'ecart sur un facteur trois et demi
     de largeur. Elle est donc ECRITE ici, et en deux morceaux qui
     se lisent : la hauteur que la fenetre accorde a l'objet
     ordinaire, et CE QUE CETTE HAUTEUR MESURE. Le meme couple que
     celui de la tuile (`--tuile-mm`, cf. sol.js), un etage plus
     haut — et c'est bien une regle de mise en page, donc elle
     s'ecrit dans une feuille de style. Le JS la lit et la pose
     (epoque.js, `declareLeMonde`), il ne la calcule pas.

     LE PLUS PETIT COTE COMMANDE, ET C'EST TOUTE L'ORIENTATION.
     Couche, c'est la hauteur qui manque : un objet plus haut que
     la fenetre ne se voit pas d'un coup, et un rayon se parcourt.
     Debout, c'est la largeur : la bande reserve `1,35 x` sa
     hauteur pour ce qu'elle porte (cf. `.rang`, `--part`), et une
     colonne trop etroite serrait l'objet — c'est exactement la que
     la negociation intervenait, en faisant retomber toute la page
     (mesure : de 0,630 a 0,333 px/mm sur deux parts de 112 px).
     Le `min()` reprend ce role, et c'est le seul endroit ou la
     largeur avait quelque chose a dire. Une seule expression, pas
     de point de rupture, pas de media query : l'orientation n'est
     pas un cas, c'est le cote qui gagne.

     Le `clamp` et le terme en hauteur sont ceux qui marchaient
     deja — les valeurs du livre, inchangees. Sur toute fenetre
     ordinaire le rendu ne bouge donc pas d'un pixel : ce qui est
     nouveau, c'est qu'il ne depend plus de ce qu'il y a dessus.
     --------------------------------------------------------- */
  --monde-haut-brut: clamp(170px, min(26vh, 66vw), 290px);
  /* CE QUE CETTE HAUTEUR MESURE. Un nombre mesure, pas choisi : la
     negociation rendait 297,4 mm sur toute fenetre ordinaire (0,003362
     et 0,003365 a deux fenetres que tout separe).

     306, ET C'EST LE PIRE CAS QUI LE FIXE. Le rapport n'est constant
     que tant que la hauteur suit la fenetre : au PLANCHER du `clamp`
     ci-dessus, la bande cesse de retrecir alors que ce qu'un objet
     reserve en pixels fixes — son socle, ses marges, la place que sa
     loupe demande au sol — ne bouge pas. Mesure sur le rayon
     Chat-Man, il faut alors 303,9 mm la ou une fenetre ordinaire en
     demande 297,5, et 302,2 par la fiche produit. Une echelle
     declaree se cale sur le plus exigeant, sinon elle est fausse
     quelque part : ailleurs, c'est deux pour cent de moins qu'hier,
     et deux pour cent ne se voient pas. Un point de rupture, si. */
  --monde-mm: 306;

  /* L'OBJET ORDINAIRE — la diagonale, en millimetres, de celui sur lequel la
     feuille a ete reglee : un A5, 148 x 210. Tout ce qui se mesure prend sa
     part contre lui (`partDe`), tout ce qui ne dit rien prend sa place.

     ELLE VIT ICI PARCE QUE C'EST UNE REGLE DE MISE EN PAGE. Elle etait dans
     le moteur (`sujet.js`, `plaqueDiagonale`), et la page la lui demandait —
     « deux exemplaires d'un meme nombre finissent toujours par diverger ».
     C'etait juste tant que le moteur arrivait avant la mise en page. Il
     arrive maintenant apres (il est differe, cf. les entetes), et la bande a
     besoin de ce nombre pour prendre sa part AVANT lui. Le demander au moteur
     n'etait donc plus possible, et s'en donner une copie aurait ramene
     exactement le probleme que la question evitait. Il se declare : la page
     le lit ici, le moteur aussi. Une loi, deux lecteurs — comme
     `--monde-haut` et `hautDuMonde()`. */
  --plaque-mm: 257;

  /* ET L'ECHELLE SE LIT DONC ICI, EN CSS. Les deux morceaux ci-dessus la
     disent deja ; ce qui manquait, c'etait de l'ECRIRE, pour que ce qui
     peint avant tout JS puisse la lire. `Monde.declare` la reprend telle
     quelle (epoque.js, `declareLeMonde` : la meme hauteur, mesuree, divisee
     par le meme nombre) — ce n'est pas une seconde loi, c'est la meme,
     disponible un cran plus tot.

     Une longueur divisee par un nombre reste une longueur : ceci vaut des
     PIXELS PAR MILLIMETRE, et tout ce qui se mesure en millimetres peut
     s'en deduire une taille sans attendre que quoi que ce soit se monte. */
  --monde-echelle: calc(var(--monde-haut-brut) / var(--monde-mm));
  /* Ce que la fenetre accorde a l'objet ordinaire, UNE FOIS L'ECHELLE CALEE
     (plus bas) : la bande et le dessin lisent le meme nombre. */
  --monde-haut: calc(var(--monde-echelle) * var(--monde-mm));

  /* LA DENSITE DE L'ECRAN, declaree par l'entete de chaque page (un script
     en ligne, avant la premiere image) : une feuille ne la connait pas.
     Sans ce script, c'est le pixel CSS. */
  --dpr: 1;

  --marge:   clamp(20px, 5vw, 64px);
  --colonne: 1180px;

  /* La gouttiere. Le panneau du rayon va d'un bord de l'ecran a l'autre —
     aucune bande blanche sur les cotes, on est DANS l'univers — mais son
     texte, lui, reste dans la colonne. Cette valeur est le retrait qui
     ramene le contenu sur l'axe du nom de la maison : tous les etages du
     site l'utilisent, c'est ce qui les tient alignes malgre leurs largeurs
     differentes. */
  --gouttiere: calc(max(0px, (100% - var(--colonne)) / 2) + var(--marge));

  /* La matiere du rayon — la feuille dans laquelle l'encoche de l'onglet
     est taillee. Epoque a la sienne : papier blanc, arete noire. Une peau
     (peau-*.css) ne fait que remplacer ces deux valeurs, et le motif par
     dessus si son univers en a un. C'est tout ce qu'un nouvel univers a
     besoin de dire pour habiller ses onglets.

     L'arete n'est pas une boite : elle ne ferme rien en bas, la feuille
     continue de descendre. Le seul contour ferme du site est celui de
     l'encoche, et c'en est un objet — un onglet a des flancs. */
  --rayon-bord: var(--encre);
  --rayon-fond: var(--papier);
}

/* EPOQUE EST UN UNIVERS COMME LES AUTRES, et depuis qu'un onglet porte le
   sien, il lui faut pouvoir le DIRE. Ces valeurs sont deja celles de la
   racine ; elles sont redites ici parce qu'une peau les remplace a la racine
   quand la page appartient a son univers — et l'onglet du rayon voisin les
   heritait alors d'elle. L'onglet Clothing prenait le brun du mur Chat-Man
   pendant qu'on lisait Chat-Man. Un onglet qui ne declare rien reste sous la
   racine, comme avant : ce bloc ne cree pas de second chemin, il rend a
   `epoque` le droit de se nommer. */
[data-peau="epoque"] {
  --rayon-bord: var(--encre);
  --rayon-fond: var(--papier);
  /* ET SON ABSENCE DE MOTIF EN FAIT PARTIE. Une peau declare par HERITAGE :
     posee sur la racine, elle descend dans tout ce que la page contient — y
     compris dans un onglet qui nomme un AUTRE univers, et qui ne redit que ce
     qu'il redit. Sans cette ligne, l'onglet Clothing d'une page Chat-Man
     heritait du motif de Chat-Man et se faisait carreler (mesure : les trois
     onglets peints, deux d'entre eux sur du papier blanc). Un univers qui se
     nomme dit donc TOUT ce que la maison lui demande, l'absence comprise. */
  --sol-motif: none;
}

/* ---------------------------------------------------------
   CE QUE LA TUILE MESURE -> CE QU'ELLE REND

   La peau declare ce que son monogramme COUVRE (`--tuile-mm`) ;
   ce qu'il rend a l'ecran se deduit, et il n'y a rien a choisir.
   C'est le meme geste que `Monde.declare` un etage plus bas :
   une grandeur qui se calculait s'ecrit, et ce qui l'attendait
   n'attend plus.

   ELLE VALAIT UN NOMBRE DE PIXELS, ET C'ETAIT LE DEFAUT. La peau
   posait `--tuile: 38px` — le milieu de ce que le sol rendait, a
   cinq pour cent pres — et le vrai carrelage arrivait avec le
   sol. Mesure sur le rayon Chat-Man : le sol publiait 45,118 px
   la ou la feuille en declarait 38, dix-neuf pour cent d'ecart.
   C'est cet ecart, et lui seul, qui obligeait le panneau et
   l'encoche a ne RIEN peindre en attendant — mesure, first-paint
   a 88 ms et first-contentful-paint a 168, quatre-vingts
   millisecondes d'aplat brun a chaque arrivee sur le rayon, la ou
   une page sans sol peint tout d'un coup (116 ms, FP = FCP).

   Ecrite, la formule ne peut plus se tromper : le fond CSS et la
   toile du sol sortent du meme nombre, donc ils se posent a la
   meme maille des la premiere image, et la toile ne fait que
   remplacer un dessin par le meme dessin, plus net. Il n'y a plus
   rien a attendre, donc plus de `data-carrele`, plus de
   `data-sans-sol`, et plus de reseau a publier.

   Sur `[data-peau]` et non sur `:root` : un onglet range porte
   l'univers du rayon qu'il ouvre, donc sa propre `--tuile-mm`, et
   il lui faut sa propre maille — celle du carrelage de son mur,
   pas de celui de la page ou il se trouve. Une peau qui ne dit
   pas ce que sa tuile mesure n'a pas de carrelage : la maille
   vaut zero, et le sol se retire (sol.js, `lit`).
   --------------------------------------------------------- */
[data-peau] {
  --tuile: calc(var(--tuile-mm, 0) * var(--monde-echelle));
}

/* ---------------------------------------------------------
   LA TUILE TOMBE SUR UN NOMBRE ENTIER DE PIXELS D'ECRAN

   Et c'est l'ECHELLE qui s'y cale, pas la tuile : `--tuile` reste
   `--tuile-mm x --monde-echelle`, la loi ne change pas, c'est
   l'unite qui s'arrondit. Tout ce qui se mesure en millimetres
   suit donc — objets, bandes (`--monde-haut` se deduit d'elle),
   loupes, cales — et rien n'est corrige apres coup.

   POURQUOI. A 65,556 px de maille (telephone, densite 2), deux
   rasteriseurs ne sautent pas les memes colonnes : le fond CSS et
   la toile du sol, a meme maille et meme phase, ne gardaient que
   80 a 96 % de pixels identiques (29 septembre, sept densites) —
   et le bord d'une loupe peinte a part serait tombe sur cette
   difference. A maille entiere, une planche gravee UNE fois se
   recopie telle quelle : plus rien a reechantillonner, donc plus
   rien sur quoi deux dessins puissent diverger.

   VERS LE BAS, JAMAIS VERS LE HAUT. `--monde-mm` est cale sur le
   pire cas avec +0,7 % de marge (1200 x 300, cf. plus haut) : un
   arrondi au plus proche pouvait en manger 0,68. Arrondie par
   defaut, l'echelle ne peut que retrecir — aucune bande ne deborde
   de plus qu'hier, par construction.

   CE QUE CA COUTE : moins d'un pixel d'ecran sur une tuile, soit
   -0,85 % sur telephone a densite 2 (65,556 -> 65) et -0,26 % en
   1440 x 900 (45,118 -> 45). Et l'echelle depend maintenant de la
   densite : la meme fenetre sur deux ecrans differe d'autant.

   SUR LA TUILE DE L'UNIVERS OU L'ON EST. La racine porte celle de
   sa peau ; un onglet qui nomme un autre univers se cale sur la
   sienne (meme regle, sur `[data-peau]`), donc son carrelage est
   entier lui aussi — et ce qu'il contient n'est que du texte. Sans
   tuile declaree, le pas vaut un kilometre : l'arrondi ne se voit
   plus (un millionieme), c'est l'echelle d'avant.

   `round()` manquant, la regle ne s'applique pas et l'on garde
   l'echelle brute : le carrelage d'hier, pas une page cassee.
   --------------------------------------------------------- */
@supports (width: round(nearest, 1.5px, 1px)) {
  :root, [data-peau] {
    --monde-echelle: calc(
      round(down,
            var(--monde-haut-brut) / var(--monde-mm)
              * var(--tuile-mm, 1000000) * var(--dpr),
            1px)
      / (var(--tuile-mm, 1000000) * var(--dpr)));
  }
}

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

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

/* ON EST ENTRE : LA PAGE NE DEFILE PLUS.

   Une bande ouverte fait exactement un ecran et tout ce qu'elle montre y
   tient (cf. `.rang--ouvert`) : il n'y a plus rien a atteindre en defilant, et
   defiler ne ferait que sortir de l'objet par accident. `epoque.js` pose
   l'attribut a la fin du geste d'ouverture — pas avant, le geste a lui-meme
   une page a traverser — et le retire avant celui de la sortie.

   La gouttiere de barre reste reservee en permanence, sinon la barre qui
   disparait decalerait toute la page d'une dizaine de pixels au moment ou
   l'on entre — un saut, au seul endroit du site ou l'on a promis qu'il n'y en
   aurait pas. */
html { scrollbar-gutter: stable; }
/* UNE PAGE BLOQUEE L'EST AUSSI CONTRE LE NAVIGATEUR. `overflow: hidden`
   retire le defilement ; il ne retire pas ce que le telephone fait QUAND IL
   N'Y EN A PLUS — le rebond, et le tirer-pour-recharger. Or tout l'apercu
   s'appuie sur des mesures prises au clic et sur une page qu'on tient pour
   immobile : un rechargement declenche au doigt pendant le sejour, c'est le
   geste perdu et la page qui repart de zero. */
html[data-apercu] { overflow: hidden; overscroll-behavior: none; }
/* Pendant le geste, les autres bandes s'ecartent de l'axe et sortent du champ
   par les cotes (cf. epoque.js, les autres bandes sont posees sur le meme
   sol). Ce qui sort a droite allongerait la page : on lui retire l'axe
   horizontal pour la duree du geste, et lui seul — le vertical, le recul en a
   besoin pour ramener la page ou elle etait. */
html[data-geste] { overflow-x: hidden; }
/* MAIS `overflow` SUR `html` NE BORNE PAS LA LARGEUR DU DOCUMENT. Pose la,
   il se PROPAGE a la fenetre — et `html` lui-meme calcule alors `visible`.
   Il retire donc le geste de defilement horizontal, ce qu'on lui demandait,
   et il ne retire rien du tout a la boite de debordement : le document reste
   large de ce que les bandes projetees occupent.

   Ce que ca coute, et ce n'est pas un detail d'affichage : un document plus
   large que l'ecran, un navigateur de telephone le REDUIT pour le faire
   tenir. Mesure sur l'apercu d'un pin's en 360 x 670 — document large de
   705 px, et `window.innerHeight` qui passe de 670 a 1313. Le rapport des
   deux est le meme a trois decimales (1,958 contre 1,960), et sur un vrai
   telephone aussi (2,044 contre 2,045). Le bloc conteneur, lui, ne bouge
   pas d'un pixel, et `visualViewport` ne signale rien.

   Or `innerHeight` est lu partout — le milieu de la fenetre pour le verre
   (`lumiere.js`) et pour le tunnel (`tunnel.js`), la bande peinte du sol,
   la fuite de l'optique. Il double, ces milieux descendent de 321 px, il
   revient, ils remontent : c'est la boucle rythmee qu'on voyait, et elle
   n'a jamais ete un probleme de dessin.

   On borne donc la ou ca borne, en `clip` — qui decoupe un seul axe sans
   faire de l'autre un scrollport, ce que `hidden` ne sait pas faire, et le
   recul a besoin du vertical. La regle du dessus reste : elle garde le geste
   du doigt contre le navigateur, et c'est un autre travail. Mesure apres :
   zero debordement d'un bout a l'autre du geste, et `innerHeight` qui ne
   bouge plus.

   ET CE QUI BORNE, C'EST LE PANNEAU, PAS LE RAYON. La coupe etait posee sur
   `.rayon` — la COLONNE — alors que la piece en sort par ses marges
   (`--piece-bord`) pour aller d'un bord de la fenetre a l'autre, et que
   `data-geste` couvre aussi le SEJOUR. La piece ouverte etait donc tranchee
   aux bords de la colonne pendant tout l'apercu : le livre a gauche, la
   cartouche a droite, a 234 et 1286 px sur une fenetre de 1562. `.page` a la
   largeur de la fenetre (sa gouttiere est un padding) : couper la, c'est
   couper au bord qu'on voit. */
/* (La coupe est posee sur `.page` une fois pour toutes — plus bas, « ce que
   porte un onglet ne sort pas de son onglet ».) */

/* LA NUIT DE LA MAISON. Un telephone qui prefere le sombre recoit la maison
   inversee : papier noir, encre blanche — toujours deux valeurs, toujours
   aucune couleur. Rien d'autre ne change ici : chaque univers a sa propre
   nuit dans sa peau, et les oeuvres n'en ont pas. `data-theme="clair"` la
   refuse, comme sur le mur. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="clair"]) {
    --encre:  var(--blanc);
    --papier: var(--noir);
  }
}

body {
  margin: 0;
  background: var(--papier);
  color: var(--encre);
  font-family: var(--grotesk);
  font-size: clamp(15px, 1.05vw, 17px);
  line-height: 1.55;
  /* Les deux, et c'est le site qui les fait (cf. `--encre`, `--papier`) :
     dit au navigateur, pour qu'il accorde ce qui lui appartient — barres de
     defilement, champs — et n'assombrisse rien de lui-meme. */
  color-scheme: light dark;
}

img { max-width: 100%; height: auto; display: block; }

a { color: inherit; text-decoration: none; }
a:focus-visible, button:focus-visible {
  outline: 1px solid var(--encre);
  outline-offset: 3px;
}
/* Un focus donne par le script hors clavier ne s'annonce pas
   (`data-sans-anneau`, cf. donneLeFocus dans epoque.js). */
[data-sans-anneau]:focus-visible { outline: none; }

/* ---------------------------------------------------------
   Ossature
   --------------------------------------------------------- */

/* Le panneau du rayon. C'est LUI, et rien d'autre, qui peut porter la peau
   d'un univers (voir peau-*.css) : le motif s'arrete a ses bords, et il ne
   monte pas jusqu'aux onglets. En bas, il n'y a plus rien apres lui — le pied
   de page et sa bande blanche sont deposes, la page finit sur le mur. */
.page {
  /* CE QUI DEBORDE EN BAS RESTE DANS LE PANNEAU. Une bande ouverte fait un
     ecran et deborde par-dessus ce qui la suit (voir --piece-place) : sur les
     dernieres bandes du rayon, elle deborde du panneau lui-meme, et la piece
     se retrouvait posee moitie sur le carrelage, moitie sur le blanc du
     document. Le panneau prend donc exactement ce que la piece lui prend de
     plus (`--piece-reserve`, mesuree au clic par le JS). La reserve ne
     rallonge pas le document : ce qui deborde comptait deja dans le
     defilement, on ne fait que l'habiller. */
  padding: calc(clamp(24px, 4vh, 40px) + var(--bande-ep) * 1px) var(--gouttiere)
           calc(96px + var(--piece-reserve, 0px));
  /* LA BANDE DE L'ONGLET OUVERT, en pixels CSS (sans unite, pour que le JS la
     lise telle quelle : `Epoque.hautDuReseau`). Zero tant que l'etage ne dit
     pas qu'il y en a une (`data-bande`, pose par `poseLaBande`) ; alors le
     contenu ET le carrelage partent sous elle, du meme nombre. */
  --bande-ep: 0;
  /* Le bord haut ferme la feuille — SAUF SOUS L'ENCOCHE, ou il n'y a rien a
     fermer : les deux pieces n'en font qu'une. Il gardait sa place dans la
     mise en page (l'encoche descend d'un pixel dedans) mais pas sa couleur :
     le trait se peint juste en dessous, avec un trou (voir .page::before).

     Au repos, l'onglet passe AUSSI devant (`.maison` relevee) et sa
     languette descend sur le trait (`.onglet[aria-current]::before`) : la
     jonction est faite deux fois, par deux chemins qui ne s'arrondissent pas
     pareil — chacun couvre les fautes de l'autre. En apercu et pendant un
     geste, le panneau repasse devant : il ne reste que le trou, qui suffit. */
  border-top: 1px solid transparent;
  background-color: var(--rayon-fond);
  /* Le motif d'une peau se compte a partir de CE coin. L'encoche recale le
     sien dessus (voir Epoque.sertit) — d'ou l'origine explicite, pour que
     les deux mesurent depuis leur bord et non depuis leur interieur. */
  background-origin: border-box;
  background-repeat: repeat;
  background-size: var(--tuile) var(--tuile);
  /* ET SA PHASE EST CELLE DU SOL : un BORD de tuile sur l'axe median, un bord
     de tuile sur la bordure haute — exactement `origine = { x: largeur / 2,
     y: 0 }` (sol.js, `mesure`). Le fond partait du coin, comme un fond CSS le
     fait par defaut, et il tombait donc a cote du carrelage que la toile
     allait peindre : deux reseaux qui ne s'accordent nulle part. `50%` pose
     le CENTRE de la tuile sur l'axe ; une demi-tuile de plus y pose son bord,
     et tout ce qui est centre redevient symetrique (cf. sol.js).

     Une peau qui sait zoomer son fond (le repli de l'apercu) surcharge ces
     deux lignes — elle doit alors reprendre ce demi-pas, a la maille du
     moment. */
  background-position: calc(50% + var(--tuile) / 2) calc(var(--bande-ep) * 1px);
}
.page[data-bande] { --bande-ep: var(--onglet-bande-ep); }

/* ET CE MILIEU TOMBE SUR UN PIXEL D'ECRAN. La maille est entiere (plus haut,
   `--monde-echelle`) ; un milieu entre deux pixels et chaque rasteriseur pose
   le reseau de son cote — le sol lit ce meme nombre, arrondi au meme pas
   (`Epoque.milieuDuReseau`). Sans `round()`, le milieu d'avant. */
@supports (background-position-x: round(nearest, calc(50% + 1px), 1px)) {
  .page {
    background-position:
      round(nearest, calc(50% + var(--tuile) / 2), calc(1px / var(--dpr)))
      round(nearest, calc(var(--bande-ep) * 1px), calc(1px / var(--dpr)));
  }
}

/* LE TRAIT, ET SON TROU. Il occupe la place du bord haut, dont il a pris la
   couleur. `--encoche-x` et `--encoche-l` sont posees par le JS (Epoque.sertit)
   et disent ou l'encoche mord ; absentes — pas de JS, pas d'onglet ouvert —
   le trou est de largeur nulle et le trait est plein, comme un bord ordinaire.
   Rien a tenir a jour en double. */
.page::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  width: 100%;
  /* LA BANDE — ESSAI. L'onglet ouvert qui porte une couleur (`--encoche`)
     la prolonge sous lui, sur toute la largeur du panneau. C'est le trait
     qui la porte, sous son filet : elle remplit le trou sous l'encoche, la
     ou le bord du panneau laissait voir le fond de la peau entre les deux
     pieces (mesure, un liseré brun de 0,67 px a dpr 1,5). La couleur est
     posee par `poseLaBande` (`--onglet-bande`) avec `data-bande` ; sans
     elle, rien ne se peint que le trait.

     Le trait et la bande sont deux COUCHES : qui veut un trait plein (la
     grille) ferme le trou (`--trou: 0`), il ne repeint pas le fond — sans
     quoi il effacait la bande avec. */
  height: calc(1px + var(--bande-ep) * 1px);
  background:
    /* Le trait du bas : transparent, sauf pour qui le demande (la grille). */
    linear-gradient(var(--trait-bas, transparent) 0 0) bottom / 100% 1px no-repeat,
    linear-gradient(to right,
      var(--rayon-bord) 0 var(--encoche-x, 0px),
      transparent var(--encoche-x, 0px)
                  calc(var(--encoche-x, 0px) + var(--encoche-l, 0px) * var(--trou, 1)),
      var(--rayon-bord) calc(var(--encoche-x, 0px) + var(--encoche-l, 0px) * var(--trou, 1)) 100%)
      top / 100% 1px no-repeat,
    /* LA BANDE PREND AUSSI LA RANGEE DU TRAIT, ET LE TROU EST L'INTERIEUR DE
       L'ONGLET. Le panneau se peint apres l'etage du haut : dans cette rangee,
       ce qu'il pose recouvre la derniere rangee de l'onglet. Le trait y couvre
       donc les bords de l'onglet (meme couleur, ils se prolongent jusqu'au
       coin), et la bande remplit l'interieur (sans elle on voyait le fond de
       l'onglet, qui n'est pas la bande). Un arrondi au bord du trou tombe
       sur un bord, jamais sur le fond. Mesure : outils/banc-onglets/jonction.py. */
    linear-gradient(var(--onglet-bande, transparent) 0 0) 0 0 / 100% 100% no-repeat;
  pointer-events: none;
  /* ET LA BANDE EST AU-DESSUS DE TOUT CE QUE LE PANNEAU PORTE AU REPOS. Sans
     etage, elle se peignait AVANT les bandes (ordre du document) : le voile
     d'un objet verrouille, un disque qui monte au-dessus de sa prise, la
     recouvrait — et la lisait, puisqu'un `backdrop-filter` lit tout ce qui est
     peint avant lui. Peinte apres, elle n'est plus derriere personne : rien
     ne la floute. Une bande verrouillee monte a 2 (au-dessus du tunnel) :
     la bande la passe donc a 3. Une bande OUVERTE (3 aussi) reste devant,
     parce qu'elle vient apres dans le document — la piece couvre la vue. */
  z-index: 3;
}

/* ---------------------------------------------------------
   LE SOL — la place du motif projete
   Quand sol.js tient, le motif du panneau cesse d'etre un fond
   repete : il devient le PLAN sur lequel les objets sont poses,
   dessine avec l'oeil des livres (sol.js). Cette feuille ne
   sait rien de tout ca — elle ne fait que lui faire sa place,
   et cette place est la meme pour tout univers qui en voudrait
   un. Ce qui est carrele, et a quelle taille, appartient a la
   peau ; sans peau qui le declare, rien de ceci ne sert.

   Il vient DERRIERE — z-index negatif — et le panneau devient
   pour l'occasion un contexte d'empilement. Sans ce contexte,
   un enfant en z-index negatif passerait derriere le fond de
   son ancetre commun, c'est-a-dire derriere la couleur du
   panneau, qui le cacherait entierement. Avec, il se glisse
   exactement ou il faut : au-dessus de cette couleur, sous tout
   ce que le panneau porte.
   --------------------------------------------------------- */
/* ET LE PANNEAU ISOLE TOUJOURS, QU'IL AIT UN SOL OU NON. Un panneau qui
   n'isolerait que parfois donnerait deux comportements a tenir d'accord ; ici
   il n'y en a qu'un, et c'est celui qui tournait deja partout ou un univers
   pose son motif. */
.page {
  position: relative;
  isolation: isolate;
}

/* CE QUE PORTE UN ONGLET NE SORT PAS DE SON ONGLET — par construction, pas
   par prudence de chacun. La lune de la planete Chat-Man, sur le haut de son
   orbite, passait PAR-DESSUS la rangee d'onglets : le panneau du hub ne
   coupait qu'a droite, et chaque contenu devait se tenir tout seul. Le
   panneau coupe donc tout ce qu'il porte, sur ses deux axes, quoi que ce
   soit et d'ou que ca vienne :
   - `overflow-x: clip` borne le DOCUMENT (ce qui sort a droite ne l'elargit
     pas — cf. plus haut, `html[data-geste]`) sans faire du panneau un
     conteneur de defilement : le vertical reste au document, `sticky`
     marche ;
   - `clip-path: inset(0)` borne la PEINTURE a la boite de bord : le trait
     du haut (`.page::before`, dans la bordure) reste, tout ce qui monte
     au-dessus est coupe. `overflow: clip` sur l'axe vertical aurait coupe
     a la boite de remplissage — donc le trait — et `overflow-clip-margin`
     n'est pas partout.
   Ce qui est fixe dans le panneau (la fleche de l'apercu) y reste : pendant
   le sejour, le panneau couvre la vue — mesure, son haut a -34 px. */
.page {
  overflow-x: clip;
  clip-path: inset(0);
}

/* L'enveloppe couvre le panneau et COUPE : le motif lui appartient, il ne
   deborde pas dans la maison au-dessus. */
.sol {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
}

/* La toile ne fait que la hauteur de la FENETRE et se replace dedans a
   chaque image : un rayon fait plusieurs milliers de pixels de haut, et
   personne n'en regarde plus d'un ecran a la fois. Un pixel CSS y est un
   pixel de toile, reduit par `transform` (sol.js, `trace`) — et elle n'a de
   pixels que sous une camera : au repos, le fond et les loupes la
   remplacent. */
.sol__toile {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
  will-change: transform;
}

/* LES LOUPES DU REPOS — une toile par groupe de lentilles qui se touchent,
   peintes une fois (sol.js, `loupesAuRepos`). Meme loi que le fond : le
   calque est reduit de `--dpr`, un pixel CSS y est un pixel d'ecran, et une
   toile y a une place et une taille entieres. Sous une camera la toile du
   sol prend tout, et elles se retirent : elles ne suivent pas le geste. */
.sol__loupes {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
  transform: scale(calc(1 / var(--dpr)));
  will-change: transform;
  pointer-events: none;
}
.sol__loupe { position: absolute; }
.sol[data-camera] > .sol__loupes { display: none; }

/* LE FOND DU SOL — la planche que la toile repete, repetee par le navigateur
   sous elle (sol.js, `poseLeFond`). Il tient la place la ou la toile n'est
   pas encore : en defilant, avant son premier dessin.

   UN PIXEL CSS DU CALQUE EST UN PIXEL D'ECRAN. Le calque est `--dpr` fois
   plus grand que le panneau, et il est reduit d'autant : la case y fait
   `--planche` px CSS entiers, sa phase aussi, et une transformation ne
   s'arrondit pas au soixante-quatrieme de pixel comme une longueur de mise
   en page. C'est ce qui le rend exact aux densites fractionnaires, la ou un
   fond ordinaire derive d'un centieme de pixel par case (mesure : 100 %
   contre 0 a 10 % de colonnes identiques, a 1,25, 1,75 et 2,625).

   Il ne s'allume qu'avec sa planche (`data-planche`, posee une fois l'image
   decodee) ; avant, le fond de la peau tient la place — et il reste dessous
   de toute facon, il ne coute rien de plus qu'avant. */
.sol__fond {
  display: none;
  position: absolute;
  top: 0;
  left: 0;
  width: calc(100% * var(--dpr));
  height: calc(100% * var(--dpr));
  background-repeat: repeat;
  background-size: var(--planche) var(--planche);
  transform-origin: 0 0;
  transform: scale(calc(1 / var(--dpr)));
  will-change: transform;
  pointer-events: none;
}
[data-planche] > .sol > .sol__fond { display: block; }

/* ---------------------------------------------------------
   LA LOUPE N'EST PLUS ICI — elle est dans le sol

   Un objet trop petit ou trop grand n'est pas rendu a
   l'echelle du monde : on s'en approche, ou on s'en eloigne,
   et le carrelage sous lui change de maille pour le dire (cf.
   loupe.js). C'etait une surface posee dans la bande, avec ses
   cellules et son motif ; elle etait PLATE pendant que tout
   autour d'elle se creusait.

   Le sol la peint maintenant lui-meme, comme une couche de
   plus de son propre carrelage : meme plan, meme oeil, meme
   trajet, meme motif (cf. sol.js, peintZones). Il n'y a donc
   plus rien a decrire ici, et plus de seconde geometrie a
   tenir d'accord avec la premiere.
   --------------------------------------------------------- */

/* ---------------------------------------------------------
   L'etage EPOQUE — la maison au-dessus de ses rayons
   Le nom, la signature, les onglets. Du texte pose sur le
   blanc, jamais une barre. Cet etage est noir et blanc en
   toutes circonstances : c'est la maison qui tient les
   univers, elle n'en porte aucun.
   --------------------------------------------------------- */

.maison {
  /* Meme gouttiere que le panneau : les deux etages n'ont pas la meme
     largeur — l'un va au bord, l'autre non — mais leur contenu tombe sur
     le meme axe. */
  padding: var(--marge) 0 0;
}

/* LA LANGUETTE PEINT SA DERNIERE RANGEE ELLE-MEME. Elle descend d'un pixel
   dans le panneau, sur la rangee du trait : la, c'est elle ou lui. Le trou
   du trait (.page::before) se comptait sur elle, et un pixel d'ecran
   d'ecart entre l'arrondi du navigateur et le notre laissait une colonne de
   bande sous son bord (mesure : outils/banc-onglets/jonction.py, 1 a 6 px
   d'ecran par coin a dpr 2 et 3). Elle passe donc DEVANT : ses bords et son
   fond couvrent le trait, au pixel de l'ecran, sans calcul.
   Le `z-index` de la rangee n'y suffisait pas : `view-transition-name` fait
   de la maison un contexte d'empilement, et le panneau, qui la suit, peint
   par-dessus tout ce qu'elle contient. C'est donc la MAISON qui passe devant.
   Sauf pendant un aperçu : la piece couvre alors la vue, maison comprise. */
html:not([data-apercu]):not([data-geste]) .maison {
  position: relative;
  z-index: 1;
}

/* LA GOUTTIERE EST PORTEE PAR CHAQUE ETAGE, PAS PAR LA MAISON. Posee en
   retrait sur `.maison`, elle enfermait la rangee d'onglets dans la colonne :
   une marge negative ne la rendait pas, puisque `--gouttiere` se compte en
   `100%` et qu'un pourcentage de marge se lit sur le CONTENU de la maison,
   deja retire. Mesure a 1024 px : rangee de 32 a 958 pour une fenetre de
   1009, les onglets coupes a 25 px du bord gauche et 51 du droit. La maison
   va donc d'un bord a l'autre, et chacun reprend l'axe lui-meme. */
.maison__logo,
.maison__baseline { margin-inline: var(--gouttiere); }

/* Le nom se DESSINE : les contours du mot dans la typo Epoque Classique, en
   data: (la typo ne part pas en ligne). Ecrit par outils/logo/vecto.py entre
   les deux reperes — ne pas retoucher a la main. Le lien garde son texte et
   sa boite d'hier (rien ne bouge en dessous, le nom reste lu par les lecteurs
   d'ecran) ; seule son encre passe au dessin, en masque sur currentColor,
   donc la peau qui colore le lien colore le logo. */
.maison__logo {
  display: block;
  position: relative;
  font-family: var(--mono);
  font-weight: 700;
  font-size: clamp(17px, 1.6vw, 21px);
  letter-spacing: 0.14em;
  -webkit-text-fill-color: transparent;
}
.maison__logo::before {
  --logo: /* LOGO-DEBUT */ url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='360 -4784 23136 5032'%3E%3Cpath d='M360 -4784V0H3616V-720H1080V-2032H3256V-2752H1080V-4064H3616V-4784ZM4516 -4784V0H5236V-2008H6384Q6612 -2008 6822.0 -2078.0Q7032 -2148 7204.0 -2276.0Q7376 -2404 7504.0 -2576.0Q7632 -2748 7702.0 -2958.0Q7772 -3168 7772 -3396Q7772 -3624 7702.0 -3834.0Q7632 -4044 7504.0 -4216.0Q7376 -4388 7204.0 -4516.0Q7032 -4644 6822.0 -4714.0Q6612 -4784 6384 -4784ZM5236 -4064H6384Q6524 -4064 6644.0 -4012.0Q6764 -3960 6856.0 -3868.0Q6948 -3776 7000.0 -3656.0Q7052 -3536 7052 -3396Q7052 -3256 7000.0 -3136.0Q6948 -3016 6856.0 -2924.0Q6764 -2832 6644.0 -2780.0Q6524 -2728 6384 -2728H5236ZM9940 -4784Q9672 -4784 9428.0 -4702.0Q9184 -4620 8980.0 -4470.0Q8776 -4320 8626.0 -4116.0Q8476 -3912 8394.0 -3668.0Q8312 -3424 8312 -3156V-1628Q8312 -1360 8394.0 -1116.0Q8476 -872 8626.0 -668.0Q8776 -464 8980.0 -314.0Q9184 -164 9428.0 -82.0Q9672 0 9940 0Q10208 0 10452.0 -82.0Q10696 -164 10900.0 -314.0Q11104 -464 11254.0 -668.0Q11404 -872 11486.0 -1116.0Q11568 -1360 11568 -1628V-3156Q11568 -3424 11486.0 -3668.0Q11404 -3912 11254.0 -4116.0Q11104 -4320 10900.0 -4470.0Q10696 -4620 10452.0 -4702.0Q10208 -4784 9940 -4784ZM9940 -4064Q10128 -4064 10292.0 -3994.0Q10456 -3924 10582.0 -3798.0Q10708 -3672 10778.0 -3508.0Q10848 -3344 10848 -3156V-1628Q10848 -1440 10778.0 -1276.0Q10708 -1112 10582.0 -986.0Q10456 -860 10292.0 -790.0Q10128 -720 9940 -720Q9752 -720 9588.0 -790.0Q9424 -860 9298.0 -986.0Q9172 -1112 9102.0 -1276.0Q9032 -1440 9032 -1628V-3156Q9032 -3344 9102.0 -3508.0Q9172 -3672 9298.0 -3798.0Q9424 -3924 9588.0 -3994.0Q9752 -4064 9940 -4064ZM13916 -4784Q13648 -4784 13404.0 -4702.0Q13160 -4620 12956.0 -4470.0Q12752 -4320 12602.0 -4116.0Q12452 -3912 12370.0 -3668.0Q12288 -3424 12288 -3156V-1628Q12288 -1360 12370.0 -1116.0Q12452 -872 12602.0 -668.0Q12752 -464 12956.0 -314.0Q13160 -164 13404.0 -82.0Q13648 0 13916 0Q14140 0 14348.0 -58.0Q14556 -116 14736 -220L15204 248L15716 -264L15264 -712Q15396 -908 15470.0 -1140.0Q15544 -1372 15544 -1628V-3156Q15544 -3424 15462.0 -3668.0Q15380 -3912 15230.0 -4116.0Q15080 -4320 14876.0 -4470.0Q14672 -4620 14428.0 -4702.0Q14184 -4784 13916 -4784ZM13916 -4064Q14104 -4064 14268.0 -3994.0Q14432 -3924 14558.0 -3798.0Q14684 -3672 14754.0 -3508.0Q14824 -3344 14824 -3156V-1628Q14824 -1524 14802.0 -1426.0Q14780 -1328 14736 -1240L14376 -1604L13864 -1092L14196 -764Q14060 -720 13916 -720Q13728 -720 13564.0 -790.0Q13400 -860 13274.0 -986.0Q13148 -1112 13078.0 -1276.0Q13008 -1440 13008 -1628V-3156Q13008 -3344 13078.0 -3508.0Q13148 -3672 13274.0 -3798.0Q13400 -3924 13564.0 -3994.0Q13728 -4064 13916 -4064ZM16264 -4784V-1628Q16264 -1360 16346.0 -1116.0Q16428 -872 16578.0 -668.0Q16728 -464 16932.0 -314.0Q17136 -164 17380.0 -82.0Q17624 0 17892 0Q18160 0 18404.0 -82.0Q18648 -164 18852.0 -314.0Q19056 -464 19206.0 -668.0Q19356 -872 19438.0 -1116.0Q19520 -1360 19520 -1628V-4784H18800V-1628Q18800 -1440 18730.0 -1276.0Q18660 -1112 18534.0 -986.0Q18408 -860 18244.0 -790.0Q18080 -720 17892 -720Q17704 -720 17540.0 -790.0Q17376 -860 17250.0 -986.0Q17124 -1112 17054.0 -1276.0Q16984 -1440 16984 -1628V-4784ZM20240 -4784V0H23496V-720H20960V-2032H23136V-2752H20960V-4064H23496V-4784Z'/%3E%3C/svg%3E") /* LOGO-FIN */;
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  translate: 0 -50%;
  height: 0.8em;
  aspect-ratio: 4.6;
  background: currentColor;
  -webkit-mask: var(--logo) no-repeat left center / contain;
  mask: var(--logo) no-repeat left center / contain;
}

/* La signature de la maison. Elle se lit sous le nom, a la meme echelle que
   le reste du texte : c'est une mention, pas une accroche. */
.maison__baseline {
  font-family: var(--grotesk);
  font-size: 0.94em;
  margin-block: 7px 0;   /* les cotes sont la gouttiere, posee plus haut */
}

/* LA BASCULE DE JOUR, au bout de la signature (cf. `bascule`, epoque.js).
   Pas de boite : un signe a la taille du texte, a son encre, et une cible
   plus large que son trace pour le doigt. L'icone montre la destination. */
.bascule {
  display: inline-grid;
  place-items: center;
  width: 2em;
  height: 2em;
  margin: -0.5em 0 -0.5em 0.3em;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  vertical-align: middle;
  cursor: pointer;
}
.bascule__icone { width: 1.05em; height: 1.05em; fill: currentColor; }
.bascule__icone--sombre { display: none; }
:root[data-theme="clair"] .bascule__icone--clair  { display: none; }
:root[data-theme="clair"] .bascule__icone--sombre { display: block; }

/* ---------------------------------------------------------
   La bande — les objets mis en avant, en defilement continu
   Elle est a l'etage de la maison, donc en noir et blanc :
   aucune peau d'univers ne la touche. Elle s'etend jusqu'aux
   bords de la colonne, la ou le panneau commence plus bas —
   les deux surfaces ont la meme largeur, ce qui les fait lire
   comme deux etages du meme meuble.
   --------------------------------------------------------- */

.bande {
  /* Le vide entre deux colonnes, et ce que le nom a le droit d'y prendre
     avant de passer a la ligne : un peu moins de la moitie, pour qu'il reste
     du blanc entre deux noms qui debordent tous les deux. */
  --bande-gouttiere: clamp(20px, 3vw, 40px);
  --bande-tolerance: calc(var(--bande-gouttiere) * 0.35);
  /* Les marges negatives annulent le retrait de l'en-tete : la bande occupe
     exactement la boite du panneau, plus bas. Aucun retrait interieur — sur
     une bande qui defile, un objet cale a 64 px du bord ne le reste pas une
     seconde, et la seule chose qui compte est l'endroit ou il disparait. */
  margin: clamp(22px, 4vh, 40px) 0 0;
  /* Ce qui sort par les cotes doit disparaitre, pas repousser la page. */
  overflow: hidden;
  /* Les objets s'effacent dans le blanc au lieu d'etre tranches net : une
     coupe franche dit « boite », un fondu dit « ca continue ». Le fondu vaut
     dans les deux sens — on doit sentir qu'il en arrive autant qu'il en part. */
  -webkit-mask-image: linear-gradient(to right,
      transparent, #000 clamp(28px, 5vw, 76px),
      #000 calc(100% - clamp(28px, 5vw, 76px)), transparent);
          mask-image: linear-gradient(to right,
      transparent, #000 clamp(28px, 5vw, 76px),
      #000 calc(100% - clamp(28px, 5vw, 76px)), transparent);
}

.bande {
  /* On l'attrape pour la faire glisser : a la souris comme au doigt. Le
     defilement vertical de la page reste au navigateur (pan-y), le geste
     horizontal est a la bande. */
  touch-action: pan-y;
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
}
.bande[data-tenue] { cursor: grabbing; }
.bande img { -webkit-user-drag: none; }

.bande__piste {
  display: flex;
  width: max-content;
  /* La position est posee par le JS a chaque image (cf. epoque.js, bande) :
     la vitesse ne tombe pas d'un coup au survol, elle ralentit, et la bande
     se tire a la main. Une animation CSS ne sait faire ni l'un ni l'autre. */
  will-change: transform;
}

.bande__groupe { display: flex; }

.bande__objet {
  /* Largeur fixe, et c'est delibere : elle rend la mesure du groupe
     independante du chargement des images. Le JS peut calculer le saut des
     la construction, sans attendre le moindre octet. */
  flex: 0 0 clamp(108px, 11vw, 148px);
  padding-right: var(--bande-gouttiere);
  /* Et la largeur declaree fait loi. Sans ceci, `min-width: auto` — la regle
     par defaut d'un element flex — rend a la colonne la largeur de son plus
     long mot : c'etait le NOM qui dictait la taille de l'objet, un titre en
     trois mots elargissait sa case de trente pour cent, et le saut mesure au
     montage ne valait plus rien. La colonne se declare, le texte s'y range. */
  min-width: 0;
}

.bande__visuel {
  height: clamp(74px, 9vh, 104px);
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.bande__visuel img {
  max-height: 100%;
  width: auto;
  object-fit: contain;
  filter: drop-shadow(
    calc(var(--light-x) * 14px)
    calc(var(--light-y) * 14px)
    22px
    rgba(0, 0, 0, 0.26)
  );
}

.bande__nom {
  font-family: var(--mono);
  font-size: 0.72em;
  letter-spacing: 0.04em;
  margin: 10px 0 0;
  /* Le nom est sous l'objet, l'objet est au milieu de sa colonne : le nom
     aussi. Cale a gauche, il ne nommait plus ce qu'il y a au-dessus. */
  text-align: center;
  /* Un nom trop long passe a la ligne — il ne pousse pas sa colonne et il ne
     se coupe pas en plein mot. La TOLERANCE est la pour le cas d'a cote :
     celui qui manque la colonne de quelques pixels. Plutot que de casser sa
     ligne pour si peu, il deborde dans la gouttiere — de part et d'autre,
     puisqu'il reste centre, et la gouttiere est le meme vide des deux cotes.
     Au-dela, il va bien a la ligne. */
  width: calc(100% + var(--bande-tolerance) * 2);
  margin-left: calc(var(--bande-tolerance) * -1);
  overflow-wrap: break-word;
}

/* Sans mouvement, la bande ne defile plus d'elle-meme mais se tire toujours
   a la main — c'est le JS qui lit la preference (cf. epoque.js, bande). */

/* ---------------------------------------------------------
   Les onglets — l'encoche taillee dans la feuille du rayon
   Ce ne sont pas des liens soulignes poses au-dessus d'un
   panneau : l'onglet ouvert EST le panneau, remonte de
   quelques centimetres. Meme feuille, meme motif, meme bord
   qui en fait le tour — et le trait du dessous s'interrompt
   sous l'encoche, parce qu'il n'y a rien a fermer la ou les
   deux ne font qu'un.

   UN ONGLET GARDE SA MATIERE, OUVERT OU RANGE. Une feuille
   rangee derriere reste la meme feuille : elle ne devient pas
   du texte parce qu'on regarde ailleurs. Chaque onglet porte
   donc l'univers du rayon qu'il ouvre — son fond, son motif,
   sa lettre — et le JS le lui dit en posant `data-peau` sur
   lui (voir Epoque.chrome). La maison ne nomme aucun univers :
   un onglet sans peau declaree prend le papier blanc et
   l'arete noire d'Epoque, donc exactement l'onglet d'hier.
   Ce qui distingue l'ouvert, ce n'est plus sa matiere — c'est
   son bord ferme et le pixel par lequel il mord le panneau.

   ET SA MAILLE EST CELLE DU PANNEAU, DES LA PREMIERE IMAGE. Elle
   ne s'attend plus : elle se DECLARE (`--tuile`, plus haut), et
   les deux pieces la lisent dans la meme feuille. L'onglet
   attendait que le sol la publie — donc au mieux un tour de
   boucle, et le carrelage s'allumait sous l'oeil a chaque
   arrivee. Ce qui reste a attendre n'est plus la maille, c'est
   la MATIERE : le fichier decode. Jusque-la le fond CSS tient la
   place, a cette maille et a cette phase ; la toile prend ensuite
   le relais par-dessus lui, et il reste dessous (la tuile est
   opaque, cf. peau-chat-man.css). Ce qui change en route est la
   nettete, pas le dessin.
   --------------------------------------------------------- */

.onglets {
  display: flex;
  align-items: flex-end;
  gap: 0;
  /* La rangee ne passe JAMAIS a la ligne. Un onglet renvoye a la ligne du
     dessus n'a plus rien sous lui : l'encoche flotte au-dessus du blanc et
     la soudure avec le panneau est perdue — c'est tout ce qui fait qu'un
     onglet est un onglet. Trop d'onglets pour la fenetre, ils defilent
     lateralement plutot que de casser la ligne. */
  flex-wrap: nowrap;
  overflow-x: auto;
  overflow-y: hidden;
  /* ET CE QUI DEBORDE NE DEBORDE PAS SUR LE NAVIGATEUR. Un balayage qui
     atteint le bout d'une boite se propage a son parent, et sur telephone
     le dernier parent est le navigateur lui-meme : le geste de retour. Une
     rangee qu'on pousse jusqu'a son dernier onglet quittait donc la page.
     Mesure a 375 px : la rangee fait 344 px pour 291 de large, la ligne 295
     pour 278 — les deux debordent pour de bon. `contain` garde le geste chez
     elle ; la ou rien ne deborde, il n'y a rien a garder et rien ne change.
     Meme mot que `.case--texte`, qui le disait deja pour la verticale. */
  overscroll-behavior-x: contain;

  /* L'encoche descend d'un pixel dans le panneau : sans cette place reservee,
     `overflow` la trancherait au bord de la boite. Le retrait est ensuite
     annule par la marge, la rangee garde donc exactement la meme hauteur.
     Deux pixels : la languette ouverte deborde d'un de plus, dans la bande
     (cf. `.onglet[aria-current="page"]::before`). */
  padding-bottom: 2px;
  /* La barre de defilement n'a rien a dire ici : le fondu du bord suffit. */
  scrollbar-width: none;
  /* La rangee a la largeur de la fenetre, et elle y finit comme elle y
     commence : autant de vide a droite qu'a gauche (cf. les marges `auto`
     plus bas, qui retombent a zero quand elle deborde — un `center` couperait
     le debut d'un scrollport). */
  margin: clamp(26px, 4.5vh, 48px) 0 -2px;
  /* Un peu d'air de chaque cote, aussi au bout du defilement. */
  padding-inline: calc(1px * var(--onglet-marge));
  /* L'encoche descend d'un pixel dans le panneau : c'est ce pixel qui
     soude les deux pieces. Il faut donc que la rangee passe DEVANT. */
  position: relative;
  z-index: 1;
}

.onglets::-webkit-scrollbar { display: none; }
.onglets > :first-child { margin-left: auto; }
.onglets > :last-child { margin-right: auto; }

.onglet {
  /* Un onglet ne se laisse pas comprimer par ses voisins : la rangee defile,
     elle ne serre pas — et il ne s'etire pas non plus. */
  flex: 0 0 auto;
  font-family: var(--grotesk);
  font-size: 1.02em;
  padding: 8px var(--onglet-flanc) 9px;
  /* Le bord existe pour tous et n'apparait que quand il a lieu d'etre :
     rien ne bouge quand un onglet s'ouvre ou qu'on le survole. */
  border: 1px solid transparent;
  border-bottom: 0;
  /* Le haut s'emousse, le bas reste droit : c'est par lui que l'encoche se
     soude au panneau, et un coin arrondi y laisserait passer le blanc. */
  border-radius: var(--onglet-arrondi) var(--onglet-arrondi) 0 0;
  /* La matiere de SON rayon, pas celle de la page ou l'on est. Ces deux
     valeurs viennent du `data-peau` de l'onglet quand il en porte un, de la
     racine sinon — une peau (peau-*.css) pose son motif par-dessus. */
  background-color: var(--rayon-fond);
  background-origin: border-box;
  background-repeat: repeat;
  background-size: var(--tuile) var(--tuile);
  /* Sa PHASE, elle, ne se declare pas ici : un onglet n'est pas au milieu du
     panneau, et le reseau qu'il doit suivre est celui du panneau (cf.
     Epoque.sertit, qui pose le decalage en ligne). Ce qu'on declare, c'est la
     maille — la meme des la premiere image, sans attendre personne. */
  /* Le pixel qui descend dans le panneau et soude les deux pieces. */
  margin-bottom: -1px;
  position: relative;
  /* La feuille de l'encoche se PEINT, comme celle du panneau (sol.js,
     `carreau`) : sa toile se pose en z-index negatif, et il faut donc que
     l'onglet soit un contexte d'empilement — sinon elle passerait derriere
     son fond, qui la cacherait. Meme raison, meme remede que `.page`. */
  isolation: isolate;
}

/* LA FEUILLE DE L'ENCOCHE — peinte par le sol, pas repetee par le fond.

   Elle se compte depuis le coin de la BORDURE, la ou un fond se comptait
   (`background-origin: border-box`) : le filet qui ferme l'encoche porte le
   motif comme le reste. Le retrait vaut donc l'epaisseur du filet, et rien
   en bas ou il n'y en a pas (`border-bottom: 0`).

   La forme est celle de l'onglet, et elle est declaree la-haut : le sol ne
   dessine pas un contour, il remplit celui qu'on lui donne. */
.onglet__sol {
  position: absolute;
  /* Le coin de la BORDURE : la boite d'un enfant absolu part du coin de
     REMPLISSAGE, on remonte donc de l'epaisseur du filet. */
  top: -1px;
  left: -1px;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  /* SA TAILLE VIENT DU JS, ET C'EST LA CONDITION DE LA NETTETE. Un canvas
     est un element REMPLACE : `width: auto` lui rend sa taille intrinseque
     et `right`/`bottom` sont alors ignores (mesure : 150 px de large pour
     une encoche de 120). Mais on ne peut pas non plus lui poser la taille
     exacte de l'encoche — elle tombe sur un pixel fractionnaire (119,64), et
     un canvas dont la toile ne fait pas un nombre entier de pixels d'ecran
     est REECHANTILLONNE par le navigateur : le flou reviendrait par la porte
     par laquelle on vient de le sortir. Le sol lui donne donc un nombre
     entier de pixels d'ecran et la taille CSS qui lui correspond exactement
     (sol.js, `Carreau.peint`), quitte a depasser d'un demi-pixel. */
}


/* LE SIGNE D'UN ONGLET (`rayons[].logo`, pose par epoque.js). Sa place est
   DECLAREE - un carre a la hauteur de la lettre - et l'image s'y loge sans
   la deformer : l'onglet a donc sa largeur a la premiere image, que le
   fichier soit arrive ou non, et l'encoche se mesure une fois. En `em` :
   il suit la lettre de son onglet, quel que soit l'univers qui la compose.
   Sans logo, ni l'un ni l'autre de ces noeuds n'existe. */
.onglet__logo {
  display: inline-block;
  width: var(--onglet-logo, 1.15em);
  height: var(--onglet-logo, 1.15em);
  margin-right: 0.45em;
  vertical-align: -0.22em;
  object-fit: contain;
}

.onglet:has(> .onglet__logo) { padding-left: var(--onglet-flanc-logo); }

.onglet__nom { display: inline-block; white-space: nowrap; }

/* LES LANGUETTES RANGEES SE CHEVAUCHENT, comme dans un classeur : chacune
   glisse sous la suivante, et l'ouverte passe devant toutes. Une languette
   rangee n'est pas RACCOURCIE - elle est COUVERTE : elle garde son nom
   entier, et c'est sa marge droite qui la rentre sous la suivante, de tout
   ce qui depasse `--onglet-texte`. La longueur du nom se mesure (`--nom-l`,
   posee par epoque.js, `replie`) ; ce qu'on en montre se declare ici, et
   le curseur du banc la suit sans script. `--onglet-recouvre` mord en plus
   sur le bord. L'ordre de peinture fait le reste : a egalite, la suivante
   dans le document passe devant. La derniere n'a personne pour la couvrir :
   elle ne rentre rien.
   Ce qu'elle montre se SERRE sur un ecran etroit : `--onglet-serre` (pose
   par `replie` sur la rangee, seulement quand elle ne tient pas) dit ce
   qu'il faudrait montrer pour tenir ; la feuille le borne entre ses deux
   declarations. Sans JS, ou quand tout tient, c'est `--onglet-texte`. */
.onglets > .onglet + .onglet {
  margin-left: calc(-1px * var(--onglet-recouvre));
}
.onglets > .onglet:not([aria-current="page"]):not(:last-child) {
  --onglet-montre: clamp(var(--onglet-texte-min), var(--onglet-serre, var(--onglet-texte)), var(--onglet-texte));
  margin-right: calc(-1 * max(0px, var(--nom-l, 0px) - 1em * var(--onglet-montre)));
}
.onglet[aria-current="page"] { z-index: 1; }

/* L'OMBRE DU CHEVAUCHEMENT. Chaque languette pose la sienne sur celle
   qu'elle couvre, et seulement sur elle : a gauche pour une rangee (la
   precedente passe dessous), des deux cotes pour l'ouverte, qui couvre ses
   deux voisines. La premiere ne couvre personne.
   C'est l'ombre de la LANGUETTE ELLE-MEME : une copie de sa forme
   (`border-radius: inherit`), qui porte l'ombre sans etalement — une ombre
   a etalement negatif se resserre aussi en hauteur et ne laissait qu'une
   tache au milieu du flanc ; une bande droite oubliait l'arrondi. Elle
   longe donc le flanc et tourne avec le coin. `clip-path` ne garde que le
   cote qui couvre — jusqu'au bout de l'arrondi, sans quoi l'ombre du coin
   etait coupee droite — et rien au-dessus de la rangee : l'ombre ne se
   pose que sur les languettes, jamais sur le fond. Elle est remontee
   (`--ombre-y`) pour monter jusqu'en haut de la languette couverte. */
.onglets > .onglet {
  position: relative;
  --ombre-c: color-mix(in srgb, var(--noir, #000) calc(var(--onglet-ombre-force) * 100%), transparent);
  --ombre-d: calc(1px * var(--onglet-ombre));
  --ombre-y: calc(-1 * var(--ombre-d) * var(--onglet-ombre-haut));
}
.onglets > .onglet + .onglet::after,
.onglets > .onglet[aria-current="page"]::after {
  content: "";
  position: absolute;
  inset: 0 0 calc(-2 * var(--ombre-d));
  border-radius: inherit;
  pointer-events: none;
  box-shadow: calc(-.5 * var(--ombre-d)) var(--ombre-y) var(--ombre-d) var(--ombre-c);
  clip-path: inset(0 calc(100% - var(--onglet-arrondi)) calc(2 * var(--ombre-d)) calc(-3 * var(--ombre-d)));
}
.onglets > .onglet[aria-current="page"]::after {
  box-shadow: calc(-.5 * var(--ombre-d)) var(--ombre-y) var(--ombre-d) var(--ombre-c),
              calc(.5 * var(--ombre-d)) var(--ombre-y) var(--ombre-d) var(--ombre-c);
  clip-path: inset(0 calc(-3 * var(--ombre-d)) calc(2 * var(--ombre-d)));
}
.onglets > .onglet[aria-current="page"]:first-child::after {
  box-shadow: calc(.5 * var(--ombre-d)) var(--ombre-y) var(--ombre-d) var(--ombre-c);
  clip-path: inset(0 calc(-3 * var(--ombre-d)) calc(2 * var(--ombre-d)) calc(100% - var(--onglet-arrondi)));
}
.onglets > .onglet[aria-current="page"]:last-child::after {
  box-shadow: calc(-.5 * var(--ombre-d)) var(--ombre-y) var(--ombre-d) var(--ombre-c);
  clip-path: inset(0 calc(100% - var(--onglet-arrondi)) calc(2 * var(--ombre-d)) calc(-3 * var(--ombre-d)));
}

/* ET L'ONGLET OUVERT PORTE OMBRE SUR LES AUTRES. Son panneau passe devant
   les languettes rangees : son bord haut projette une ombre qui monte sur
   leur pied. Elle est peinte DANS chaque languette (ombre interieure), donc
   elle ne se pose que sur elles, jamais sur le fond. */
.onglets > .onglet:not([aria-current="page"]) {
  --ombre-p: calc(1px * var(--onglet-ombre-pied));
  /* Le flou est celui de toutes les ombres d'onglet (`--ombre-d`) : la
     hauteur ne fait que TRANSLATER le bord, le degrade garde sa largeur. */
  /* Le creux deborde les cotes et le haut de 1,5 flou : rien ne revient
     par la, meme a 0. Le degrade finit a P du pied. */
  box-shadow: inset 0 calc(-1 * (var(--ombre-p) + .5 * var(--ombre-d))) var(--ombre-d) calc(-1.5 * var(--ombre-d)) var(--ombre-c);
}

/* UN SIGNE A L'ENCRE (`rayons[].logoEncre`) SUIT L'ENCRE DE LA PAGE. Un
   signe noir sur transparent disparaitrait de nuit, ou le papier est noir :
   il s'inverse avec elle. Un signe en couleurs ne le declare pas, et ne
   bouge pas. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="clair"]) .onglet__logo--encre { filter: invert(1); }
}

/* TOUTES LES LANGUETTES ONT LEUR CONTOUR, rangees comme ouverte. Depuis
   qu'elles se chevauchent, c'est le trait qui dit ou l'une finit et ou la
   suivante commence ; sans lui, deux languettes de meme matiere se fondaient
   en une. Le bord existait deja pour tous (transparent) : il prend sa
   couleur, et rien ne change de place. Le bas reste ouvert (`border-bottom:
   0`) : seule l'ouverte se soude au panneau. */
.onglet {
  border-color: var(--rayon-bord);
}

/* SEULE LA LANGUETTE OUVERTE DESCEND DANS LE TRAIT. La maison passe devant
   le panneau (cf. `.maison`) : un onglet ferme qui y descendrait couvrirait
   le trait de sa couleur. Il s'arrete donc au bord, et le pixel qu'il ne
   prend plus en bas, il le rend a son retrait — meme haut, meme lettre, la
   meme part visible qu'avant, quand le trait passait sur sa derniere rangee. */
.onglet:not([aria-current="page"]) {
  margin-bottom: 0;
  padding-bottom: 8px;
}

/* ET SON BORD BAS TOMBE DANS SA PROPRE COULEUR. Le haut du panneau tombe
   sur une fraction de pixel (1, 1,25, 1,5) : la derniere rangee de la
   languette n'en couvre qu'une part, et l'autre laisse voir le dessous.
   L'interieur descend donc d'un pixel de plus, dans la bande, qui est sa
   couleur (`poseLaBande` la lit sur elle) : le bord a demi couvert est
   couleur sur couleur, il n'y a plus rien a voir, quel que soit l'arrondi.
   Il ne couvre que l'INTERIEUR, et ne le dit pas par un nombre : il prend la
   boite de bordure de l'onglet (`left`/`right` a moins un bord) avec le meme
   bord, transparent, et son fond s'arrete au bord (`padding-box`). Memes
   nombres, meme arrondi au pixel d'ecran : a densite 1,25 une boite posee a
   0 de l'interieur s'arrondissait pour son compte et mordait la colonne du
   bord. Sous les bords, c'est donc le trait qui continue, couleur de bord
   sur couleur de bord.
   `::before` et pas `::after` : le `::after` d'un onglet appartient a sa
   lettre (le second trace, `[data-contour]`, typo-chat-man-og.css) — le
   prendre effacait le liseré brun de Chat-Man ouvert. */
.onglet[aria-current="page"]::before {
  content: "";
  position: absolute;
  left: -1px;
  right: -1px;
  bottom: -1px;
  height: 2px;
  border: solid transparent;
  border-width: 0 1px;
  background-color: inherit;
  background-clip: padding-box;
  pointer-events: none;
}

/* UNE ENCOCHE PEUT PORTER UNE COULEUR A ELLE. Son rayon la declare
   (`rayons[].encoche.couleur`, config.json) et epoque.js la pose en
   `--encoche` : un aplat, sans le motif de la peau ni la toile du sol qui le
   peint (la tuile est opaque, elle couvrirait la couleur). La lettre, le
   bord et la soudure au panneau restent ceux de l'univers. Plus specifique
   que la peau, qui se lit apres cette feuille. */
.onglets .onglet[data-encoche] {
  background-color: var(--encoche);
  background-image: none;
}
.onglets .onglet[data-encoche] .onglet__sol { display: none; }

/* ET SA LETTRE SUIT L'APLAT, PAS LA PAGE. Noire sur un aplat clair, blanche
   sur un sombre — de jour comme de nuit : une couleur ne s'inverse pas avec
   le papier. epoque.js la deduit de la luminance (`encreSur`) et la pose en
   `--encoche-encre`. Meme poids que la voix d'un univers (`.onglet[data-peau]`,
   lue apres cette feuille) : une chartre qui porte son liseré garde le
   dernier mot. Le signe a l'encre suit la lettre, et non plus la nuit. */
.onglet[data-encoche] { color: var(--encoche-encre, inherit); }
.onglets .onglet[data-encoche] .onglet__logo--encre { filter: none; }
.onglets .onglet[data-encoche-sombre] .onglet__logo--encre { filter: invert(1); }

/* Le son ferme la ligne des onglets, a l'autre bout. */
.onglets__son {
  margin-left: auto;
  align-self: center;
  font-family: var(--mono);
  font-size: 0.8em;
  letter-spacing: 0.1em;
  background: none;
  border: 0;
  padding: 0 0 10px;
  cursor: pointer;
  color: inherit;
}

/* ---------------------------------------------------------
   Titres
   --------------------------------------------------------- */

.titre {
  font-family: var(--mono);
  font-weight: 400;
  font-size: clamp(11px, 1vw, 13px);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  margin: 0 0 clamp(28px, 5vh, 56px);
}

/* UN RAYON NE DIT PAS SON NOM DEUX FOIS. Il est ecrit sur son onglet, et
   l'onglet est allume : le repeter juste dessous n'apprend rien et coupe la
   feuille d'un titre qui ne titre rien.

   Il sort de la VUE, pas de la page : le document garde son h1, et ce qui lit
   la page a voix haute garde de quoi annoncer ou l'on est. D'ou l'absolu — un
   titre qui se contenterait d'etre transparent tiendrait encore sa place et
   sa marge, et la feuille commencerait cinquante pixels trop bas.

   `data-onglet` est pose par le JS sur les pages qui rendent un rayon
   (cf. epoque.js, chrome). Les pages de la maison n'en portent pas : aucun
   onglet ne dit leur nom, leur titre reste. */
[data-onglet] .titre {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: 0;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.section__titre {
  font-family: var(--mono);
  font-weight: 400;
  font-size: clamp(11px, 1vw, 13px);
  letter-spacing: 0.22em;
  text-transform: uppercase;
  margin: clamp(64px, 12vh, 120px) 0 clamp(24px, 4vh, 40px);
}

.prose {
  max-width: 62ch;
  font-size: 1.05em;
}
.prose p { margin: 0 0 1.2em; }

/* UNE IMAGE DANS LA PROSE. Elle tient dans la colonne de texte et pas au-dela
   — 62ch sur une fiche, 46ch dans la bande ouverte. Une image qui deborderait
   sa colonne demanderait des marges negatives, et la bande ouverte, elle, n'a
   pas la place de les rendre.

   Pas de cadre, pas de fond, pas de bordure : la meme ombre portee que les
   objets du catalogue, sous la meme lumiere. Ce qui pose l'image dans la
   page, c'est l'ombre, pas un trait. */
.prose__figure {
  margin: clamp(28px, 4vh, 44px) 0;
  max-width: 100%;
}
.prose__figure img {
  display: block;
  max-width: 100%;
  height: auto;
  box-shadow:
    calc(var(--light-x) * 6px) calc(var(--light-y) * 6px) 12px -4px rgba(0, 0, 0, 0.22),
    calc(var(--light-x) * var(--sh-dist)) calc(var(--light-y) * var(--sh-dist))
      var(--sh-blur) -18px rgba(0, 0, 0, 0.34);
}

/* La legende n'est pas du texte courant : elle nomme, comme un prix ou un
   titre d'objet. Donc Space Mono, petit, aligne sur le bord gauche de
   l'image — jamais centre sous elle, rien n'est centre ici. */
.prose__legende {
  font-family: var(--mono);
  font-size: 0.72em;
  letter-spacing: 0.04em;
  margin-top: 0.7em;
  opacity: 0.62;
}

/* Dans la bande ouverte, la prose se tient en 46ch et la piece fait un ecran :
   une image y prend toute la largeur du texte, et pas davantage. */
.rang--ouvert .rang__texte .prose__figure {
  margin: clamp(18px, 2.6vh, 28px) 0;
}

/* ---------------------------------------------------------
   Le catalogue — objets qui flottent
   Pas de carte, pas de bordure, pas de fond : l'ombre portee
   est la seule chose qui pose l'objet dans l'espace.
   --------------------------------------------------------- */

.vitrine {
  display: grid;
  gap: clamp(32px, 6vw, 72px) clamp(24px, 4vw, 56px);
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}

/* A la une : trois objets, sans scroll sur desktop. La hauteur du visuel est
   bornee au viewport moins la nav et le bloc de texte sous l'objet. */
.vitrine--une { grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); }
.vitrine--une .objet__visuel { height: min(46vh, 380px); }

.objet { display: block; }

.objet__visuel {
  height: 220px;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  margin-bottom: 20px;
}

.objet__visuel img {
  max-height: 100%;
  width: auto;
  object-fit: contain;
  /* L'ombre suit le detourage du PNG, pas sa boite : c'est ce qui fait
     flotter l'objet au lieu de le poser sur une vignette. */
  filter: drop-shadow(
    calc(var(--light-x) * var(--sh-dist))
    calc(var(--light-y) * var(--sh-dist))
    var(--sh-blur)
    rgba(0, 0, 0, 0.28)
  );
  transition: transform 260ms ease;
}
.objet:hover .objet__visuel img { transform: translateY(-6px); }

/* Repli quand le visuel n'existe pas encore : un trait, pas une boite grise. */
.objet__vide {
  width: 100%;
  height: 100%;
  border-bottom: 1px solid var(--encre);
}

.objet__nom {
  font-family: var(--mono);
  font-size: 0.95em;
  margin: 0 0 4px;
}

.objet__prix {
  font-family: var(--mono);
  font-size: 0.95em;
  margin: 0;
}

.objet__statut {
  font-family: var(--mono);
  font-size: 0.72em;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  margin: 6px 0 0;
}

/* Le prix de la fiche produit : 52 px, c'est lui l'argument. */
.prix--grand {
  font-family: var(--mono);
  font-size: clamp(34px, 5vw, 52px);
  line-height: 1;
  margin: 0;
}

/* ---------------------------------------------------------
   Boutons — un mot souligne, rien de plus
   --------------------------------------------------------- */

.bouton {
  font-family: var(--mono);
  font-size: 0.9em;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--encre);
  padding: 0 0 3px;
  cursor: pointer;
  color: inherit;
  display: inline-block;
}
.bouton:hover { opacity: 0.55; }

/* ---------------------------------------------------------
   Le hub — ou aller, et rien d'autre
   Des noms qui flottent, en Mono : pas de cartes, pas de cases,
   pas de bordure. Le premier groupe tient sous l'etage du haut,
   donc son titre ne prend pas la grande marge d'une section.
   --------------------------------------------------------- */

/* LA PLANETE EST ANCREE, ELLE NE SE RANGE PAS. Elle etait une colonne de
   la grille, large de l'astre ET de la place gardee pour ses satellites :
   changer le plan d'une orbite deplacait l'astre (22 px a 1440 de large),
   et sur un ecran etroit elle passait devant la liste et la poussait de
   toute sa hauteur. Elle sort du flux : le CENTRE de l'astre se declare
   (`--planete-x`, en part de la largeur du hub ; `--planete-y`, depuis le
   haut du hub), et rien de ce qu'elle porte ne bouge ni elle, ni le texte.
   Ses satellites flottent autour, sans place gardee ; ce qui sort de la
   fenetre est coupe par le panneau (plus bas), pas par la gouttiere. */
.hub {
  /* son diametre, `--planete-d`, est a la racine : les nuages le lisent
     aussi (plus bas, `.planete`) */
  --planete-x: 67.5%;
  --planete-y: calc(var(--planete-d) / 2);
  position: relative;
}
@media (max-width: 720px) {
  .hub { --planete-x: 74%; }
}
/* Une lune sur son orbite peut sortir de l'ecran ou monter vers les
   onglets : c'est le panneau qui la coupe, comme tout ce qu'il porte (plus
   haut, « ce que porte un onglet ne sort pas de son onglet »). */

/* LE TEXTE PASSE DEVANT, ET IL GARDE SES CLICS. Sur un ecran etroit, la
   planete est a cote des noms, parfois dessous : c'est elle qui cede. La
   liste ne prend le clic que sur ses liens — ailleurs, il tombe sur
   l'astre. */
.hub__portes-liste {
  position: relative;
  z-index: 2;
  pointer-events: none;
}
.hub__portes-liste a { pointer-events: auto; }

.hub__portes-liste .section__titre:first-child { margin-top: 0; }

.hub__portes {
  list-style: none;
  margin: 0;
  padding: 0;
}
.hub__portes li + li { margin-top: 0.35em; }

.hub__porte {
  font-family: var(--mono);
  font-size: clamp(22px, 3.2vw, 40px);
  letter-spacing: 0.02em;
  line-height: 1.25;
  color: inherit;
  text-decoration: none;
}
.hub__porte:hover { opacity: 0.55; }

/* ---------------------------------------------------------
   La planete — un univers vu de loin
   Le DISQUE est la planete : le motif de la peau de l'astre, son
   fond, un modele de lumiere et l'ombre des objets de la maison.
   Il est la des la premiere image, et il RESTE. La toile de
   planete.js se pose dessus quand elle a peint (`data-peint`) et
   ne fait que lui donner sa courbure et sa rotation ; sans elle,
   c'est la planete immobile, pas un trou.

   Quatre motifs sur le diametre : c'est ce que montre la face
   visible d'un globe qui en porte huit a l'equateur (`tuiles`
   dans la donnee), donc le passage du disque a la sphere ne
   change pas la maille. La lumiere vient d'ou viennent les
   ombres (`--light-x`, `--light-y`) : le reflet est a l'oppose
   de l'ombre portee, et la toile lit les memes nombres.
   --------------------------------------------------------- */

.hub__planetes {
  position: absolute;
  top: calc(var(--planete-y) - var(--planete-d) / 2);
  left: calc(var(--planete-x) - var(--planete-d) / 2);
  width: var(--planete-d);
  display: grid;
  gap: clamp(40px, 8vh, 80px);
}

/* Le diametre d'une planete se declare a la racine : la planete le lit, et
   les nuages qui passent devant elle aussi (cumulus.js, `--cumulus-taille`
   en est un multiple) — une loi, deux lecteurs, la meme proportion sur un
   telephone et sur un bureau. */
:root { --planete-d: clamp(180px, 32vw, 400px); }

.planete {
  display: block;
  color: inherit;
  text-decoration: none;
}

.planete__astre {
  position: relative;
  display: block;
  width: var(--planete-d);
  aspect-ratio: 1;
  border-radius: 50%;
  background-color: var(--rayon-fond);
  background-image:
    radial-gradient(circle at
        calc(50% - var(--light-x) * 45%) calc(50% - var(--light-y) * 45%),
      rgba(255, 255, 255, 0.10), transparent 40%, var(--vide, #000) 100%),
    var(--tile, none);
  background-size: 100% 100%, 25% 25%;
  background-position: center, center;
  box-shadow:
    calc(var(--light-x) * 6px) calc(var(--light-y) * 6px) 12px -4px rgba(0, 0, 0, 0.22),
    calc(var(--light-x) * var(--sh-dist)) calc(var(--light-y) * var(--sh-dist))
      var(--sh-blur) -18px rgba(0, 0, 0, 0.34);
}

/* LA TOILE DEBORDE DE L'ASTRE DE CE QUE SES SATELLITES PARCOURENT — le
   debord que planete.js compte et declare. Sans satellite
   il est nul, et la toile est l'astre. Elle n'a plus de coins a arrondir : le
   disque se decoupe dans le dessin, bord lisse compris. Et elle ne prend pas
   le clic : ce qui deborde passe au-dessus des voisins sans les couvrir. */
.planete__toile {
  position: absolute;
  left: calc(var(--debord-x, 0) * -100%);
  top: calc(var(--debord-y, 0) * -100%);
  width: calc(100% + var(--debord-x, 0) * 200%);
  height: calc(100% + var(--debord-y, 0) * 200%);
  pointer-events: none;
  opacity: 0;
  /* UNE PLANETE EST DE LA LUMIERE QUI S'AJOUTE AU CIEL. Sa face cachee ne
     renvoie rien — la toile la peint noire — et `screen` ajoute ce noir au
     fond sans le changer : elle se fond dans le vide, ou dans le bleu du
     jour, comme la lune qu'on voit l'apres-midi, sans que personne ait a
     connaitre la couleur du ciel. Sur un ciel clair, la face eclairee palit
     d'autant : c'est l'atmosphere, pas un reglage. */
  mix-blend-mode: screen;
}
/* SANS FONDU. Le disque est plat et la sphere est penchee : pendant un fondu
   les deux motifs se superposent en biais, et c'est un brouillage qu'on voit.
   La toile remplace le disque d'un coup, a sa premiere image. */
.planete__astre[data-peint] .planete__toile { opacity: 1; }
/* ET LE DISQUE S'EFFACE DERRIERE ELLE. La toile couvre tout son interieur ;
   il ne se voyait plus qu'a la frange du bord, ou deux antialiasings se
   superposent — un liseré autour de la face cachee, qui la decoupait du vide
   qu'elle doit rejoindre. */
.planete__astre[data-peint] { background: none; }

/* Le nom est centre sous l'astre, sur une ligne : il deborde de part et
   d'autre d'un petit astre plutot que de pousser a droite. */
.planete__nom {
  display: block;
  text-align: center;
  white-space: nowrap;
  margin-top: clamp(16px, 2.4vh, 24px);
  font-family: var(--mono);
  font-size: clamp(11px, 1vw, 13px);
  letter-spacing: 0.22em;
  text-transform: uppercase;
}
.planete:hover .planete__nom { opacity: 0.55; }

/* ---------------------------------------------------------
   Accessibilite
   --------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ---------------------------------------------------------
   Fiche produit — un gabarit unique, pilote par ?id=
   Deux colonnes sur large : l'objet flotte a gauche, le texte
   respire a droite. Toujours aucune boite : c'est le vide qui
   separe les deux colonnes, pas un trait.
   --------------------------------------------------------- */

.fiche {
  display: grid;
  gap: clamp(32px, 5vw, 80px);
  grid-template-columns: 1fr;
  align-items: start;
}
@media (min-width: 860px) {
  .fiche { grid-template-columns: 1.15fr 1fr; }
}

.fiche__visuel {
  height: min(62vh, 520px);
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.fiche__visuel img {
  max-height: 100%;
  width: auto;
  object-fit: contain;
  /* Meme lumiere que la vitrine, a une distance plus grande : l'objet est
     plus pres du regard ici, son ombre tombe donc plus loin. */
  filter: drop-shadow(
    calc(var(--light-x) * var(--sh-dist) * 1.4)
    calc(var(--light-y) * var(--sh-dist) * 1.4)
    calc(var(--sh-blur) * 1.3)
    rgba(0, 0, 0, 0.30)
  );
}

/* Vignettes : des miniatures posees, pas une pellicule a bordures. */
.fiche__vignettes {
  display: flex;
  gap: clamp(12px, 2vw, 22px);
  flex-wrap: wrap;
  margin-top: clamp(20px, 4vh, 36px);
}
.fiche__vignette {
  width: 74px;
  height: 74px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  opacity: 0.42;
  transition: opacity 200ms ease;
}
.fiche__vignette[aria-current="true"],
.fiche__vignette:hover { opacity: 1; }
.fiche__vignette img { max-height: 100%; width: auto; }

.fiche__nom {
  font-family: var(--mono);
  font-weight: 400;
  font-size: clamp(20px, 2.2vw, 27px);
  line-height: 1.2;
  margin: 0 0 clamp(18px, 3vh, 28px);
}

.fiche__auteur {
  font-family: var(--grotesk);
  font-size: 0.9em;
  margin: -14px 0 clamp(18px, 3vh, 28px);
}

.fiche__prix { margin-bottom: clamp(22px, 4vh, 34px); }

.fiche__achat {
  display: flex;
  align-items: baseline;
  gap: clamp(14px, 2.5vw, 26px);
  flex-wrap: wrap;
  margin-bottom: clamp(26px, 5vh, 44px);
}

/* Un bouton sans destination : le mot reste, le trait disparait. Rien ne
   promet un clic qui ne menerait nulle part. */
.bouton--inerte {
  border-bottom-color: transparent;
  cursor: default;
  opacity: 0.4;
}
.bouton--inerte:hover { opacity: 0.4; }

.fiche__livraison {
  font-family: var(--mono);
  font-size: 0.72em;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.fiche__specs {
  margin: 0;
  font-family: var(--mono);
  font-size: 0.82em;
  letter-spacing: 0.04em;
}
/* Le tiret est une PUCE, pas le debut du texte : il est sorti du flux et
   le texte commence au bord du padding. Une chartre d'univers qui double
   ses lettres d'un second trace (typo-chat-man-og.css) cale sa copie sur
   la boite de rappel du li et reprend son padding — un tiret reste dans le
   flux la decalerait de sa largeur. */
.fiche__specs li {
  list-style: none;
  margin: 0 0 6px;
  position: relative;
  padding-left: 1.25em;
}
.fiche__specs li::before {
  content: "— ";
  position: absolute;
  left: 0;
}

.fiche__retour {
  font-family: var(--mono);
  font-size: 0.78em;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  display: inline-block;
  margin-top: clamp(48px, 8vh, 88px);
  border-bottom: 1px solid var(--encre);
  padding-bottom: 2px;
}

/* ---------------------------------------------------------
   Apparition — les objets se posent, ils ne clignotent pas.
   Le decalage suit l'ordre de lecture ; il est plafonne pour
   qu'une vitrine longue ne fasse pas attendre le dernier objet.
   --------------------------------------------------------- */

@keyframes epoque-pose {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

/* Elle ne joue qu'a la PREMIERE page de la session (data-entree, pose par
   le JS) : rejouee a chaque onglet, la cascade raconte un rechargement, et
   c'est justement ce qu'on veut faire oublier. Ensuite, c'est le passage
   entre documents qui porte le mouvement. */
[data-entree] .objet {
  animation: epoque-pose 620ms cubic-bezier(0.22, 0.61, 0.36, 1) backwards;
  animation-delay: calc(min(var(--i, 0), 7) * 70ms);
}

/* =========================================================
   LE PASSAGE D'UNE PAGE A L'AUTRE
   Le site est fait de vraies pages — une URL par objet, rien
   a recharger a la main, aucun routeur. Le navigateur sait
   depuis peu enchainer deux documents comme une seule scene :
   c'est lui qui anime, on ne fait que nommer ce qui persiste.
   La ou il ne sait pas, la navigation reste normale : le site
   ne repose jamais sur l'animation pour fonctionner.
   ========================================================= */

@view-transition { navigation: auto; }

/* La page qui part glisse vers le haut, celle qui vient monte a sa place :
   on tourne une page, on ne fond pas d'un ecran a l'autre. Court — 260 ms —
   parce que c'est un passage, pas un spectacle. */
@keyframes page-sort {
  to { opacity: 0; transform: translateY(-10px); }
}
@keyframes page-entre {
  from { opacity: 0; transform: translateY(14px); }
}

/* Ce qui ne change pas d'un rayon a l'autre ne doit pas se rejouer. Sans
   nom, la maison et ses onglets appartiennent a la photo de la PAGE : ils
   repartent donc avec elle et reviennent avec l'autre, et c'est ce
   va-et-vient d'elements identiques qui se lit comme un rechargement
   complet. Nommes, ils restent en place et seul le panneau change.
   Un nom doit etre unique dans la page : ces deux-la n'existent qu'en un
   exemplaire, sur toutes les pages du site. */
.maison { view-transition-name: maison; }
.page   { view-transition-name: panneau; }

/* L'ETAGE DES ONGLETS NE FOND PAS : il se pose. L'onglet clique est deja
   ouvert sur la page qui part (cf. `ouvreAuClic`, epoque.js), donc il n'y a
   rien a faire glisser — et un fondu entre deux images de la meme rangee
   ne se lisait que comme un degrade sur les languettes. La classe porte sur
   tous les onglets, quel que soit leur nom (`onglet-<rayon>`, pose par
   sertit). */
.maison, .onglets > .onglet { view-transition-class: etage; }
::view-transition-group(*.etage),
::view-transition-old(*.etage),
::view-transition-new(*.etage) { animation: none; }
::view-transition-old(*.etage) { display: none; }

/* Le fondu par defaut additionne les deux images (plus-lighter) : deux fonds
   clairs superposes donnent un eclair blanc au milieu du passage. C'est le
   clignotement. On empile normalement, l'ancienne au-dessus. */
::view-transition-old(root),
::view-transition-new(root),
::view-transition-old(panneau),
::view-transition-new(panneau) {
  mix-blend-mode: normal;
}

/* Le panneau fond sur place, court : c'est un onglet qu'on ouvre, pas une
   page qu'on tourne. */
::view-transition-group(panneau) {
  animation-duration: 260ms;
  animation-timing-function: cubic-bezier(0.22, 0.61, 0.36, 1);
}

::view-transition-old(root) {
  animation: page-sort 200ms cubic-bezier(0.4, 0, 1, 1) both;
}
::view-transition-new(root) {
  animation: page-entre 320ms cubic-bezier(0.22, 0.61, 0.36, 1) both;
}

/* L'objet, lui, ne part pas : il traverse. Le navigateur interpole sa boite
   d'une page a l'autre des que les deux visuels portent le meme nom (pose par
   Epoque.nomme). Un peu plus lent que la page, pour qu'on le SUIVE du regard
   au lieu de le retrouver arrive. */
::view-transition-group(objet-traverse) {
  animation-duration: 480ms;
  animation-timing-function: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* ---------------------------------------------------------
   Le rayon — les objets de haut en bas, visuel a gauche
   Pas une grille : une pile qu'on descend. Chaque rang a la
   place d'un vrai texte a droite, ce que la vitrine n'a pas.
   --------------------------------------------------------- */

/* L'ecart entre deux rangs. Il etait large a l'epoque ou chaque texte
   vivait sur son carton de papier : il fallait separer deux plaques. Sans
   carton, la meme valeur ne separe plus rien, elle disperse — on descend
   une pile, pas une suite de pages. */
.rayon {
  /* LA TAILLE D'UN OBJET DU RAYON. Un rang tient dans moins de place qu'avant :
     plus l'objet est haut, moins on en voit d'un coup, et un rayon se
     PARCOURT.

     ET C'EST LA TAILLE DE L'OBJET DE REFERENCE, PAS CELLE DE TOUS. Ce nombre
     est celui du voisin le plus ordinaire — un A5, 257 mm de diagonale (cf.
     sujet.js, `plaqueDiagonale`), et ce sont les valeurs qui marchaient deja
     pour les livres : elles ne bougent pas. Une bande ne reserve pas la place
     d'un livre pour y poser un pin's de 30 mm : le monde n'a qu'une echelle
     (cf. optique.js, Monde.negocie), donc le pin's s'y dessinait deja a sa
     vraie taille — au milieu de trois cents pixels de vide, et a une demi-page
     de son voisin de ligne. La bande prend donc la part que l'objet PESE :
     `--rang-part`, posee par epoque.js d'apres ce que la donnee declare. Sans
     declaration, la part vaut 1 et rien du catalogue existant ne bouge. */
  /* C'EST LA MEME QUE CELLE DU MONDE, et il n'y en a qu'une : la
     hauteur de la bande ordinaire EST ce sur quoi l'echelle est
     declaree (cf. `:root`, `--monde-haut`). Deux exemplaires d'un
     meme nombre finissent toujours par diverger, et celui-la
     divergerait entre la mise en page et le dessin. */
  --rang-nominal: var(--monde-haut);
  --rang-haut: var(--rang-nominal);

  display: flex;
  flex-direction: column;
  gap: clamp(26px, 4vh, 56px);
}

/* ---------------------------------------------------------
   LA LIGNE — plusieurs objets a la meme hauteur

   Une pile descend un objet a la fois, et c'est le defaut. Mais
   deux objets qui vont ensemble — un meme geste decline, une
   commande et ce qu'elle donne — n'ont pas a se suivre : ils se
   posent COTE A COTE, et leur ensemble se centre sur sa ligne.

   C'est la donnee qui le dit (`products.json`, champ `ligne`) :
   deux objets qui se suivent et portent la meme cle font une
   ligne. Aucun rayon n'est nomme ici, et une ligne d'un seul
   objet n'existe pas — c'est la pile, telle quelle, et il n'y a
   pas de second chemin a tenir a jour.

   ELLE NE RENVOIE JAMAIS A LA LIGNE. Ce qui ne tient pas dans la
   fenetre — un telephone devant trois objets — se BALAYE : une
   ligne est un rayon horizontal, pas un paragraphe. Un objet
   coupe par le bord, c'est quelque chose a aller chercher ; le
   meme objet renvoye a la ligne, c'est la pile qu'on venait de
   quitter. La ou tout tient, il n'y a rien a balayer et rien ne
   se voit.
   --------------------------------------------------------- */
.ligne {
  /* LA PLACE D'UN OBJET SUR LA LIGNE — et ce n'est pas une largeur qu'on
     choisit : c'est celle que l'objet EXIGE pour ne pas rapetisser.

     UNE PART TROP ETROITE NE RETRECIT PAS QUE SA LIGNE, ELLE RETRECIT TOUT
     LE RAYON. Les objets d'une page sont poses sur le meme sol, donc ils
     partagent une echelle, et c'est la plus petite qui gagne (cf. optique.js,
     Monde.negocie) : une bande trop etroite pour son objet impose sa mesure a
     toute la page. Mesure sur ce rayon — deux parts de 112 px : l'echelle du
     monde tombe de 0,630 a 0,333, et TOUS les objets, y compris ceux qui sont
     seuls sur leur ligne, sont dessines a la moitie de leur taille.

     Elle se compte donc sur la hauteur de l'objet, le seul nombre qui dise
     deja sa taille. Au-dela de 1,2 fois cette hauteur, la largeur cesse de
     borner quoi que ce soit (mesure, livre compris) — l'echelle est celle de
     la pile, au dix-millieme pres. 1,35 laisse de quoi porter un objet un peu
     plus large sans que la page paie pour lui.

     Et elle ne s'etire pas : deux objets cote a cote sont un COUPLE, leur
     ensemble se centre, et ce qui reste de largeur est du sol. Partagee, la
     ligne les aurait pousses chacun au milieu de sa moitie d'ecran — cote a
     cote de nom. Passe ce que la fenetre peut tenir, c'est la ligne qui
     deborde et se balaye, jamais l'objet qui rapetisse.

     Elle se declare donc sur le RANG (plus bas) et non ici : c'est la part
     d'un OBJET, comptee sur la hauteur de cet objet-la. Deux pin's et un
     livre sur la meme ligne n'exigent pas la meme place, et une part unique
     posee sur la ligne les aurait tous ecartes a la mesure du plus grand. */

  display: flex;
  flex-wrap: nowrap;
  /* `safe` : quand ca deborde, le centrage cede au bord de depart — centre,
     le premier objet serait coupe du cote ou l'on ne peut pas revenir. */
  justify-content: safe center;
  /* `start`, pas `stretch` : une piece ouverte fait un ecran de haut, et ses
     voisines n'ont pas a la suivre. */
  align-items: start;
  /* SERRE. Deux objets d'une meme ligne se tiennent — l'ecart dit qu'ils vont
     ensemble, il ne les separe pas. Ce qui les eloigne vraiment, ce n'est
     d'ailleurs pas lui : c'est la part, et l'air que la toile garde autour de
     l'objet qu'elle porte. Les deux se resserrent ensemble. */
  gap: clamp(10px, 1.4vw, 20px);
  overflow-x: auto;
  /* ET CE QUI DEBORDE NE DEBORDE PAS SUR LE NAVIGATEUR. Un balayage qui
     atteint le bout d'une boite se propage a son parent, et sur telephone
     le dernier parent est le navigateur lui-meme : le geste de retour. Une
     rangee qu'on pousse jusqu'a son dernier onglet quittait donc la page.
     Mesure a 375 px : la rangee fait 344 px pour 291 de large, la ligne 295
     pour 278 — les deux debordent pour de bon. `contain` garde le geste chez
     elle ; la ou rien ne deborde, il n'y a rien a garder et rien ne change.
     Meme mot que `.case--texte`, qui le disait deja pour la verticale. */
  overscroll-behavior-x: contain;

  /* PAS DE BARRE. Ce qui dit qu'une ligne se balaye, c'est l'objet coupe par
     le bord — pas un rail blanc pose en travers du rayon : la maison ne
     dessine pas de boites, et une barre qui n'apparait que sur les lignes
     trop longues serait le seul meuble de la page. Le geste, lui, ne change
     pas d'un iota : molette, doigt, ou tabulateur. */
  scrollbar-width: none;

  /* LA TOILE MORD DANS L'AIR DE LA BANDE (cf. --toile-air) : une couverture
     qu'on entrouvre monte au-dessus de son rang. Ce qui deborde d'une boite
     qui se balaye est coupe — la ligne rend donc cet air en dedans et le
     reprend en dehors : le scrollport le contient, la mise en page ne bouge
     pas d'un pixel. */
  padding-block: var(--toile-air);
  margin-block: calc(-1 * var(--toile-air));

  /* ET ELLE VA JUSQU'AU BORD DE L'ECRAN. Le scrollport coupe ce qu'il
     porte a SA boite, et cette boite s'arretait a la marge de la page : un
     pin's balaye disparaissait 20 px avant le bord, sa bulle — posee sur le
     sol, hors de la ligne — continuant seule jusqu'au bout. La ligne prend
     donc la marge en dehors, sans la rendre en dedans : ce qui tient se
     centre toujours, et la marge n'etait que de l'air autour de la colonne.
     Au-dela de la colonne (grand ecran), elle ne deborde que de sa marge. */
  margin-inline: calc(-1 * var(--marge));
}

/* La meme part pour chacun, quoi qu'on y pose : c'est ce qui fait qu'une
   ligne se lit d'un coup d'oeil. */
.ligne::-webkit-scrollbar { display: none; }

.ligne > .rang {
  flex: 0 0 var(--part);
  /* L'ECART QUE LES BULLES DEMANDENT — cf. loupe.js, `ecart`. Une marge et
     pas une part : l'objet garde la place qu'il exige, la ligne s'ecarte
     juste assez pour que deux domes ne se touchent pas, en pas du reseau.
     Zero entre deux objets sans loupe : la ligne d'hier. */
  margin-inline-start: var(--rang-ecart, 0px);
}
/* Une part n'est pas la colonne : ce qu'on y pose s'y borne. */
.ligne .rang__visuel img { max-width: 100%; }

/* LE SCROLLPORT S'EFFACE QUAND UNE PIECE S'OUVRE. Une piece est epinglee en
   haut de la FENETRE (cf. .rang--ouvert .rang__visuel) : dans une boite qui
   se balaye, elle s'epinglerait sur cette boite, et tout ce qu'elle montre
   serait coupe a hauteur de la ligne. Ce que la ligne avait de balaye, le JS
   le porte le temps de l'apercu (cf. epoque.js, les lignes qu'on balaye) —
   rien ne bouge a l'ecran, ni au clic ni a la fermeture. */
.rayon[data-ouvert] .ligne { overflow: visible; }

.rang {
  /* CE QUE CET OBJET-CI PESE. La part vient de la donnee (cf. epoque.js,
     `partDe`) : la diagonale declaree de l'objet rapportee a celle de l'objet
     de reference. Rien de declare, part 1, la bande d'hier — il n'y a pas de
     second chemin a tenir a jour pour les objets qui ne se mesurent pas.

     Elle se declare ICI et pas sur le rayon : une valeur calculee sur le
     rayon y serait resolue une fois pour toutes, et toutes les bandes
     heriteraient de la meme. */
  --rang-haut: calc(var(--rang-nominal) * var(--rang-part, 1));
  /* LA PART QU'IL PREND SUR UNE LIGNE — le meme rapport, sur le seul nombre
     qui dise deja sa taille (cf. .ligne, ou se lit pourquoi 1,35). */
  --part: calc(var(--rang-haut) * 1.35);

  display: grid;
  gap: clamp(16px, 2.6vw, 36px);
  grid-template-columns: 1fr;
  align-items: center;
  /* La pastille se pose DANS la bande, sur le disque du visuel : c'est la
     bande qui lui sert de repere, et c'est elle que le JS mesure. */
  position: relative;
  /* ET LES ETAGES D'UNE BANDE N'APPARTIENNENT QU'A ELLE. Le sol de l'objet,
     la pastille, la prise, la toile : quatre couches qui ne se rangent que
     les unes par rapport aux autres. Sans cet isolement elles se rangeraient
     dans l'etage de la page, ou une bande ouverte monte a 3 pour passer
     devant les suivantes — la toile d'une bande d'en dessous se retrouverait
     au meme rang qu'elle, et gagnerait pour etre plus loin dans le document.
     Une bande garde son z-index chez ses soeurs, elle cesse seulement de
     laisser fuir ceux de ses enfants. */
  isolation: isolate;

  /* LA PLACE QUE LE SOL RECLAME AUTOUR DE L'OBJET.

     Un objet dont le cran differe de celui de son voisin a besoin de quoi
     franchir les anneaux qui les separent : sans cette place, deux tuiles
     voisines se retrouvent dans un rapport de quatre (cf. epoque.js,
     poseLaLoupe, et loupe.js, `reserve`). Le JS mesure ce qui manque de
     chaque cote ; ici on ne fait que l'ajouter.

     Zero partout ou il n'y a pas de sol, ou quand deux voisins ont le meme
     cran : le rayon garde alors exactement le rythme qu'il avait. C'est le
     repli, et il n'y a rien a tenir a jour en double. */
  margin-block:
    calc(var(--rang-sol-haut, 0px) + var(--rang-cale-y, 0px))
    var(--rang-sol-bas, 0px);

  /* LE CALAGE SUR LE RESEAU DU SOL — cf. loupe.js, `cale`.

     Le carrelage est regulier et l'objet est rond : le motif ne se lit pareil
     des deux cotes que si l'objet tombe sur une ligne du reseau. Le JS mesure
     l'ecart au noeud le plus proche ; ici on ne fait que l'appliquer.

     En x c'est un GLISSEMENT : la bande bouge, sa place ne bouge pas — sur
     une ligne, les parts et l'ecart des voisines restent exactement ce
     qu'ils etaient. En y c'est une POUSSEE, ajoutee a la reserve du sol :
     ce qu'il y a au-dessus est une garantie, un calage ne l'entame pas, il
     descend. Zero partout ou il n'y a pas de sol — le rayon garde alors le
     rythme qu'il avait, et il n'y a rien a tenir a jour en double. */
  left: var(--rang-cale-x, 0px);
}
/* `data-revient` : ce rang-la n'ARRIVE pas, il REVIENT. La cascade raconte
   l'arrivee sur le site, et elle ne se raconte qu'une fois — un rang rebati
   en cours de page (la bande qu'on ouvre, celle qu'on referme) n'est pas une
   arrivee, c'est le meme objet qui reprend sa place.
   Ce n'est pas qu'une question de recit : l'animation part de
   `translateY(14px)`, donc la boite du rang MENT pendant six cents
   millisecondes. Le zoom se mesure sur cette boite-la (cf. epoque.js, pose),
   et le recul repose le livre dessus — d'ou le sursaut a la fin du geste, qui
   s'ajoutait a lui-meme a chaque aller-retour. */
[data-entree] .rang:not([data-revient]) {
  animation: epoque-pose 620ms cubic-bezier(0.22, 0.61, 0.36, 1) backwards;
  animation-delay: calc(min(var(--i, 0), 7) * 90ms);
}
@media (min-width: 720px) {
  /* Le visuel tient un peu moins de la moitie : le texte doit avoir l'air
     d'accompagner l'objet, pas de lutter avec lui. */
  .rang { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); }
}

/* Un rang tient dans moins de place qu'avant : plus l'objet est haut, moins
   on en voit d'un coup, et un rayon se PARCOURT. */
.rang__visuel {
  /* Elle se declare sur le RAYON (plus haut) : c'est la taille d'un objet du
     rayon, et la ligne a besoin du meme nombre pour savoir quelle place lui
     reserver (cf. .ligne, --part). */
  height: var(--rang-haut);
  display: flex;
  align-items: center;
  justify-content: center;
}

.rang__visuel img {
  max-height: 100%;
  width: auto;
  object-fit: contain;
  filter: drop-shadow(
    calc(var(--light-x) * var(--sh-dist))
    calc(var(--light-y) * var(--sh-dist))
    var(--sh-blur)
    rgba(0, 0, 0, 0.30)
  );
  transition: transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
.rang:hover .rang__visuel img { transform: translateY(-6px); }

/* Un livre est FERME dans la liste. Une legere rotation suffit a dire qu'il a
   une epaisseur et qu'il s'ouvre — l'inclinaison se redresse au survol, comme
   si on le prenait en main avant de l'ouvrir. */
.rang__ferme {
  --ferme-penche: -1.4deg;
  transform-origin: 50% 100%;
  transform: rotate(var(--ferme-penche));
}
/* Il se REDRESSE, il ne se souleve pas : un livre qu'on prend en main pivote
   vers soi, il ne levite pas au-dessus de son etagere. */
.rang:hover .rang__ferme { transform: rotate(0deg); }

.rang__nom {
  font-family: var(--mono);
  font-weight: 400;
  font-size: clamp(17px, 1.7vw, 22px);
  line-height: 1.2;
  margin: 0 0 8px;
}

.rang__auteur {
  font-family: var(--grotesk);
  font-size: 0.9em;
  margin: 0 0 10px;
}

.rang__prix {
  font-family: var(--mono);
  font-size: 1.02em;
  margin: 0;
}

/* CE QUE L'OBJET A A DIRE, EN ENTIER. Le panneau n'est pas un chapeau qui
   renverrait a une fiche : c'est la fiche. La colonne de lecture reste
   etroite — un panneau large se lit mal, meme quand il a la place.

   Le titre des details ne prend pas ici la respiration qu'il prend sur une
   page servie (`.section__titre`, 64 a 120 px) : la piece fait un ecran, et
   ce qui deborde se parcourt. */
.rang--ouvert .rang__texte .prose {
  max-width: 46ch;
  margin-top: clamp(16px, 2.6vh, 24px);
}
.rang--ouvert .rang__texte .prose p:last-child { margin-bottom: 0; }

.rang--ouvert .rang__texte .section__titre {
  margin: clamp(24px, 4vh, 38px) 0 clamp(12px, 2vh, 18px);
}

/* FERMEE, LA BANDE NE DIT PLUS RIEN. Ni nom, ni prix, ni etat, ni extrait :
   l'objet, son disque, et le jeton du prix pose dessus. Un rayon se PARCOURT
   du regard — ce qui se lit appartient a l'objet ouvert, c'est ce qu'on va
   chercher en avancant dedans.

   Tout est dans le DOM des le rendu : l'ouverture n'a rien a fabriquer, elle
   ne fait que rendre visible ce qui etait deja la. */
.rang:not(.rang--ouvert) .rang__texte { display: none; }

/* Plus rien a cote du visuel : il prend la bande entiere. La regle est plus
   specifique que celle du grand ecran, l'ordre des deux n'a donc pas
   d'importance. */
.rang:not(.rang--ouvert) { grid-template-columns: 1fr; }

.rang__bouton { margin-top: clamp(12px, 2vh, 18px); }

/* ---------------------------------------------------------
   LA PASTILLE — un carre si rond qu'il passe pour un cercle

   Ce n'est pas un cercle : c'est un rectangle au format carre
   dont le rayon de coin vaut la moitie du cote, donc rond tant
   qu'il reste carre. Un prix de deux chiffres et un symbole y
   tient sans le deformer ; ce qui est plus long l'allonge, et
   elle devient d'elle-meme un rectangle aux angles arrondis.
   Aucune regle n'a a choisir entre deux formes — il n'y en a
   qu'une, et c'est le contenu qui la tend.

   La couleur vient du disque de l'objet (JS : --pastille-fond).
   Sans disque, elle prend la matiere du rayon : la maison en
   noir et blanc, un univers dans la sienne, et rien n'est ecrit
   ici qui appartienne a un univers.
   --------------------------------------------------------- */
.pastille {
  /* LA TAILLE D'UN JETON D'OBJET ORDINAIRE. C'est la reference, comme
     `--rang-nominal` l'est pour la bande : ce sont les valeurs qui
     marchaient deja, elles ne bougent pas. Ce qui pese moins la reprend a
     sa part (cf. .rang__pastille). */
  --pastille-nominale: clamp(46px, 4.6vw, 58px);
  --pastille-corps-nominal: clamp(11px, 0.95vw, 13px);
  --pastille-taille: var(--pastille-nominale);
  --pastille-corps: var(--pastille-corps-nominal);
  --pastille-fond: var(--rayon-bord);
  --pastille-encre: var(--rayon-fond);

  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--pastille-taille);
  height: var(--pastille-taille);
  /* LA MARGE EST UNE FRACTION DE LA PASTILLE, PAS UNE FRACTION DE LA LETTRE.

     Elle n'entre en jeu que quand le texte depasse le carre : en deca, c'est
     la largeur minimale qui commande, et la pastille reste ronde.

     Elle s'ecrivait en `em`, donc sur le CORPS — et le corps ne descend pas
     comme la forme : un jeton peut rapetisser, une lecture non, d'ou deux
     planchers (0,72 et 0,9, cf. .rang__pastille). Un petit objet portait donc
     une pastille a 72 % avec une marge a 90 % : l'ecart entre les deux
     plancher devenait du blanc en trop, qu'un prix un peu long comblait
     d'abord — il rattrapait l'ecart avant de rencontrer la marge, puis
     l'allongeait. Mesure sur le rayon Chat-Man : 6,05 px de marge sur un
     ouvrage, 5,45 sur un pin's deux fois plus petit.

     Elle se compte donc sur le corps NOMINAL — la reference, comme
     `--pastille-nominale` l'est pour la forme — et pas sur celui que la bande
     a retenu : c'est une seule et meme valeur d'un bout a l'autre du rayon,
     quel que soit ce que l'objet pese. Un prix long allonge alors sa pastille
     des qu'il touche la marge, jamais avant, et il la touche a la meme
     distance du bord partout. Rien de ce qui pese une part entiere ne bouge
     d'un pixel : a taille nominale, c'est le 0,55em d'hier au centieme. */
  padding: 0 calc(var(--pastille-corps-nominal) * 0.55);
  border-radius: calc(var(--pastille-taille) / 2);
  background: var(--pastille-fond);
  color: var(--pastille-encre);
  font-family: var(--mono);
  font-size: var(--pastille-corps);
  line-height: 1;
  white-space: nowrap;
}

/* L'ECART ENTRE LE NOMBRE ET SA MONNAIE. Moins qu'une espace — en mono, la
   plus petite espace disponible vaut une chasse pleine, celle d'un chiffre :
   elle detache le symbole au lieu de l'en separer. Un ecart pose sur le noeud
   n'a pas cette contrainte, et se regle au dixieme de cadratin. */
.pastille__monnaie { margin-left: 0.18em; }

/* LE CENTRE DE LA BOITE N'EST PAS LE CENTRE DE CE QU'ON VOIT. Deux ecarts
   s'additionnent, et aucun des deux n'est un defaut de calcul :

   en hauteur, la boite d'une ligne se centre sur la CHASSE du corps, jambages
   compris — or un prix n'en a pas un seul, ses glyphes tiennent tous
   au-dessus de la ligne de base et paraissent donc trop hauts ;

   en largeur, le blanc de l'ecart et celui qui suit le symbole pesent a
   droite, et poussent la masse du nombre vers la gauche.

   On corrige donc a l'oeil, en fractions de cadratin pour que la correction
   suive le corps, et par une transformation : elle ne touche pas la boite, la
   pastille garde exactement la taille que son contenu lui donne. */
.pastille__texte {
  display: block;
  transform: translate(0.26em, 0.1em);
}

/* A VENIR, LE JETON DIT L'ETAT A LA PLACE DU PRIX (cf. epoque.js,
   `pastille`, et `accueil.bientot`). Un mot, pas un nombre : il allonge le
   jeton en pilule, comme un prix long. Le disque se serre donc
   (`--pastille-serre`, qui entre dans la loi de taille, cf. .rang__pastille)
   pour qu'un mot ne pese pas plus qu'un prix ; l'arrondi et la descente sur
   le cercle suivent. */
.pastille--signe { --pastille-serre: 0.62; }

/* ET SON MOT NE PREND PAS LA CORRECTION D'UN PRIX. Celle-ci rattrape le
   blanc d'une monnaie, qui pese a droite ; un mot n'en a pas, et elle le
   poussait hors du centre (mesure : 2,9 px). */
.pastille--signe .pastille__texte { transform: none; }

/* Elle n'est pas dans le flux : elle est POSEE sur le disque — ou, sans
   disque, sur le bas-droite de l'objet lui-meme (cf. epoque.js,
   poseSurLeLivre). Le bas-droite de la BANDE n'est plus qu'un repli d'avant
   toute mesure : une bande a la taille de ce qu'elle porte, donc son coin
   est sur l'objet des que l'objet est petit. */
.rang__pastille {
  /* ET ELLE A LA TAILLE DE CE QU'ELLE DESIGNE. Un jeton de cinquante pixels
     vaut le cinquieme d'un livre et les deux tiers d'un pin's : pose sur le
     petit, ce n'est plus une marque au bord du disque, c'est un prix qui
     couvre l'objet. Il prend donc la part que l'objet pese, la meme que la
     bande (cf. .rang, --rang-part) — et rien de ce qui pese une part entiere
     ou davantage ne bouge d'un pixel.

     Deux planchers, et pas un : un jeton peut rapetisser, une lecture non.
     La forme descend plus bas que le corps, qui s'arrete la ou un prix cesse
     de se lire — c'est le texte qui tend alors la pastille, et elle s'allonge
     d'elle-meme comme elle le fait pour un prix long. */
  --pastille-part: min(1, var(--rang-part, 1));
  --pastille-taille: calc(var(--pastille-nominale) * max(0.72, var(--pastille-part)) * var(--pastille-serre, 1));
  --pastille-corps: calc(var(--pastille-corps-nominal) * max(0.9, var(--pastille-part)));

  /* DE COMBIEN ELLE DESCEND SOUS LE BORD DU DISQUE. Centree pile dessus, elle
     mordait trop haut sur la couleur et se confondait avec elle ; un peu plus
     bas, elle pend du cercle au lieu d'y etre posee. C'est une fraction de sa
     propre taille, pas un nombre de pixels : elle descend d'autant sur un
     grand ecran que sur un petit. */
  --pastille-descente: calc(var(--pastille-taille) * 0.22);

  position: absolute;
  right: 4%;
  bottom: 0;
  z-index: 2;
  /* La bande entiere est le lien : le jeton n'a rien a intercepter. */
  pointer-events: none;
}

/* TANT QU'UNE MESURE EST EN ROUTE, ELLE NE SE MONTRE PAS. Le coin est le
   repli d'un objet qui n'aura jamais de disque, pas une place ou patienter :
   le livre d'une bande se monte apres la page, et pendant cette seconde-la
   on voyait le jeton dans le coin, puis sauter sur le disque. Il attend donc
   sa place — le JS retire `data-attend` des que le moteur a repondu, avec ou
   sans disque (cf. epoque.js, poseSurLeLivre).

   Il ne se retire pas de la mise en page : il n'y est deja pas (il est
   absolu), et une visibilite ne coute pas un calcul de plus. */
.rang__pastille[data-attend]:not(.pastille--posee) { visibility: hidden; }

/* UNE BANDE NE SE MONTRE QU'ENTIERE, ET DANS L'ORDRE (cf. epoque.js,
   enTour). Elle garde sa place : invisible, pas retiree. Ce qui tarde porte
   un anneau a la place de l'objet. L'objet s'efface par l'OPACITE, pas par
   la visibilite : une couverture en `loading="lazy"` cachee ne se charge
   jamais, et la bande attendrait pour toujours ce qu'elle empeche de venir.
   Le moteur n'est pas cache : il retient l'objet lui-meme et garde son
   disque (cf. Sujet.retiens) — le cercle est la tout de suite, et sa
   pastille avec lui, qui en fait partie. */
.rang[data-arrive] .rang__visuel > :not(.sujet__hote) { opacity: 0; }
/* LE FONDU D'ARRIVEE. La toile de l'objet (`.sujet__image`) est cachee
   pendant l'attente ; le disque, sur la toile du sol, reste net. Le fondu
   ne se joue qu'a la SORTIE de l'attente : la transition est portee par
   l'etat d'arrivee, donc l'entree en attente est immediate. L'opacite de la
   toile entiere, pas de chaque piece : une couverture ne laisse jamais voir
   les pages a travers elle en route. */
.rang[data-arrive] .rang__visuel .sujet__image { opacity: 0; }
.rang:not([data-arrive]) .rang__visuel .sujet__image {
  transition: opacity var(--arrivee-fondu) ease-out;
}
/* Une couverture garde le leve de son survol (`.rang__visuel img`). */
.rang:not([data-arrive]) .rang__visuel > :not(.sujet__hote) {
  transition: opacity var(--arrivee-fondu) ease-out,
              transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
.rang[data-charge] .rang__visuel { position: relative; }
.rang[data-charge] .rang__visuel::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  width: 28px; height: 28px; border-radius: 50%;
  border: 2px solid color-mix(in srgb, var(--noir) 12%, transparent);
  border-top-color: color-mix(in srgb, var(--noir) 70%, transparent);
  animation: tourne-charge var(--chargement-tour) cubic-bezier(.45, .05, .55, .95) infinite;
}
@keyframes tourne-charge { to { transform: rotate(1turn); } }
@media (prefers-reduced-motion: reduce) {
  .rang[data-charge] .rang__visuel::after { animation: none; }
  .rang:not([data-arrive]) .rang__visuel .sujet__image { transition: none; }
  .rang:not([data-arrive]) .rang__visuel > :not(.sujet__hote) {
    transition: transform 300ms cubic-bezier(0.22, 0.61, 0.36, 1);
  }
}

/* Mesuree : le JS a lu le bas-droite du disque dans la fenetre. Elle s'y
   centre — a moitie sur le disque, a moitie dehors. */
.rang__pastille.pastille--posee {
  right: auto;
  bottom: auto;
  left: var(--pastille-x);
  top: var(--pastille-y);
  transform: translate(-50%, calc(-50% + var(--pastille-descente)));
}

/* ELLE FAIT PARTIE DU CERCLE, PAS DE L'OBJET.

   Elle prend la couleur du disque et pend a son bord : c'est la meme piece,
   vue par un autre bout. Posee par-dessus tout, elle mordait sur le dessin —
   sur un ouvrage le disque deborde assez pour que ca ne se voie pas, sur un
   pin's le jeton couvrait le pin's. Elle se glisse donc entre les deux
   toiles du sujet : au-dessus du sol, sous l'objet (cf. .sujet__sol). Ce qui
   depasse du cercle se lit entier, ce qui passe derriere l'objet passe
   derriere — et le prix se lit comme une marque posee sur le sol, pas comme
   une etiquette collee sur l'oeuvre.

   La bande ouverte n'a rien a ranger : son jeton n'existe plus. */
.rang:not(.rang--ouvert) .sujet__toile,
.rang:not(.rang--ouvert) .sujet__image { z-index: 3; }

/* Ouverte, la bande dit tout : le prix a repris sa place dans le texte, le
   jeton n'a plus rien a annoncer. */
.rang--ouvert .rang__pastille { display: none; }

/* SAUF LE TEMPS DU GESTE : il appartient au cercle, et le cercle est encore
   la. Le JS le glisse entre les deux toiles du sujet et le repose a chaque
   image sur le disque peint (cf. epoque.js, suitLeJeton) ; il s'efface du
   nombre qui efface les autres bandes, donc il revient PENDANT le recul et
   non a la derniere image. Au sejour (`--apercu-t` a 1), il ne se voit pas.
   Sous l'objet, au-dessus du sol : la toile de l'objet est a 1. Et il grandit
   avec son disque, autour de son propre centre — pas d'un coin. */
.rang--ouvert .sujet > .rang__pastille[data-suit] {
  display: inline-flex;
  z-index: 0;
  opacity: calc(1 - var(--apercu-t, 1));
  scale: var(--pastille-echelle, 1);
}

/* LE TOUR — deux fleches cintrees sur le bord du disque (cf. epoque.js,
   leTour). Posees par le JS a la boite de l'ellipse peinte, AU-DESSUS de
   l'objet : ce sont des commandes. Elles n'arrivent qu'avec la piece, sur
   la fin du geste, et repartent au debut du recul — du meme nombre. Sans
   contour, sombre ou blanc selon le disque. Le sombre est `--tour-sombre`
   si la peau le declare, le noir de la maison sinon. */
.tour {
  position: absolute;
  z-index: 2;
  pointer-events: none;
  opacity: calc((var(--apercu-t, 1) - 0.85) / 0.15);
}
.tour__dessin {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.tour__fleche path {
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* Petites a l'oeil, pas au doigt : la prise, invisible, est la moitie du
   disque agrandi de son cote (cf. leTour, `PRISE`). Une SURFACE, pas un
   trait : elle se touche par son interieur. */
.tour__fleche .tour__prise {
  fill: transparent;
  stroke: none;
}
/* Sans contour : l'encre est choisie contre le disque (cf. leTour). */
.tour__trait {
  stroke: var(--tour-encre, var(--noir));
  stroke-width: var(--tour-trait, 2.3px);
}
.tour[data-pret] .tour__prise { pointer-events: fill; cursor: pointer; }
/* Aucun effet au survol : le curseur suffit. Au clavier seulement, le trait
   s'epaissit pour dire ou l'on est. */
/* On tourne, on ne selectionne pas : ni texte au double clic, ni zoom au
   double toucher, ni flash gris sous le doigt. */
.tour__fleche {
  outline: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}
.tour__fleche:focus-visible .tour__trait {
  stroke-width: calc(var(--tour-trait, 2.3px) * 1.6);
}

/* ---------------------------------------------------------
   LA PRISE — la bande est le lien, l'objet est la poignee

   Une bande fermee traverse la colonne entiere ; l'objet tient
   au milieu, dans une fraction de cette largeur. Tant que la
   bande entiere recevait le clic, on entrait dans un livre en
   cliquant loin de lui, sur du vide. Ce qui reagit doit avoir la
   taille de ce qu'il designe.

   La bande reste le lien — c'est lui qui navigue, lui qui prend
   le focus, lui que le survol allume : le clic remonte de la
   prise jusqu'a lui, et les etats de survol suivent l'ancetre.
   On ne deplace pas le geste, on retrecit sa zone.

   Sans mesure (JS absent, objet sans image), elle couvre la
   bande : le repli, c'est exactement ce qu'on avait avant.
   --------------------------------------------------------- */
.rang:not(.rang--ouvert) { pointer-events: none; }
.rang:not(.rang--ouvert) .rang__prise { pointer-events: auto; }

.rang__prise {
  position: absolute;
  inset: 0;
  /* Sous le jeton (qui ne prend rien) et au-dessus de la toile : c'est elle
     qui recoit, et rien d'autre n'a a s'interposer. */
  z-index: 1;
  cursor: inherit;
}

/* Mesuree : le JS a lu l'objet et son disque dans la fenetre, et y a ajoute
   l'air qui rend la cible confortable (cf. epoque.js, PRISE_AIR_X). */
.rang__prise.prise--posee {
  inset: auto;
  left: var(--prise-x);
  top: var(--prise-y);
  width: var(--prise-l);
  height: var(--prise-h);
}

/* Ouverte, la bande n'est plus un lien : il n'y a plus rien a attraper. */
.rang--ouvert .rang__prise { display: none; }

/* ---------------------------------------------------------
   LE VERROU — un objet qu'on montre sans le laisser ouvrir

   La donnee le dit (`verrou`, cf. epoque.js) ; la feuille dit a
   quoi ca ressemble. L'objet garde sa place, sa taille et son
   disque : seul ce qui le DESSINE passe a travers le grain —
   l'image de repli et les toiles de l'objet, jamais le sol, qui
   reste le sol. Le filtre se pose donc sur chaque toile et pas
   sur ce qui les contient : rien entre la bande et ses toiles ne
   fait d'etage.

   Le mot est du cote de la maison, pas de l'univers : plaque
   noire, lettre blanche, comme l'acquerir. Il ne se clique pas —
   rien ne s'allume au survol, il n'y a rien a promettre.
   --------------------------------------------------------- */
:root {
  /* La maille du grain (`--verrou-grain`, en pixels) n'est pas dite ici :
     elle fait partie des reglages du verrou (epoque.js, `VERROU`), qui la
     pose sur la racine avant qu'un objet verrouille n'existe. */
  /* Pas de flou par-dessus : un pixel assez gros brouille deja de lui-meme,
     et son trace reste net. */
  /* Le voile : ce qui entoure l'objet — sa bulle, son socle, le sol — passe
     au meme flou, sur un disque plus large que lui, borde par la mosaique elle-meme.
     `--verrou-voile-flou` ne sert que la ou le grain ne passe pas.
     Sa taille (`--verrou-voile`, en fois le plus grand cote de l'OBJET) est
     un reglage du verrou, posee sur la racine comme la maille. */
  --verrou-voile-flou: 7px;
}

.verrou__grain { position: absolute; }

/* LA MOSAIQUE PASSE AU-DESSUS DU TUNNEL. La margelle d'un puits et l'ombre
   du fond vivent dans un calque frere des bandes, a l'etage 1 (tunnel.js,
   `prendLeCalque`) : une bande sans etage se peignait dessous, et la levre
   du puits barrait le voile d'un trait net (mesure, 19 octobre 2026 : le
   t-shirt de Chat-Man, margelle de 372 px sur un voile de 348). A l'etage 2
   la bande se peint apres le calque, et le voile LIT la margelle comme il
   lit le sol — elle passe au grain avec le reste. La bande d'onglet monte a
   3 pour rester devant (cf. `.page::before`) ; une bande verrouillee ne
   s'ouvre pas, elle ne dispute rien a la bande ouverte. */
.rang[data-verrou] { cursor: default; z-index: 2; }

.rang[data-verrou] .rang__visuel img,
.rang[data-verrou] .sujet__toile,
.rang[data-verrou] .sujet__image,
.bande__objet[data-verrou] .bande__visuel img {
  filter: url(#verrou-grain);
}

/* Sous le voile, l'objet peut rendre son propre grain (`VERROU.objet`) : le
   voile le couvre et porte deja la mosaique. Hors d'un rayon il n'y a pas
   de voile, donc l'objet garde le sien. */
:root[data-verrou-nu] .rang[data-verrou] .rang__visuel img,
:root[data-verrou-nu] .rang[data-verrou] .sujet__toile,
:root[data-verrou-nu] .rang[data-verrou] .sujet__image {
  filter: none;
}

.rang[data-verrou]:hover .rang__visuel img { transform: none; }

.rang[data-verrou] .rang__prise {
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: default;
  /* Elle porte le mot, donc elle passe devant la toile de l'objet (3) : une
     bande verrouillee n'a pas de jeton a laisser au-dessus d'elle. */
  z-index: 4;
}

/* LE VOILE NE FILTRE RIEN DE CE QU'IL CONTIENT : il n'a rien dedans. Il lit
   ce qui est DERRIERE lui (`backdrop-filter`) — la loupe et le carrelage que
   le sol a peints, le verre, le disque — sans qu'aucun d'eux ne change de
   toile ni d'etage. Son bord est celui des cases (cf. epoque.js, `#verrou-voile`).

   C'est un noeud et non un `::before` : sa grille peut glisser sous le
   defilement (`VERROU.glisse`), et une loi de defilement se declare sur un
   element (cf. epoque.js, `voileDuVerrou`). */
.rang[data-verrou] .verrou__voile {
  position: absolute;
  inset: 0;
  /* Flou seul d'abord : c'est ce que garde un navigateur qui ne sait pas
     lire un filtre du document derriere un element. Les autres prennent la
     ligne suivante — le meme grain que l'objet, donc une seule matiere. */
  -webkit-backdrop-filter: blur(var(--verrou-voile-flou));
  backdrop-filter: blur(var(--verrou-voile-flou));
  backdrop-filter: url(#verrou-voile);
  pointer-events: none;
}
.rang[data-verrou] .rang__prise.prise--posee .verrou__voile {
  /* Un disque centre sur l'OBJET, de rayon `--r` compte sur l'objet seul
     (`--objet-*`, posees par poseLaPrise) : la prise compte aussi le disque
     du socle, et sur un objet sous une bulle le voile avalait tout le tunnel
     (19 oct. 2026). Sans mesure de l'objet, c'est la prise — le repli.
     Il ne sort pas de la bande par les cotes : ce qui deborde la allonge le
     document, et un document plus large que l'ecran, un telephone le reduit
     (cf. apercu.md). Tout se compte dans le repere de la bande, puis se
     rend dans celui de la prise. */
  --ox: var(--objet-x, calc(var(--prise-x) + var(--prise-l) / 2));
  --oy: var(--objet-y, calc(var(--prise-y) + var(--prise-h) / 2));
  --r: calc(max(var(--objet-l, var(--prise-l)), var(--objet-h, var(--prise-h))) * var(--verrou-voile, 1.6) / 2);
  --g: max(0px, calc(var(--ox) - var(--r)));
  --d: min(calc(var(--prise-x) + var(--prise-l) + var(--prise-reste, 0px)), calc(var(--ox) + var(--r)));
  inset: auto;
  left: calc(var(--g) - var(--prise-x));
  width: calc(var(--d) - var(--g));
  top: calc(var(--oy) - var(--r) - var(--prise-y));
  height: calc(2 * var(--r));
  /* Le bord, c'est le filtre qui le trace, case par case. Ce masque-ci ne
     sert qu'a un navigateur reste au flou : il lui evite un rectangle. Une
     maille plus large que le disque, pour ne rogner aucune case. */
  --m: radial-gradient(circle calc(var(--r) + var(--verrou-grain, 0) * 1px) at calc(var(--ox) - var(--g)) 50%, #000 calc(100% - 1px), transparent);
  -webkit-mask-image: var(--m);
  mask-image: var(--m);
}

.verrou__mot {
  position: relative;
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  line-height: 1;
  padding: 0.7em 0.9em 0.65em;
  background: var(--encre);
  color: var(--papier);
  white-space: nowrap;
  user-select: none;
  -webkit-user-select: none;
  pointer-events: none;
}

.bande__objet[data-verrou] { cursor: default; }
.bande__objet[data-verrou] .bande__visuel { position: relative; }
.bande__objet[data-verrou] .verrou__mot {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  font-size: 0.56rem;
}

/* ---------------------------------------------------------
   LA VITRINE EN GRILLE — un rayon qui se BALAYE

   Une pile se descend : un objet, puis le suivant, chacun sa
   ligne et le temps qu'on lui donne. Certains rayons ne se
   descendent pas — on y compare des coupes et des couleurs
   avant d'entrer quelque part. Ceux-la se posent en grille.

   LE TRAIT EST L'ETAGERE, et il n'y a que lui : un filet entre
   deux cases, aucun fond, aucune ombre, aucun coin arrondi. La
   case n'est pas un objet pose sur le rayon, c'est la PLACE
   d'un objet — de la meme famille que le disque du socle, qui
   dit ou l'objet se tient sans rien ajouter a l'objet.

   Il se ferme a deux : le haut et la gauche appartiennent a la
   grille, la droite et le bas a chaque case. Une derniere
   rangee incomplete ne dessine donc pas de cases vides —
   l'etagere s'arrete ou les objets s'arretent, au lieu de
   promettre des places qui n'existent pas.

   AUCUN RAYON N'EST NOMME ICI. C'est `config.json` qui declare
   `vitrine` sur le rayon et `chrome` qui le pose sur la page
   (cf. epoque.js) ; un rayon qui ne declare rien garde la pile.
   --------------------------------------------------------- */
/* L'ONGLET N'EST PAS UNE PAGE QUI CONTIENT UN TABLEAU : L'ONGLET EST LE
   TABLEAU. La feuille rend donc ses gouttieres et son air du haut — la grille
   part du bord de la fenetre et s'arrete a l'autre, et le bord haut de la
   feuille, celui ou l'encoche de l'onglet vient mordre, LUI SERT DE PREMIER
   TRAIT. Elle n'en dessine pas un second par-dessus.

   Le bas ne bouge pas : c'est la reserve de la piece ouverte, elle n'a rien a
   voir avec le cadrage (cf. --piece-reserve).

   Ce qu'un rayon aurait a dire en introduction reprend la gouttiere pour lui
   seul ; vide — c'est le cas aujourd'hui — il ne prend pas de place du tout,
   sans quoi la grille commencerait sous un blanc que personne n'a demande. */
[data-vitrine="grille"] .page {
  padding-top: calc(var(--bande-ep) * 1px);
  padding-inline: 0;
}
/* ET CE PREMIER TRAIT NE S'INTERROMPT PAS SOUS L'ENCOCHE. Le trou du bord haut
   (.page::before) soude l'onglet au panneau sur une pile, ou il n'y a rien a
   fermer ; sur une grille, ce trait est la ligne du haut du tableau, et le
   trouer, c'est laisser la premiere rangee ouverte sous la languette. Le
   systeme de l'encoche ne bouge pas (`--encoche-x` / `--encoche-l` restent
   posees) : c'est la grille qui choisit de le peindre plein. */
[data-vitrine="grille"] .page:not([data-bande])::before { --trou: 0; }
/* SOUS UNE BANDE, LA LIGNE DU HAUT DU TABLEAU EST SOUS LA BANDE. La grille
   commence apres elle (`padding-top`, plus haut) : fermer le trou du bord
   haut, c'etait tirer un trait entre l'onglet et sa propre couleur — la
   languette coupee de sa bande (mesure : 303 pixels d'ecran de trait sous
   l'onglet de /clothing/ a densite 3). Le trou reste donc ouvert, et le
   trait du tableau se pose au bas de la bande, plein. */
[data-vitrine="grille"] .page[data-bande]::before { --trait-bas: var(--rayon-bord); }
[data-vitrine="grille"] .page > .prose { padding-inline: var(--gouttiere); }
[data-vitrine="grille"] .page > .prose:empty { display: none; }
/* Une page qui porte une grille ET du texte (Weave) : le texte garde sa
   gouttiere, la grille seule va d'un bord a l'autre. */
[data-vitrine="grille"] .page > :not(.rayon):not(.prose) { padding-inline: var(--gouttiere); }

[data-vitrine="grille"] .rayon {
  --trait: 1px;
  /* La case la plus etroite qu'on accepte. `min(50%, …)` : sur un telephone
     elle ne reclame pas plus d'une demi-largeur — DEUX COLONNES AU MOINS,
     partout. Une grille d'une colonne n'est plus une grille, c'est une pile
     qui ne dit pas son nom, et l'on n'y compare plus rien. */
  --case: clamp(190px, 22vw, 280px);
  /* LE FORMAT D'UNE CASE — largeur sur hauteur, et il se declare UNE fois,
     ici, pour toute grille de la maison. Il se deduisait : un objet carre,
     plus une bande de texte de la hauteur qu'elle voulait — et deux rayons
     batis sur la meme grille n'avaient pas les memes cases (mesure a 1024 :
     251 x 292 dans l'un, 251 x 324 dans l'autre, selon qu'un nom passait a la
     ligne). Aujourd'hui tout est carre ; une grille qui voudra un autre
     format redira ce nombre, rien d'autre. */
  --case-format: 1;

  display: grid;
  /* `auto-fill`, PAS `auto-fit` : les colonnes qu'aucun objet n'occupe restent
     ouvertes. Une case garde ainsi son format quel que soit le nombre
     d'objets — trois pull-overs ne font pas trois cases larges d'un tiers
     d'ecran — et ce qui reste au bout de la rangee n'est pas une zone morte :
     c'est une place, et une place se dessine (cf. epoque.js, casesVides). */
  grid-template-columns: repeat(auto-fill, minmax(min(50%, var(--case)), 1fr));
  /* Il n'y a pas d'ecart entre deux cases : l'ecart, c'est le trait. */
  gap: 0;
  /* Le haut est deja la : c'est l'arete de la feuille. */
  border-left: var(--trait) solid var(--rayon-bord);
}

/* LE CADRE S'EN VA AVEC LE RAYON. Les cases s'effacent avec le geste
   (`--apercu-reste`, plus haut) ; ce trait-la est sur la grille elle-meme,
   qu'aucune opacite ne touche — il resterait pose en travers de la piece
   ouverte, puis disparaitrait d'un coup a la fermeture. Il suit donc le meme
   nombre, par l'encre : une couleur, ca se coupe de transparent, et la grille
   garde ses enfants pleins. */
[data-vitrine="grille"] .rayon[data-ouvert] {
  border-color: color-mix(in srgb,
    var(--rayon-bord) calc(var(--apercu-reste) * 100%), transparent);
}

[data-vitrine="grille"] .rayon > .rang {
  border-right: var(--trait) solid var(--rayon-bord);
  border-bottom: var(--trait) solid var(--rayon-bord);
  /* La case ENTIERE a le format — objet et cartel compris, places vides
     aussi : c'est la largeur que la grille lui donne qui fait sa hauteur.
     `min-height: 0` : sans lui un contenu trop haut repousserait le format
     (le minimum automatique d'une boite a proportion, c'est son contenu). */
  aspect-ratio: var(--case-format);
  min-height: 0;
}

/* La case : l'objet prend la place, la bande prend ce qu'il lui faut. */
[data-vitrine="grille"] .rang:not(.rang--ouvert) {
  /* LA CASE NE S'ELARGIT PAS POUR SON OBJET. `1fr` ne descend pas sous le
     contenu : a deux colonnes sur un telephone (187px), le carre du t-shirt
     reclamait 203px par son image et debordait sur la case voisine. La
     largeur vient de la grille, l'objet s'y range. */
  grid-template-columns: minmax(0, 1fr);
  /* Le cartel a sa rangee d'une ligne (`--cartel-haut`, plus bas) : une
     seconde ligne de nom ne la fait pas grandir. */
  grid-template-rows: minmax(0, 1fr) var(--cartel-haut);
  align-items: stretch;
  gap: 0;
}

/* L'OBJET PREND CE QUE LE CARTEL LAISSE. Le carre etait ici, sur l'objet, avec
   un plafond de hauteur pour les cases trop larges ; le format est monte sur
   la case (`--case-format`, plus haut), et l'objet n'a plus rien a decider :
   toute la largeur, et la hauteur qui reste au-dessus de la bande du bas.
   Plus de plafond non plus — une case ne s'elargit plus quand le rayon est
   court (`auto-fill`), elle n'a donc plus de quoi devenir immense. */
[data-vitrine="grille"] .rang:not(.rang--ouvert) .rang__visuel {
  height: auto;
  min-height: 0;
  width: 100%;
  padding: clamp(14px, 2vw, 26px);
}
[data-vitrine="grille"] .rang:not(.rang--ouvert) .rang__visuel img {
  max-width: 100%;
}

/* UNE VIGNETTE INSCRITE (cf. epoque.js, grille, `inscription`). Le texte
   est grave dans le repere de l'image, pas de la case : la planche a la
   taille de l'image, et tout se compte en fraction de sa largeur. Les
   nombres sont ceux du kit logo Weave 03 sur son canevas de 2000 : ligne de
   base a 1790 (89,5 %), corps 72 (3,6 %), Arial, #171717. */
.rang__planche {
  position: relative;
  display: block;
  width: 100%;
  container-type: inline-size;
}
.rang__planche img { display: block; width: 100%; }
.rang__inscription {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 10.5%;
  line-height: 0;
  text-align: center;
  white-space: nowrap;
  font: 3.6cqw/0 Arial, Helvetica, sans-serif;
  color: #171717;
  pointer-events: none;
}

/* UNE CASE QUI N'EST QU'UN LIEN (cf. epoque.js, grille) — un outil, un
   article, une page : rien a ouvrir sur place, rien a mesurer. La case
   entiere recoit le clic ; il n'y a pas de prise a poser sur un objet. */
.rang.rang--lien:not(.rang--ouvert) { pointer-events: auto; }
.rang--lien { color: inherit; text-decoration: none; }

/* Un objet range dans sa case ne se souleve pas au survol : la case est sa
   place, pas une main qui le prend.

   ET RIEN NE BOUGE, LIVRES COMPRIS. La regle d'avant rendait `none` a toute
   image survolee — donc au livre aussi, qui est penche au repos : il se
   redressait sous la souris, le seul objet de la grille a reagir. Ne pas
   reagir, c'est garder SA pose, pas en prendre une autre : le livre relit
   son inclinaison la ou elle est declaree (`--ferme-penche`). */
[data-vitrine="grille"] .rang:hover .rang__visuel img { transform: none; }
[data-vitrine="grille"] .rang:hover .rang__visuel .rang__ferme {
  transform: rotate(var(--ferme-penche));
}

/* SAUF UNE CARTE OUVERTE, QU'ON PREND EN MAIN (Epoque.penche, cf. rayon.md).
   La transformation n'existe que le temps du geste : au repos, la case n'a
   ni perspective ni calque de plus. Au doigt, l'appui long ne doit ouvrir
   ni le menu de l'image ni une selection. */
[data-penchable] {
  -webkit-touch-callout: none;
  -webkit-tap-highlight-color: transparent;
}
[data-penchable], [data-penchable] * {
  -webkit-user-select: none;
  user-select: none;
}
[data-penchable] img { -webkit-user-drag: none; }
/* L'apercu du generateur garde ses champs editables et selectionnables. */
[data-penchable] :is(input, textarea, [contenteditable]:not([contenteditable="false"])),
[data-penchable] [contenteditable]:not([contenteditable="false"]) * {
  -webkit-user-select: text;
  user-select: text;
}
[data-penche] {
  transform: perspective(1000px) rotateX(var(--penche-x, 0deg)) rotateY(var(--penche-y, 0deg));
  transition: transform .16s ease;
}
@media (prefers-reduced-motion: reduce) { [data-penche] { transform: none; } }

/* LA BANDE DU BAS — le nom d'un cote, le prix de l'autre, sous l'objet et
   sans trait entre les deux : la case est deja cernee. C'est le cartel de
   la case : elle ne dit que ce
   qu'il faut pour choisir d'entrer, le reste attend la piece.

   La bande fermee reste muette partout ailleurs (cf. plus haut) : ici elle
   parle, mais de deux choses seulement. Tout ce que le DOM porte deja se
   retire, et l'on rend le nom et le prix — pas d'exception a fabriquer a
   l'ouverture, c'est la meme bande, montree autrement. */
/* LA CASE RESERVE UNE LIGNE AU CARTEL, qu'il en porte une ou deux : l'objet
   garde sa taille d'une case a l'autre. Un nom sur deux lignes ne prend rien
   a l'objet — la bande, posee en bas de sa rangee, monte dans la marge
   blanche du visuel. Au-dela de deux lignes, le nom se coupe (« … »).
   (Une bande de deux lignes pour tous rapetissait tous les objets, et un nom
   d'une ligne y restait en haut, une ligne vide dessous.) */
/* La voix du cartel se declare sur la grille : le retour d'une case ouverte
   (`.case__libelle`) la lit aussi. */
[data-vitrine="grille"] .rayon {
  --cartel-corps: clamp(13px, 1.1vw, 15px);
  --cartel-interligne: 1.25;
  --cartel-marge: clamp(9px, 1.2vw, 14px);
  --cartel-haut: calc(var(--cartel-interligne) * var(--cartel-corps) + 2 * var(--cartel-marge));
}
[data-vitrine="grille"] .rang:not(.rang--ouvert) .rang__texte {
  align-self: end;
  display: flex;
  /* Meme corps, meme interligne : alignes par le haut, nom et prix partagent
     la premiere ligne (un nom coupe en boite n'a plus que sa derniere ligne
     pour ligne de base). */
  align-items: flex-start;
  justify-content: space-between;
  gap: clamp(8px, 1.4vw, 16px);
  padding: var(--cartel-marge) clamp(11px, 1.4vw, 16px);
  box-sizing: border-box;
}
[data-vitrine="grille"] .rang:not(.rang--ouvert) .rang__texte > * { display: none; }
[data-vitrine="grille"] .rang:not(.rang--ouvert) .rang__texte > .rang__nom,
[data-vitrine="grille"] .rang:not(.rang--ouvert) .rang__texte > .rang__prix {
  display: block;
  margin: 0;
  font-size: var(--cartel-corps);
  /* UN SEUL INTERLIGNE POUR LES DEUX. Le prix en avait un plus haut que le
     nom : une case sans prix avait donc une bande moins haute, un objet plus
     grand au-dessus, et son nom tombait plus bas que celui des voisines. */
  line-height: var(--cartel-interligne);
}
/* Le nom cede, le prix ne cede pas : c'est le plus court des deux, et un
   prix coupe ne veut plus rien dire. */
[data-vitrine="grille"] .rang:not(.rang--ouvert) .rang__texte > .rang__nom {
  min-width: 0;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  overflow-wrap: anywhere;
}
[data-vitrine="grille"] .rang:not(.rang--ouvert) .rang__texte > .rang__prix {
  flex: none;
  white-space: nowrap;
}

/* UNE CASE SANS IMAGE (cf. epoque.js, cartel) : pas de bande en bas, le
   nom se pose au milieu de la case, comme le mot d'un bouton. */
[data-vitrine="grille"] .rang--sans-visuel:not(.rang--ouvert) {
  grid-template-rows: minmax(0, 1fr);
}
[data-vitrine="grille"] .rang--sans-visuel:not(.rang--ouvert) .rang__texte {
  align-self: stretch;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
}

/* Le prix se lit dans la bande : le jeton n'a plus rien a annoncer. */
[data-vitrine="grille"] .rang:not(.rang--ouvert) .rang__pastille { display: none; }

/* UNE PLACE VIDE. Elle n'a rien a montrer et rien a dire — elle n'est meme
   pas dans la page pour ce qui la lit a voix haute. Elle n'a donc AUCUNE
   regle a elle : elle prend les traits de sa case parce qu'elle EST une case,
   et son seul bien propre est sa hauteur, mesuree sur ses voisines et posee
   par le JS. C'est ce qui la fait s'effacer, se transformer et revenir avec
   les autres bandes sans que rien ne la connaisse. */
[data-vitrine="grille"] .rang--vide { cursor: default; }

/* ---------------------------------------------------------
   LE GABARIT — ce qu'une case a le droit d'etre
   (cf. epoque.js, grille ; .claude/rules/rayon.md)

   Une grille n'a qu'un reseau : les colonnes que la fenetre lui donne, des
   cases au format, un filet. Tout ce qui s'y pose est une CASE, et une case
   ne dit jamais OU elle va : elle dit ce qu'elle EST, dans un vocabulaire
   ferme. L'ordre de la donnee fait le reste, a toutes les largeurs.

     fusion    `bloc`    quatre cases reunies, deux sur deux — et c'est la
                         seule : rien ne traverse la page
     division  `rangees` deux moities, l'une sur l'autre
               `colonnes` deux moities, cote a cote
               `six`     trois sur deux
               `une`     une seule part, la case entiere

   Une fusion REUNIT des cases qui existent ; une division PARTAGE une case
   en parts egales. Ni l'une ni l'autre n'ajoute une colonne ou un filet au
   reseau.

   LE PLACEMENT EST IMPOSE. `!important` n'est pas ici un passe-droit, c'est
   la serrure : ni une feuille de module, ni un style pose par un script ne
   peut donner une colonne ou une rangee a une case. Qui veut une place
   change l'ordre de sa donnee.

   `dense` : une fusion qui ne tient pas au bout d'une rangee descend a la
   suivante, et la case d'apres vient combler ce qu'elle laisse. C'est ce qui
   fait qu'un meme ordre se lit a deux colonnes comme a six, et que les
   places vides (cf. casesVides) tombent dans les trous sans qu'on leur
   dise lesquels. */
[data-vitrine="grille"] .rayon { grid-auto-flow: dense; }

[data-vitrine="grille"] .rayon > .rang {
  grid-column: auto !important;
  grid-row: auto !important;
}
[data-vitrine="grille"] .rayon > .rang[data-fusion="bloc"] {
  grid-column: span 2 !important;
  grid-row: span 2 !important;
}

/* LE FORMAT EST IMPOSE, comme le placement. Une case du gabarit vaut une
   place, ou quatre : jamais la hauteur de ce qu'elle porte, jamais la largeur
   de la page. Une bande-titre en travers s'ecrivait `rangee` ; le mot est
   retire (13 octobre 2026), et ni une feuille de module ni un style en ligne
   ne peut le refaire a la main. Ce qui depasse d'un contenu libre est coupe :
   c'est la case qui gouverne, pas ce qu'on y met. */
[data-vitrine="grille"] .rayon > .rang--commandes,
[data-vitrine="grille"] .rayon > .rang--contenu,
[data-vitrine="grille"] .rayon > .rang--ouvrable,
[data-vitrine="grille"] .rayon > .rang--lien {
  aspect-ratio: var(--case-format) !important;
  width: auto !important;
  height: auto !important;
  min-height: 0 !important;
}

/* LA CASE RETOUR : ni objet ni vignette, son mot au milieu — la meme que
   le « Retour » des commandes de Bootleg. */
[data-vitrine="grille"] .rayon > .rang--retour .rang__visuel { display: none; }
[data-vitrine="grille"] .rayon > .rang--retour {
  display: flex; align-items: center; justify-content: center; text-align: center;
}
[data-vitrine="grille"] .rayon > .rang--retour .rang__texte { padding: 16px; }

/* LA TETE (cf. teteDe, epoque.js) : le signe de l'onglet, seul au milieu
   de sa case ; son nom reste au document, pour qui lit a voix haute. Sans
   signe, c'est son nom au milieu — la case sans visuel. Rien a cliquer. */
.rang--tete { cursor: default; }
[data-vitrine="grille"] .rayon > .rang--tete:not(.rang--sans-visuel) {
  display: flex; align-items: center; justify-content: center;
}
[data-vitrine="grille"] .rayon > .rang--tete:not(.rang--sans-visuel) .rang__visuel {
  flex: 1; align-self: stretch;
  display: flex; align-items: center; justify-content: center;
}
[data-vitrine="grille"] .rayon > .rang--tete .rang__visuel img {
  width: 50%; height: 50%; object-fit: contain;
}
[data-vitrine="grille"] .rayon > .rang--tete:not(.rang--sans-visuel) .rang__texte {
  position: absolute; width: 1px; height: 1px; padding: 0;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="clair"]) .rang--tete-encre .rang__visuel img { filter: invert(1); }
}

/* LA CASE QUI ATTEND (`attente`) : la place d'un lien a venir. Son mot
   (`note`) dit qu'il viendra ; elle ne promet pas de clic. */
.rang--attente { cursor: default; }
[data-vitrine="grille"] .rayon > .rang--attente .rang__visuel { opacity: .35; }

/* Une case qui n'est pas de cette vue n'est pas la : elle ne tient pas sa
   place en blanc, les suivantes remontent. `.rang` se declare `grid`, ce qui
   l'emporterait sur l'attribut seul. */
[data-vitrine="grille"] .rayon > .rang[hidden],
[data-vitrine="grille"] .rang__part[hidden] { display: none; }

/* LA RESERVE — ce qu'un module garde sous la main sans le montrer (les
   noeuds d'un moteur dont les commandes sont parties dans les cases). Elle
   n'est pas une case et ne compte nulle part. */
.rayon__reserve { display: none; }

/* CE QU'UNE CASE PEUT PORTER, en plus d'un objet ou d'un lien :
   des commandes (ses parts), un contenu libre, ou une chose qui s'ouvre sur
   place. Elles recoivent le geste — une bande du catalogue le laisse a sa
   prise, celles-ci n'en ont pas. */
[data-vitrine="grille"] .rayon > .rang--commandes,
[data-vitrine="grille"] .rayon > .rang--contenu,
[data-vitrine="grille"] .rayon > .rang--ouvrable {
  pointer-events: auto;
  min-width: 0;
}
/* Le contenu libre : ni objet ni cartel, la case entiere est a lui. */
[data-vitrine="grille"] .rayon > .rang.rang--contenu {
  display: block;
  padding: 0;
  overflow: hidden;
}

/* LA DIVISION. Des parts EGALES, toujours : `minmax(0, 1fr)` ne laisse pas
   une part s'elargir pour ce qu'elle porte. Le meme mot partage une case,
   une part (une part peut etre divisee a son tour), ou un element qu'un
   module a deja et qu'il fait ADOPTER (cf. epoque.js, `divise`). */
[data-vitrine="grille"] .rayon > .rang[data-division],
[data-vitrine="grille"] .rang [data-division] {
  display: grid;
  padding: 0;
  gap: 0;
  overflow: hidden;
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
}
[data-vitrine="grille"] .rayon > .rang[data-division="rangees"],
[data-vitrine="grille"] .rang [data-division="rangees"] {
  grid-template: repeat(2, minmax(0, 1fr)) / minmax(0, 1fr);
}
[data-vitrine="grille"] .rayon > .rang[data-division="colonnes"],
[data-vitrine="grille"] .rang [data-division="colonnes"] {
  grid-template: minmax(0, 1fr) / repeat(2, minmax(0, 1fr));
}
[data-vitrine="grille"] .rayon > .rang[data-division="six"],
[data-vitrine="grille"] .rang [data-division="six"] {
  grid-template: repeat(2, minmax(0, 1fr)) / repeat(3, minmax(0, 1fr));
}
.rang__part { min-width: 0; min-height: 0; }

/* Le filet entre deux parts est celui du reseau — meme trait, meme encre :
   une division ne dessine pas une autre grille dans la grille. */
[data-vitrine="grille"] .rang[data-division="rangees"] > * + *,
[data-vitrine="grille"] .rang [data-division="rangees"] > * + * {
  border-top: var(--trait) solid var(--rayon-bord);
}
[data-vitrine="grille"] .rang[data-division="colonnes"] > * + *,
[data-vitrine="grille"] .rang [data-division="colonnes"] > * + * {
  border-left: var(--trait) solid var(--rayon-bord);
}
[data-vitrine="grille"] .rang[data-division="six"] > :nth-child(n + 4),
[data-vitrine="grille"] .rang [data-division="six"] > :nth-child(n + 4) {
  border-top: var(--trait) solid var(--rayon-bord);
}
[data-vitrine="grille"] .rang[data-division="six"] > :not(:nth-child(3n + 1)),
[data-vitrine="grille"] .rang [data-division="six"] > :not(:nth-child(3n + 1)) {
  border-left: var(--trait) solid var(--rayon-bord);
}

/* CE QUI S'OUVRE SUR PLACE. Le nom est le bouton : il n'a ni bord ni fond,
   c'est toujours le cartel qu'on lit. */
.rang__ouvre {
  border: 0;
  padding: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
[data-vitrine="grille"] .rayon > .rang--ouvrable { cursor: pointer; }

/* LA CARTE A LA TAILLE DE SA COLLECTION. Fermee, elle tient dans 160px de
   large ; ouverte (le bloc de quatre places), dans 288px — les deux formats
   du generateur, a 12px du bord. C'est le meme objet aux deux endroits : il
   ne grandit pas avec la case. */
[data-vitrine="grille"] .rayon > .rang--ouvrable .rang__visuel {
  height: auto;
  min-height: 0;
  width: 100%;
  padding: 12px;
}
[data-vitrine="grille"] .rayon > .rang--ouvrable .rang__visuel img {
  max-width: min(100%, 160px);
  max-height: 100%;
  object-fit: contain;
}
[data-vitrine="grille"] .rayon > .rang--ouvrable[data-fusion="bloc"] .rang__visuel img {
  max-width: min(100%, 288px);
}

/* LE RENVOI — l'article qui raconte l'objet, en lien bleu a la maniere d'une
   encyclopedie : la seule couleur de l'interface, parce que c'est la
   convention qu'on reconnait sans la lire — en italique, comme le renvoi
   d'un article vers son article detaille. Il ne parle qu'une fois l'objet
   ouvert ; ferme, la case ne dit que son nom. */
.rang__renvois {
  display: none;
  justify-self: center;
  justify-items: center;
  gap: 2px;
  padding: 0 12px 8px;
}
.rang__renvoi {
  font-family: "Space Grotesk", sans-serif;
  font-size: 0.875rem;
  font-style: italic;
  color: #3366cc;
  text-decoration: none;
  pointer-events: auto;
}
.rang__renvoi:hover,
.rang__renvoi:focus-visible { text-decoration: underline; }
.rang[data-fusion="bloc"] > .rang__renvois { display: grid; }
@media (prefers-color-scheme: dark) {
  .rang__renvoi { color: #88a3e8; }
}

/* ---------------------------------------------------------
   LES CASES — l'objet reste, le rayon se retourne
   (`rayons[].ouverture: "cases"`, cf. epoque.js, cases)

   La case cliquee ne bouge pas. Chaque autre case s'efface
   sous une FACE posee exactement sur elle — le retour, le
   cartel, un angle, la description — et la grille garde ses
   traits : ce sont les memes places, qui disent autre chose.
   Les faces arrivent en vague depuis l'objet (`--case-d`, sa
   distance en cases) et repartent dans le meme ordre.
   --------------------------------------------------------- */
/* LE FONDU — UN SEUL, POUR TOUTE GRILLE. Ce qui part et ce qui vient dans
   une grille s'efface et revient de la meme facon, qu'on ouvre un objet du
   catalogue (les cases, ci-dessous) ou qu'un module change de vue
   (`poignee.montre`, cf. epoque.js, Epoque.grille). Deux grandeurs, posees
   ici et nulle part ailleurs ; le JS les LIT (`fondu()`) pour savoir quand
   c'est fini :
     `--fondu`      la duree du fondu d'une case
     `--fondu-pas`  le retard par case de distance — la vague qui part de la
                    case cliquee (`--case-d`, pose par le JS)
   Seul le CONTENU s'efface : les traits de la grille restent. */
:root {
  --fondu: 260ms;
  --fondu-pas: 50ms;
}

.rayon[data-cases] > .rang[inert] > *,
.rayon > .rang[data-fondu] > * {
  transition: opacity var(--fondu) ease-out;
  transition-delay: calc(var(--case-d, 0) * var(--fondu-pas));
}
.rayon[data-cases-ouvert] > .rang[inert] > *,
.rayon > .rang[data-fondu="part"] > *,
.rayon > .rang[data-fondu="vient"] > * { opacity: 0; }

.rayon[data-cases] { position: relative; }

.cases {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}

.case {
  position: absolute;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: clamp(14px, 2vw, 26px);
  opacity: 0;
  transform: translateY(12px);
  transition:
    opacity var(--fondu) ease-out,
    transform calc(var(--fondu) * 1.5) cubic-bezier(.2, .7, .2, 1);
  transition-delay: calc(var(--case-d, 0) * var(--fondu-pas));
}
/* La face vient une demi-duree apres que sa case a commence de s'effacer. */
.rayon[data-cases-ouvert] .case {
  opacity: 1;
  transform: none;
  pointer-events: auto;
  transition-delay: calc(var(--fondu) / 2 + var(--case-d, 0) * var(--fondu-pas));
}

/* LE RETOUR — toute la case est le bouton, et c'est la case retour des
   grilles d'articles (`.rang--retour`) : son mot au milieu, a la voix du nom
   d'un cartel (`--cartel-corps`, `--cartel-interligne`), rien ne s'inverse
   au survol. */
.case--retour { padding: 0; }
.case__retour {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  font-family: var(--mono);
  cursor: pointer;
}
.case__libelle {
  font-size: var(--cartel-corps);
  line-height: var(--cartel-interligne);
  font-weight: 400;
  padding: 16px;
  text-align: center;
}

/* LE CARTEL — ce qui se dit pour choisir, et le geste en bas. */
.case--cartel { gap: 8px; }
.case__nom {
  font-family: var(--mono);
  font-size: clamp(15px, 1.3vw, 18px);
  font-weight: 400;
  line-height: 1.25;
  margin: 0;
}
.case__auteur { margin: 0; font-size: 0.9em; }
.case__prix {
  font-family: var(--mono);
  font-size: clamp(28px, 3.4vw, 44px);
  line-height: 1;
  margin: auto 0 0;
}
.case--cartel .objet__statut { margin: 0; }
.case--cartel .fiche__achat { margin-top: 6px; }

/* UN ANGLE — la meme lumiere que l'objet, a la meme place. */
.case--vue { padding: 0; }
.case__vue {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(14px, 2vw, 26px);
  border: 0;
  background: none;
  cursor: zoom-in;
}
.case__vue img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

/* UNE PAGE EN CLAIR — une page de livre par case, au milieu de la sienne.
   Un vis-a-vis prend donc deux cases : tenu dans une seule, chaque page
   n'avait que la moitie de la largeur et ne se lisait plus. */
.case--planche { padding: 0; }
.case__planche {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(14px, 2vw, 26px);
}
.case__planche img {
  flex: none;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

/* LE TEXTE — il se lit dans sa case, et s'y parcourt quand il deborde. */
.case--texte {
  overflow-y: auto;
  overscroll-behavior: contain;
  font-size: clamp(13px, 1vw, 15px);
  line-height: 1.5;
}
.case__titre {
  font-family: var(--mono);
  font-size: 0.78em;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  margin: 0 0 12px;
}
.case--texte .prose { max-width: none; font-size: 1em; white-space: pre-line; }
.case--texte .prose p { margin: 0 0 1em; }

@media (prefers-reduced-motion: reduce) {
  .case,
  .rayon[data-cases] > .rang[inert] > *,
  .rayon > .rang[data-fondu] > * { transition: none; transform: none; }
}

/* LA PIECE N'EST PAS UNE CASE. Elle va d'un bord de la fenetre a l'autre,
   comme partout — mais dans une grille rien ne la tend jusque-la : elle est
   posee dans une colonne, et une marge negative de gouttiere ne la sortirait
   que de la gouttiere. Elle prend donc la largeur MESUREE du panneau et se
   decale de l'ecart MESURE entre sa case et lui (cf. epoque.js, poseBord).

   Sa place dans la grille, elle, ne bouge pas d'un cran : ni la colonne, ni
   la rangee, ni ce qui suit. Une piece qui s'etendrait sur toutes les
   colonnes rejetterait les cases suivantes a la ligne — le rayon sauterait
   au clic, exactement ce que `--piece-place` existe pour empecher. */
[data-vitrine="grille"] .rayon > .rang--ouvert {
  border: 0;
  justify-self: start;
  width: var(--piece-large, 100%);
  margin-inline: calc(-1 * var(--piece-bord, 0px)) 0;
}

/* NI UNE PART DE LIGNE — meme raison, meme mesure. Rien dans une ligne ne la
   tend jusqu'au bord de la fenetre : elle prend la largeur du panneau et
   l'ecart mesure entre sa part et lui.

   ET ELLE REND A LA LIGNE LA PART QU'ELLE OCCUPAIT FERMEE (`--piece-part`,
   mesuree au clic), comme elle rend au rayon la hauteur qu'elle occupait
   (`--piece-place`) : ce qu'elle prend de large a gauche, elle le rend a
   droite, sa part reste sa part, et les objets qui la suivent sur la ligne
   ne bougent pas d'un pixel du clic a la fermeture. */
.ligne > .rang--ouvert {
  flex: 0 0 auto;
  min-width: 0;
  width: var(--piece-large, 100%);
  /* Son ecart reste le sien : `--piece-bord` se mesure bande ecartee. */
  margin-inline:
    calc(var(--rang-ecart, 0px) - var(--piece-bord, 0px))
    calc(var(--piece-part, 0px) - var(--piece-large, 0px) + var(--piece-bord, 0px));
}

/* ---------------------------------------------------------
   L'APERCU — la bande ouverte
   On ne quitte pas le rayon pour regarder un objet : la bande
   cliquee grandit sur place et devient la scene. Rien ici ne
   sait ce qu'est un livre ni ce qu'est un univers — c'est de la
   place qu'on fait, le mouvement est en JS (Epoque.apercu) et
   le motif qui suit appartient a la peau.
   --------------------------------------------------------- */

/* Les autres bandes s'effacent SANS rendre leur place : la pile ne se
   reorganise pas sous les yeux du visiteur, elle recule.

   ET C'EST LE GESTE QUI LES EFFACE, PAS UNE TRANSITION A COTE. Elles avaient
   leur propre duree, declenchee par l'arrivee et par le depart de
   `data-ouvert` — donc au RETOUR, elles ne revenaient qu'une fois le recul
   fini : le rayon se rallumait d'un coup sur une page deja immobile, et ca se
   lisait comme un rechargement. Elles suivent maintenant le seul nombre du
   geste (`--apercu-t`, pose par Epoque.apercu) : elles s'effacent en
   avancant, elles reviennent en reculant, et la derniere image du recul EST
   le rayon rendu. Rien a synchroniser, il n'y a qu'une horloge.

   Sans le nombre — la bande vient d'etre ouverte, le geste n'a pas encore
   pose sa premiere valeur — elles sont pleines : le repli est l'etat d'avant,
   jamais un clignement. */
/* ILS NE S'EFFACENT PAS AU CLIC, ILS S'EFFACENT QUAND ON EST ASSEZ PRES.

   Le fondu suivait le geste d'un bout a l'autre : des la premiere image ils
   palissaient, alors qu'a ce moment-la le livre n'a pas encore bouge et qu'on
   les voit tous. Ils tiennent maintenant tant que le geste n'a pas fait sa
   part du chemin (`--apercu-tenue`), puis s'en vont sur ce qu'il en reste
   (`--apercu-fondu`). Joue a l'envers, c'est exactement la demande d'a cote :
   au recul ils reviennent PENDANT le dezoom, et la derniere image du geste
   est un rayon deja rendu — plus rien n'y arrive apres.

   ET CE N'EST PLUS LE FONDU QUI LES EMMENE : elles sont posees sur le sol,
   elles suivent l'oeil, elles s'ecartent et sortent du champ toutes seules
   (cf. epoque.js, les autres bandes sont posees sur le meme sol). Le fondu ne
   fait plus que finir le travail pour celles qui passent trop pres de l'axe
   pour en sortir — d'ou une tenue plus longue : ce qu'on voit partir n'a pas
   besoin qu'on l'efface. */
/* CE QUI RESTE DU RAYON, EN UN SEUL NOMBRE. Pose sur la liste, la ou vit
   deja `--apercu-t` : ce n'est pas la bande qui decide de la tenue et du
   fondu, c'est le geste, et tout ce qui doit s'en aller avec les autres
   bandes le lit ici plutot que de reecrire la formule. */
.rayon[data-ouvert] {
  --apercu-tenue: 0.5;
  --apercu-fondu: 0.5;
  --apercu-reste: clamp(0, calc(1 - (var(--apercu-t, 0) - var(--apercu-tenue))
                                    / var(--apercu-fondu)), 1);
}
.rayon[data-ouvert] .rang:not(.rang--ouvert) {
  opacity: var(--apercu-reste);
  pointer-events: none;
}

/* Ouverte, la bande n'est plus un lien : elle est l'endroit ou l'on est.

   ET C'EST UNE PIECE, plus un morceau de page. Un ecran de haut, d'un bord de
   la fenetre a l'autre, ou le livre est pose sur son sol : ce qu'on voit, on
   le voit d'UN endroit, et cet endroit ne bouge plus.

   LA SCENE PREND LA PIECE, LE PANNEAU PREND CE QU'IL LUI FAUT. La bande
   ouverte partageait la colonne en deux — huit treiziemes pour l'objet, cinq
   pour le texte — et la largeur est justement ce qui commande la taille d'un
   livre OUVERT : deux pages et le dos, cadres sur la moitie d'un ecran.
   Mesure : 0,652 px/mm dans le rayon, 0,614 une fois entre dedans — on
   avancait vers l'objet et il en sortait plus petit que sa couverture fermee.

   La piece va donc d'un bord de la fenetre a l'autre, et le panneau n'en
   prend qu'une cartouche : une colonne a droite des qu'il y a de la largeur
   (plus bas), et le bas de la piece sur un telephone, ou il se pose PAR-DESSUS
   la scene faute de place a cote. */
/* Le partage de la piece se declare a la racine : la piece le lit, et la
   pile du panier aussi (cf. PANIER), pour se poser a cote du panneau. Le
   `100%` se resout chez qui le lit — la piece et la fenetre ont la meme
   largeur. */
:root {
  /* LA MARGE DE LA PIECE — ce qu'on retire de la fenetre avant de partager.

     Ce n'est pas la gouttiere de la page : celle-ci ramene un texte sur l'axe
     de la colonne, celle-la borde une SCENE. Elle se prend sur la fenetre, en
     part de sa largeur, et se borne aux deux bouts — sur un telephone il n'y
     a rien a border, sur un tres large ecran une marge qui grandirait sans
     fin finirait par manger la piece.

     Le fond, lui, la traverse : le motif du sol continue d'un bord a l'autre.
     Elle ne borde pas l'univers, elle borde ce qui se pose dedans. */
  --piece-marge: clamp(16px, 4vw, 96px);

  /* LE TIERS — celui du panneau, et celui que le livre prend deux fois. Ecrit
     ici et nulle part ailleurs : le JS le LIT sur le panneau au lieu de
     refaire le calcul (cf. epoque.js, le partage en trois). */
  --piece-tiers: calc((100% - 2 * var(--piece-marge)) / 3);
}

.rang--ouvert {
  cursor: default;
  position: relative;
  /* Une seule case tant qu'il n'y a pas la largeur de deux : les deux enfants
     s'y superposent, la scene dessous, le panneau dessus (cf. plus bas). */
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  gap: 0;
  /* La piece va d'un bord de la fenetre a l'autre : elle sort de la gouttiere
     du panneau, comme le sol qu'elle montre. On n'est pas devant une vitrine
     posee dans une colonne, on est DANS la piece.
     `--piece-bord` est la gouttiere MESUREE, posee par le JS a l'ouverture
     (epoque.js) : ecrite en pourcentage, elle se relirait ici sur une largeur
     dont elle a deja ete retiree. Le repli garde le comportement d'avant —
     sans mesure, la piece s'arrete a la colonne. */
  margin-inline: calc(-1 * var(--piece-bord, var(--gouttiere)));

  /* LA HAUTEUR DE LA PIECE — un ecran, et le meme nombre partout : la scene
     le prend pour hauteur, le panneau pour course, la bande pour plancher. Sur
     un telephone, la barre d'adresse fait respirer `vh` a chaque defilement,
     et la piece respirerait avec ; `svh` est le petit etat, celui qui ne bouge
     pas. Un navigateur qui ne le connait pas garde `vh` et rien ne casse. */
  --piece: 100vh;
  /* OU LE PANNEAU SE POSE AU REPOS, compte depuis le haut de la piece — donc
     en part de la piece, jamais en `vh` : les deux ne valent pas la meme chose
     sur un telephone, et un panneau qui se poserait plus bas que la piece
     n'est plus dans la piece. */
  /* SUR UN ECRAN ETROIT, LA CARTOUCHE PREND LE BAS — ET ELLE EN PREND LA
     MAJEURE PARTIE. Elle se posait aux deux tiers de la piece : le livre
     avait donc tout le haut pour lui, et n'en faisait rien. Un livre est plus
     LARGE que haut — sur une fenetre verticale c'est sa largeur qui le borne
     bien avant sa hauteur — donc il flottait au milieu d'un tiers d'ecran
     blanc pendant que ce qu'il y a a lire se lisait par la fente du bas.
     Il se tient maintenant dans le premier tiers, et le reste est a la
     cartouche. Le cadrage suit : la scene cede la bande que le panneau
     couvre (cf. epoque.js, la reserve basse). */
  --panneau-haut: calc(var(--piece) * 0.40);

  /* LA PLAQUE D'ACQUERIR — sous la cartouche, jamais dedans (cf. epoque.js).
     Sa hauteur est MESUREE a l'ouverture et posee ici ; le nombre ecrit n'est
     que le repli, le temps d'une image. */
  --acquerir-h: 56px;
  --acquerir-ecart: clamp(10px, 1.4vh, 16px);
  /* La largeur de la cartouche — ecrite une fois, lue par les deux : une
     plaque plus etroite ou plus large que le cartel au-dessus d'elle serait
     un autre objet. */
  --panneau-l: min(var(--panneau-large), calc(100% - 2 * var(--gouttiere)));

  /* LA TAILLE DU PANNEAU EST LA SIENNE, PAS CELLE DE SON TEXTE.

     Elle sortait du contenu : la boite faisait la hauteur de ce qu'on y
     ecrivait et la largeur de ce que la grille lui laissait. Deux objets
     voisins n'avaient donc pas la meme cartouche — un titre sur deux lignes
     valait vingt pixels de blanc en plus — et sur un grand ecran la colonne
     se resserrait a mesure que la gouttiere grossissait : le panneau
     RETRECISSAIT quand la fenetre grandissait.

     C'est un cartel au mur d'une salle : sa taille est celle de la PIECE — le
     dernier tiers, entier — et sa hauteur est celle de sa ZONE, du haut ou il
     se pose jusqu'a la marge du bas. Ce qui s'y ecrit s'y range ; s'il
     deborde, il se parcourt chez lui.

     C'ETAIT UN NOMBRE — 300px — et le blanc restant sous la cartouche
     n'appartenait a personne : trop pour un objet bavard, du vide pour un
     objet muet, et jamais la meme colonne blanche d'une bande a l'autre. Un
     cartel occupe la place qu'on lui a reservee, texte ou pas. La zone est
     donc ce qui reste de la piece une fois retire ce qui la borde : ou il se
     pose en haut, la marge de la piece en bas — celle-la meme qui le borde a
     droite, et non la gouttiere de la page, qui grandit avec la fenetre et
     faisait RETRECIR le panneau sur un grand ecran. */
  --panneau-haute: calc(var(--piece) - var(--panneau-haut) - var(--panneau-bas)
                        - var(--acquerir-h) - var(--acquerir-ecart));
  /* Ce qui borde la cartouche EN BAS de la piece — et c'est aussi la ou la
     plaque d'acquerir s'arrete. Sur un ecran etroit la cartouche vit dans la
     gouttiere de la page, sur un grand ecran dans la marge de la scene. */
  --panneau-bas: var(--gouttiere);
  /* Sans colonne a lui — le telephone — il n'y a plus de tiers a prendre : il
     se pose en pleine largeur dans le bas de la piece. */
  --panneau-large: 370px;

  /* LA PIECE SE PARTAGE-T-ELLE ? C'est la feuille qui le sait — elle seule
     connait la largeur a partir de laquelle le panneau prend une colonne
     plutot que le bas de l'ecran — et le JS le LIT (cf. epoque.js). Sans ca
     il faudrait recopier ici le point de rupture, et deux endroits qui
     disent la meme chose finissent par ne plus la dire pareil. A zero, le
     panneau se pose PAR-DESSUS la scene : il n'y a pas de tiers a prendre,
     et le livre garde toute la piece. */
  --piece-partage: 0;

  /* EXACTEMENT UN ECRAN, ET RIEN DE PLUS. C'etait un plancher (`min-height`),
     et le panneau depassait par le bas : la bande faisait un ecran PLUS sa
     hauteur, et ce depassement etait sa course de defilement. On ne defile
     plus — tout ce qu'il y a a voir tient dans la vue, donc la page se bloque
     pendant qu'on est dedans (cf. epoque.js, `data-apercu`). Une hauteur
     ferme, et la fenetre pour seul bord.

     ELLE NE COUPE PAS. Elle l'a fait — `overflow: hidden` — et c'etait une
     arete invisible EN PLEIN MILIEU DE L'ECRAN : pendant le travelling, la
     scene est portee hors de sa bande (jusqu'a deux cents pixels plus haut,
     cf. epoque.js, o.porte) tandis que la bande, elle, est encore en train
     de monter vers le haut de la fenetre. Tout ce que la toile peignait
     au-dessus du bord de la bande etait tranche net : la feuille en vol au
     milieu de son tour, et le disque du socle avec elle. Une piece qui ne se
     parcourt pas n'a rien a couper — la page est bloquee des le clic
     (`html[data-apercu]`), et c'est la FENETRE qui borde, comme elle borde
     tout le reste. */
  height: var(--piece);

  /* ET ELLE NE POUSSE PAS CE QUI SUIT. La bande rend au flux la place
     qu'elle occupait fermee — `--piece-place`, mesuree au clic par le JS
     (cf. epoque.js, la piece ne pousse plus le rayon) — et deborde
     par-dessus les bandes du dessous, qui ne bougent donc plus du clic a la
     fermeture. Sans mesure,
     elle reprend toute sa hauteur : le repli est le comportement d'avant.

     C'est ce saut de six cents pixels, rendu d'un coup a la derniere image
     du recul, qui se lisait comme un rechargement de la page.

     ET ELLE GARDE LA MARGE QUE LE SOL LUI RESERVAIT. `margin-bottom` ecrase
     le `margin-block` de la bande (plus haut), donc `--rang-sol-bas` s'en
     allait avec — la place que la loupe reserve entre deux crans, retiree du
     flux a l'instant du clic. La bande du dessous remontait d'autant : mesure
     au clic sur un ouvrage a 52 px de reserve, le t-shirt qui le suit passait
     de 848 a 796 en `offsetTop`, et il etait ensuite projete depuis une place
     qu'il n'occupait plus (les voisines sont mesurees avant, cf. epoque.js).
     Une bande ouverte ne demande plus rien au sol, mais sa place dans le
     rayon ne doit pas bouger d'un pixel du clic a la fermeture — la reserve
     en fait partie, en bas comme en haut. */
  margin-bottom:
    calc(var(--rang-sol-bas, 0px)
         + var(--piece-place, var(--piece)) - var(--piece));
  /* Elle deborde, donc elle passe devant : les bandes qui suivent sont plus
     loin dans le document et peindraient par-dessus la scene le temps
     qu'elles s'effacent. Au-dessus de leur pastille, qui monte a 2 chez elle
     et remonte dans la pile de la page tant que sa bande est pleine — une
     bande pleine ne s'est pas encore fondue, donc ne se referme pas sur son
     contenu. */
  z-index: 3;
}

@supports (height: 100svh) {
  .rang--ouvert { --piece: 100svh; }
}

/* LA SCENE VA D'UN BORD DE LA FENETRE A L'AUTRE, LE PANNEAU SE POSE DESSUS.

   Elle ne prenait que la premiere colonne, et le canvas s'arretait donc au
   bord du panneau : tout ce qui debordait de ce bord-la etait COUPE par une
   arete qu'on ne voit pas — le disque du socle en premier, alors qu'il n'y a
   aucune raison qu'un cercle pose au sol s'arrete la ou commence une
   cartouche de texte. Il passe DESSOUS, et pour ca il faut que la toile
   continue dessous.

   La grille ne sert donc plus qu'a placer le panneau : la scene enjambe
   toutes ses colonnes, le panneau en occupe la derniere, et il est devant
   (`z-index`, plus bas). */
.rang--ouvert > .rang__visuel { grid-area: 1 / 1 / 2 / -1; }
.rang--ouvert > .rang__texte { grid-area: 1 / 1; }

/* La scene. Elle porte le zoom, donc elle a besoin d'un point fixe : le
   mouvement la ramene sur l'etat d'avant en la reduisant autour de ce point,
   puis la relache. Le centre n'est qu'un repli — sur un livre, l'apercu pose
   le point du LIVRE, qui n'est pas celui de la boite (cf. epoque.js, pose).
   L'origine ecrite ici sert donc a ce qui n'a pas de moteur : une image, ou
   la boite et ce qu'on regarde sont le meme objet.

   ELLE EST EPINGLEE. Un ecran de haut, retenue en haut de la fenetre tant que
   la bande dure : le livre, son sol et l'angle sous lequel on les voit ne
   bougent plus d'un pixel pendant qu'on defile. Ce n'est pas un effet, c'est
   la definition de l'endroit — on est ENTRE, et on ne descend pas le long de
   ce dans quoi on est entre. Ce qui defile, c'est le panneau.
   Le moteur, lui, doit l'apprendre : sa camera lit sa place en mise en page,
   et retenue elle n'y est plus (cf. epoque.js, la scene epinglee). */
.rang--ouvert .rang__visuel {
  position: sticky;
  top: 0;
  height: var(--piece);
  /* LA TOILE NE DEBORDE PLUS : IL N'Y A PLUS D'AIR OU DEBORDER.

     Dans le rayon, la toile mord dans l'air de la bande pour laisser monter
     une couverture qu'on entrouvre (cf. --toile-air). Ici la scene fait
     exactement un ecran : ce qui deborde par le bas sort de la fenetre, et
     c'est la barre du feuilletage qui s'en allait avec — les commandes de
     l'objet, hors de la vue de l'objet. Il n'y a d'ailleurs rien a laisser
     monter : les mains ouvertes, le cadrage reserve deja la place de la
     feuille en vol (livre.js, margeVol).

     Le changement de place se fait AVEC le changement de mise en page, pas
     pendant le geste : la bande est batie ouverte, puis mesuree, puis le
     mouvement part (cf. epoque.js, pose). */
  --toile-air: 0px;
  padding-bottom: clamp(10px, 2vh, 26px);
  transform-origin: 50% 50%;
  will-change: transform;
}

/* ---------------------------------------------------------
   LE PANNEAU — ce qui se lit, et ce qui bouge

   Tout ce que l'objet a a dire tient sur un rectangle blanc
   pose devant la scene. Il ne fait pas partie du decor : c'est
   la cartouche de l'objet, de la maison et non de l'univers —
   d'ou le blanc et l'encre noire, la ou tout le reste de la
   piece appartient a la peau.

   C'EST LUI, ET LUI SEUL, QUI DEFILE. Il reste dans le flux
   pendant que la scene est epinglee : descendre la page le fait
   monter le long de l'objet, sans que l'objet ni le point de vue
   ne bougent. Il n'y a rien a animer pour ca — la mise en page
   suffit.
   --------------------------------------------------------- */
.rang--ouvert .rang__texte {
  position: relative;
  z-index: 1;
  width: var(--panneau-l);
  /* IL NE DEFILE PLUS : IL TIENT DANS LA PIECE.

     Sa course etait sa propre hauteur — la bande faisait un ecran plus ce que
     le panneau depassait, et descendre le faisait remonter le long de l'objet.
     C'etait juste tant que la piece pouvait deborder de la vue. Elle n'en
     deborde plus : tout ce qu'il y a a voir tient dans un ecran, et la page se
     bloque pendant qu'on est dedans. Le panneau se borne donc a ce qui reste
     de piece sous lui, et se parcourt chez lui le jour ou un objet aura trop a
     dire — c'est la seule chose qui defile encore, et elle ne bouge pas la
     scene.

     Sans colonne a lui (le telephone), il se pose dans le BAS de la piece, sur
     le sol libre sous le livre. */
  /* ET IL SE POSE SUR LA PLAQUE, AU MILIEU. Accroche aux 40 % de la piece, un
     cartel court laissait un trou sous lui et coupait l'objet au-dessus ;
     pousse a droite, il n'etait centre que sur un telephone exactement
     large de sa gouttiere. Il descend donc jusqu'a la plaque d'acquerir, et
     tout ce qui est au-dessus de lui est a l'objet (cf. epoque.js, la
     reserve basse). La borne haute reste la meme : `--panneau-haute`. */
  justify-self: center;
  align-self: end;
  margin: var(--panneau-haut) var(--gouttiere)
          calc(var(--panneau-bas) + var(--acquerir-h) + var(--acquerir-ecart));
  /* Sa zone est sa BORNE, pas sa taille — c'est la seule chose qui separe
     l'ecran etroit du grand : ici la cartouche n'a pas de colonne a tenir,
     elle flotte dans la piece comme le reste, et un objet muet n'a pas a
     porter un rectangle blanc vide jusqu'en bas. Ce qu'elle ne prend pas
     reste du sol ; la plaque, elle, s'arrete ou la piece s'arrete. */
  max-height: var(--panneau-haute);
  overflow-y: auto;
  padding: clamp(18px, 2.6vh, 28px) clamp(18px, 2vw, 28px);
  background: var(--papier);
  color: var(--encre);
  /* Meme lumiere que les objets — il est pose dans la piece, il n'est pas
     colle sur l'ecran. */
  box-shadow:
    calc(var(--light-x) * 4px) calc(var(--light-y) * 4px) 8px -3px rgba(0, 0, 0, 0.30),
    calc(var(--light-x) * 14px) calc(var(--light-y) * 14px) 30px -10px rgba(0, 0, 0, 0.40);
}

/* ---------------------------------------------------------
   LA CARTOUCHE ENTRE PAR LE COTE — et c'est LE MEME GESTE

   Elle se posait d'un coup, a sa place, et bougeait ensuite
   d'un cheveu pendant que la piece finissait d'arriver : deux
   evenements pour une seule approche, dont un qui ne
   ressemblait a rien.

   Elle arrive donc de dehors, portee par le nombre du geste
   (`--apercu-t`) comme tout ce qui bouge pendant un zoom. Rien
   n'a d'horloge a soi : au recul, joue a l'envers, elle
   ressort du meme cote et par le meme chemin.

   PAR LE COTE LE PLUS COURT, LE PLUS PROCHE. C'est la feuille
   qui le sait, parce que c'est elle qui pose le panneau : sans
   colonne a lui il se range dans le BAS de la piece, donc il
   vient du bas ; des qu'il prend le dernier tiers, il vient de
   la DROITE. Le JS ne dit que la distance — de son bord au
   bord de la fenetre, mesuree au repos (cf. epoque.js, d'ou la
   cartouche arrive). Sans mesure, une fenetre entiere : le
   repli sort du champ, il n'y a rien a tenir a jour en double.

   Le cartel et la plaque partent ensemble, de la meme
   distance : ce sont deux morceaux d'une meme cartouche, pas
   deux objets qui se suivent.
   --------------------------------------------------------- */
.rang--ouvert {
  /* LE NOMBRE VIF, PAS LE NOMBRE ADOUCI. Le geste est amorti — c'est un
     oeil qui s'approche, il part et arrive doucement. La cartouche, elle, ne
     s'approche pas : elle se superpose. Elle prend donc le meme trajet lu sur
     l'autre courbe (cf. epoque.js, vif) — lancee au premier pixel, freinant
     jusqu'a sa place, et rendue avant que la piece ait fini d'arriver. */
  --panneau-entree: calc(1 - var(--apercu-vif, 1));
  /* UNE SEULE DIRECTION. La sortie sur son axe, et sur l'autre la seule
     chose qui l'en ecarterait : le defilement du travelling, qu'on lui rend
     (cf. epoque.js, la cartouche ne suit pas la page). Le trajet est droit,
     et il l'est dans la FENETRE — pas seulement dans le document. */
  --panneau-glisse:
    translate(0px, calc(var(--panneau-venue-y, 100vh) * var(--panneau-entree)
                        + var(--panneau-derive, 0px)));
}
.rang--ouvert > .rang__texte,
.rang--ouvert > .rang__acquerir {
  transform: var(--panneau-glisse);
  will-change: transform;
}

/* DES QU'IL Y A DE LA LARGEUR, LES DEUX CESSENT DE SE SUPERPOSER : le livre
   se tient dans la partie gauche de l'ecran, le panneau se range a droite, et
   plus rien ne passe devant l'objet.

   ET CE N'EST PLUS LA GRILLE QUI LE DIT. Elle l'a dit en colonnes — deux
   tiers pour l'objet, un tiers pour la cartouche — et une colonne ne fait que
   BORNER une boite : elle ne dit rien a la scene, qui enjambe toute la grille
   et cadre son livre au milieu de la toile. Le livre finissait donc sous le
   panneau, de vingt a cent pixels selon la fenetre, et le tiers du panneau
   fondait a mesure que la gouttiere grossissait.

   Les deux se parlent maintenant, et le JS est le seul endroit ou ils
   peuvent le faire : la scene CEDE une bande a droite, le livre se cadre dans
   ce qui reste, le panneau se pose dans ce qui a ete cede (cf. epoque.js, la
   place du panneau, et livre.js, Livre.reserve). La feuille n'a plus qu'a
   dire la taille de la cartouche et a la coller au bord droit.

   La ligne du haut est la seule ligne de la grille — la scene doit pouvoir se
   retenir sur toute la hauteur de la bande, or une position collante ne
   depasse pas sa propre case. */
@media (min-width: 720px) {
  .rang--ouvert {
    --piece-partage: 1;
    padding-right: 0;
    /* En haut : le cadrage se fait sur la LARGEUR de la double page
       (cf. livre.js, mesureCadre) et reserve de quoi lever une feuille — le
       livre tient donc une bande horizontale au milieu de la piece, et le
       panneau se pose a hauteur de regard, dans le blanc qui reste au-dessus. */
    --panneau-haut: clamp(40px, 8vh, 96px);
    --panneau-bas: var(--piece-marge);
    /* LE DERNIER TIERS, ENTIER — pour la cartouche comme pour la plaque. */
    --panneau-l: var(--piece-tiers);
    /* ET ICI, ELLE N'ENTRE PAS : ELLE EST LA.

       Sur un ecran couche, la cartouche a une colonne a elle, a cote de
       l'objet : c'est un des deux tiers de la piece, pas une surface qui
       passe. La voir traverser tout l'ecran pour venir se ranger dans une
       place qui lui est deja reservee, c'est raconter une arrivee la ou il
       n'y a qu'un partage — et le trajet est long, donc c'est long. Elle se
       pose donc avec la piece, du premier pixel.

       Sur un ecran debout elle n'a pas de colonne : elle se pose PAR-DESSUS
       la scene, dans le bas. La, c'est bien une surface qui vient — et c'est
       le mouvement d'a cote (cf. la cartouche entre par le cote).

       CE QUI RESTE, C'EST LA DERIVE. Sans entree, il n'y a plus de trajet a
       tenir droit, mais la cartouche est toujours dans le flux au-dessus
       d'une scene epinglee : le travelling la ferait glisser vers le haut
       pendant que la piece arrive. Elle ne bouge pas du tout — c'est ce que
       « sans animation » veut dire. */
    --panneau-glisse: translate(0px, var(--panneau-derive, 0px));
  }
  /* LE PANNEAU NE SE RANGE PLUS DANS UNE COLONNE : IL SE POSE A UNE PLACE.

     Il occupait la derniere colonne de la grille, et sa largeur etait donc ce
     que cette colonne voulait bien lui laisser. Or ce n'est pas la grille qui
     decide de cette piece-la : le livre se cadre dans ce que la scene ne cede
     pas (cf. epoque.js, la place du panneau), et le panneau se pose dans ce
     qu'elle a cede. Les deux se sont deja accordes quand la feuille arrive —
     il ne lui reste qu'a coller la cartouche au bord droit, a la distance
     mesuree.

     Il enjambe donc toute la grille, comme la scene, et se cale a droite. */
  /* ELLE SE POSE AU CLIC, ELLE S'EFFACE AU CLIC.

     Ce qui n'est pas venu ne s'en va pas : sans entree, la cartouche
     resterait posee, pleine et nette, pendant tout le dezoom, et
     disparaitrait a la derniere image — le recul se lirait en retard sur
     lui-meme. Elle s'en va donc a l'instant ou le retour est lance
     (`data-recul`, pose par epoque.js), comme elle etait apparue a l'instant
     du clic.

     PAR UN FONDU, ET PAS PAR UN TRAJET. Un objet du rayon s'eloigne ; une
     surface posee par-dessus n'a nulle part ou aller — elle cesse d'etre la.
     Le fondu est court devant le recul : elle est partie avant le tiers du
     chemin, et ce qui reste a voir reculer se voit en entier. Il ne touche
     qu'a l'encre — la piece garde sa mise en page, le livre son cadrage, et
     rien ne se remesure.

     Sur un ecran debout, rien de tout ca : elle est entree par le bas, elle
     ressort par le bas, et le meme nombre mene les deux. */
  .rang--ouvert > .rang__texte { transition: opacity 260ms ease-out; }
  .rang--ouvert[data-recul] > .rang__texte,
  .rang--ouvert[data-recul] > .rang__acquerir { opacity: 0; }

  .rang--ouvert > .rang__texte {
    grid-area: 1 / 1 / 2 / -1;
    justify-self: end;
    /* LE DERNIER TIERS, ENTIER. Son bord gauche tombe donc exactement sur la
       ligne des deux tiers — celle ou le livre est centre, et jusqu'ou son
       cadre va (cf. epoque.js, le partage en trois). */
    width: var(--panneau-l);
    /* TOUTE SA ZONE, DU HAUT VERS LE BAS. La hauteur EST la zone
       (cf. --panneau-haute) : il n'y a plus rien a borner par-dessus, et la
       borne du bas — posee sur la gouttiere, qui grandit avec la fenetre —
       se retire, sans quoi elle rognerait la zone sur un grand ecran. */
    height: var(--panneau-haute);
    max-height: none;
    align-self: start;
    margin-bottom: 0;
    margin-inline: 0 var(--piece-marge);
  }
}

/* La fleche de retour. Elle ne vit pas dans le flux : elle se pose au coin
   haut gauche de la VUE — la ou l'oeil va chercher la sortie de n'importe
   quel endroit ou l'on est entre — et elle y reste. Fixe, et non plus posee
   au coin de la bande : la bande fait maintenant plus d'un ecran, et une
   sortie qui defile hors du champ n'est plus une sortie. Pas de boite, pas de
   fond — un signe, et la surface de touche autour de lui. */
.apercu__retour {
  position: fixed;
  top: clamp(6px, 1.4vh, 18px);
  left: clamp(6px, 1.2vw, 20px);
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
  opacity: 0.55;
  transition: opacity 200ms linear, transform 200ms cubic-bezier(0.22, 0.61, 0.36, 1);
}
.apercu__retour:hover,
.apercu__retour:focus-visible:not([data-sans-anneau]) { opacity: 1; transform: translateX(-3px); }

.apercu__fleche {
  font-family: var(--mono);
  font-size: 22px;
  line-height: 1;
}

.rang--ouvert .rang__visuel img { max-height: 100%; }

/* L'hote du moteur : le livre se monte dedans avant d'etre demande, et le
   suit ensuite partout. Il ne doit jamais s'interposer entre la scene et le
   livre — il disparait de la mise en page sans disparaitre du DOM. */
.sujet__hote { display: contents; }

/* Le livre dans une bande — fermee comme ouverte. C'est le meme objet : il
   prend la place qu'on lui fait, et la barre, quand elle est la, se pose
   sous lui. Aucune hauteur ecrite ici : elle vient de .rang__visuel, qui
   est la seule a savoir de combien de place on dispose. */
.rang .sujet {
  display: flex;
  flex-direction: column;
  /* .rang__visuel centre ses enfants : sans ceci le livre garderait la
     hauteur de son contenu, et la toile retomberait sur la sienne propre —
     celle de la fiche produit, ou elle est seule au monde. */
  align-self: stretch;
  flex: 1 1 auto;
  width: 100%;
  min-width: 0;
  margin-top: 0;
}
.rang .sujet__toile { flex: 1 1 0%; height: auto; min-height: 0; }

/* LA TOILE DEBORDE SA PLACE, ET C'EST VOULU.

   Un livre qui s'entrouvre au survol leve sa couverture BIEN au-dessus de
   sa silhouette fermee — et dans une vue presque a plat, la hauteur se
   projette environ trois fois plus fort que la longueur : douze degres de
   couverture montent plus haut que tout ce que le livre ferme occupait.
   Cadree sur la boite fermee, la toile coupait ce geste net.

   Il y avait trois facons de lui faire de la place, et deux sont mauvaises :
   retrecir le livre pour reserver la hauteur (le monde prend la plus petite
   echelle de la page — un livre paie, tous retrecissent, et pour un
   mouvement qui ne dure qu'un survol), ou grandir `.rang__visuel` (le rayon
   s'allonge, alors qu'il a justement ete raccourci pour se PARCOURIR).

   La troisieme ne coute rien : le rang porte deja de l'air autour de son
   visuel. On n'agrandit donc pas la PLACE du livre, on agrandit ce qu'on a le
   droit de DESSINER autour — la toile deborde dans cet air, en haut et en bas
   pour que le livre ne se decale pas, et la mise en page ne bouge pas d'un
   pixel. Une toile aux mains fermees ne prend ni le clic ni le tabulateur
   (cf. .sujet--nu), son debordement ne peut donc rien attraper.

   Elle reste constante d'un etat a l'autre : la faire varier entre le rang et
   l'apercu, ce serait redimensionner la toile au milieu du geste qui les
   relie. */
/* Il se declare sur le RAYON, pas sur la bande : la ligne qui se balaye a
   besoin du meme nombre pour rendre en dedans ce qu'elle coupe en dehors
   (cf. .ligne). La bande ouverte le remet a zero chez elle, plus haut. */
.rayon { --toile-air: clamp(24px, 4vh, 46px); }
.rang .sujet {
  margin-top: calc(-1 * var(--toile-air));
  margin-bottom: calc(-1 * var(--toile-air));
}

/* Le livre ne se souleve PAS au survol. Un objet plat de la pile peut
   flotter d'un cheveu — c'est une image, elle n'a pas de poids. Le moteur,
   lui, dessine un volume pose quelque part, avec son ombre portee sous
   lui : le decoller, c'est le faire leviter au-dessus de sa propre ombre.
   Ce qu'il a a dire au survol, il le dit en s'ouvrant. */

/* Un livre aux mains fermees (livre.js, Livre.commandes) : il n'est qu'une
   image, et le lien de la bande doit passer par-dessus lui entier. */
.sujet--nu .livre__barre { display: none; }
.sujet--nu .sujet__toile { pointer-events: none; cursor: inherit; }

/* PAS DE COMMANDES DANS LA PIECE. La barre « page precedente / page
   suivante » y prenait une quarantaine de pixels sous la toile : la toile
   s'arretait donc avant le bas de la piece, et le disque du socle se faisait
   trancher sur cette arete-la — invisible, puisqu'il restait du sol dessous.
   Elle sera refaite ; en attendant elle sort de la piece, la toile reprend
   toute la hauteur, et les pages se tournent comme partout ailleurs : au clic
   gauche ou droit sur le livre. */
.rang--ouvert .livre__barre { display: none; }

.apercu__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: clamp(14px, 2.4vw, 26px);
  margin-top: clamp(14px, 2.4vh, 22px);
}

.apercu__actions .fiche__achat { margin: 0; }

/* ---------------------------------------------------------
   LA PLAQUE D'ACQUERIR — sous le cartel, et a part

   Le geste se lit deja dans la cartouche, sous le prix, avec
   le port et le reste de ce que l'objet a a dire — et il part
   avec elle des qu'on parcourt la prose. La plaque, elle, ne
   bouge pas : posee sous le cartel, meme largeur, encre pleine
   qui s'inverse au survol, meme lumiere — la maison, pas l'univers — et elle s'arrete
   ou la piece s'arrete (`--panneau-bas`).

   Toute la plaque EST le geste : le trait sous le mot ne dit
   rien de plus qu'elle, il s'efface, et c'est l'encre qui
   repond au survol. Sa hauteur est la sienne, mesuree a
   l'ouverture par le JS (`--acquerir-h`) — la cartouche lui
   cede exactement ca.
   --------------------------------------------------------- */
.rang--ouvert > .rang__acquerir {
  grid-area: 1 / 1 / 2 / -1;
  z-index: 2;
  justify-self: center;
  align-self: end;
  width: var(--panneau-l);
  margin-inline: var(--gouttiere);
  margin-bottom: var(--panneau-bas);
  background: var(--acquerir-encre);
  color: var(--papier);
  box-shadow:
    calc(var(--light-x) * 4px) calc(var(--light-y) * 4px) 8px -3px rgba(0, 0, 0, 0.30),
    calc(var(--light-x) * 14px) calc(var(--light-y) * 14px) 30px -10px rgba(0, 0, 0, 0.40);
  /* L'encre repond au survol ; l'opacite ne sert qu'au depart, sur un ecran
     couche (cf. elle se pose au clic, elle s'efface au clic). On ajoute a la
     liste, on ne la remplace pas : la reecrire ailleurs, c'est perdre
     l'inversion du survol sans que rien ne le dise. */
  transition: background 160ms linear, color 160ms linear,
              opacity 260ms ease-out;
}
@media (min-width: 720px) {
  .rang--ouvert > .rang__acquerir {
    justify-self: end;
    margin-inline: 0 var(--piece-marge);
  }
}

.rang__acquerir .bouton {
  display: block;
  width: 100%;
  padding: clamp(15px, 2.1vh, 21px) clamp(14px, 2vw, 22px);
  border-bottom: 0;
  font-size: 1em;
  letter-spacing: 0.16em;
  text-align: center;
  color: inherit;
}
.rang__acquerir .bouton:hover { opacity: 1; }

/* L'etat se dit par la FORCE de l'encre : noir plein quand l'objet
   s'acquiert, gris quand il ne s'acquiert pas — un gris, c'est-a-dire du
   noir qu'on retient, jamais une troisieme couleur. Le mot y garde sa
   pleine opacite : c'est la plaque qui porte l'etat, plus besoin de faner
   la lettre par-dessus.

   Et le gris ne s'allume pas. L'inversion au survol est une PROMESSE de
   clic ; une plaque grise n'en a aucune a faire, donc elle ne bouge pas —
   c'est ce que le gris annonce, ce serait mentir de l'animer quand meme. */
.rang__acquerir {
  --acquerir-encre: var(--encre);
}
.rang__acquerir:has(.bouton--inerte) {
  --acquerir-encre: color-mix(in srgb, var(--encre) 42%, var(--papier));
}
.rang__acquerir .bouton--inerte { opacity: 1; }

/* Et le mot est BARRE. Le gris se remarque, la barre se lit : elle dit du
   geste lui-meme qu'il ne se fait pas, sans qu'on ait a comparer cette
   plaque a une autre plus noire vue ailleurs.

   Ce qui l'explique, en revanche, ne se barre pas — on ne raye pas la
   raison. La note vit sous le mot, dans la meme encre en plus discret, et
   n'apparait que quand l'objet a quelque chose a dire de son etat : sans
   lien de paiement, il n'y a rien a expliquer, juste rien a promettre. */
.rang__acquerir .bouton--inerte { text-decoration: line-through; }

.rang__acquerir:has(.rang__acquerir__note) .bouton { padding-bottom: 3px; }
.rang__acquerir__note {
  margin: 0;
  padding: 0 clamp(14px, 2vw, 22px) clamp(13px, 1.8vh, 18px);
  font-family: var(--mono);
  font-size: 0.68em;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-align: center;
  text-decoration: none;
  opacity: 0.72;
}

.rang--ouvert > .rang__acquerir:has(.bouton:not(.bouton--inerte)):hover,
.rang--ouvert > .rang__acquerir:has(.bouton:focus-visible) {
  background: var(--papier);
  color: var(--acquerir-encre);
}

/* ---------------------------------------------------------
   Le feuilletage — l'interieur d'un ouvrage
   Une page pivote autour de son bord gauche ; celle d'apres
   attend dessous. Deux images en tout, jamais la pile entiere :
   un ouvrage de soixante pages coute le meme poids qu'un de
   quatre.
   --------------------------------------------------------- */

.feuillet { margin-top: clamp(40px, 7vh, 80px); }

.feuillet__scene {
  position: relative;
  /* La perspective donne son epaisseur au geste : sans elle, la page ne
     tourne pas, elle se retrecit. */
  perspective: 1800px;
  height: clamp(280px, 52vh, 560px);
  display: flex;
  align-items: center;
  justify-content: center;
}

.feuillet__page {
  position: absolute;
  max-height: 100%;
  width: auto;
  object-fit: contain;
  backface-visibility: hidden;
}

.feuillet__page--dessus {
  transform-origin: left center;
  transition: transform 620ms cubic-bezier(0.4, 0.1, 0.2, 1),
              opacity 620ms linear;
}

/* Avancer : la page se leve vers la gauche et se retire. Reculer : elle
   revient du meme cote, dans l'autre sens. */
.feuillet__page--dessus.tourne-avant {
  transform: rotateY(-168deg);
  opacity: 0.15;
}
.feuillet__page--dessus.tourne-arriere {
  transform: rotateY(168deg);
  opacity: 0.15;
}

.feuillet__barre {
  display: flex;
  align-items: baseline;
  gap: clamp(16px, 3vw, 30px);
  margin-top: clamp(18px, 3vh, 30px);
}

.feuillet__compte {
  font-family: var(--mono);
  font-size: 0.8em;
  letter-spacing: 0.14em;
  margin-right: auto;
}

/* ---------------------------------------------------------
   Le livre — le moteur canvas (assets/js/livre.js)
   Il se substitue au feuilletage ci-dessus quand le catalogue
   porte un bloc 'livre' ; sinon rien de ceci ne sert. Le CSS
   ne fait ici que reserver une surface : tout le dessin est
   dans le canvas, et aucune valeur d'ici n'entre dans la
   geometrie du livre — elle vient du descriptif, en
   millimetres. Voir LIVRE.md.
   --------------------------------------------------------- */

.sujet {
  margin-top: clamp(40px, 7vh, 80px);
  /* Le repere de la toile du sol, qui se pose dessus (cf. .sujet__sol). Rien
     d'autre ne change : `relative` sans z-index ne fait pas d'etage, donc ce
     qui se glisse ENTRE les deux toiles peut encore le faire depuis la
     bande. */
  position: relative;
}

/* ---------------------------------------------------------
   LE SOL DE L'OBJET — la meme image, a l'etage du dessous

   Le disque n'est pas l'objet : c'est ce sur quoi l'objet est
   pose, et le moteur le disait deja la ou il compte ce qu'on
   peut toucher. Il se peignait pourtant dans la meme toile,
   donc au meme etage — et tout ce que la page pose sur le
   disque, la pastille du prix, se retrouvait forcement
   par-dessus l'objet : un canvas est une seule image, on ne
   se glisse pas dedans. Sur un ouvrage ca ne se voyait pas,
   le disque debordant largement ; sur un pin's, le jeton
   mangeait le dessin.

   Deux toiles exactement superposees, donc, et la page a
   maintenant un entre-deux ou poser ce qui appartient au
   cercle. Sa boite se MESURE sur celle de l'objet (cf.
   sujet.js, redimensionne) : le sol suit la toile, il ne se
   cadre pas tout seul. Absolue, elle ne prend aucune place ;
   sans mesure elle ne montre rien, ce qui est exactement ce
   qu'un moteur absent laisse voir.
   --------------------------------------------------------- */
.sujet__sol {
  position: absolute;
  left: 0;
  top: 0;
  display: block;
  /* Ce n'est pas l'objet : rien ne s'y attrape (cf. Peintre.rend, `vus`). */
  pointer-events: none;
}

/* CE QUI PEINT L'OBJET — posee sur sa place, a la taille de ce qu'elle
   porte (cf. sujet.js, Peintre.cadre). La place (`.sujet__toile`) garde la
   boite, le clic et le curseur ; celle-ci n'a que les pixels, au meme etage
   qu'elle et juste apres elle dans le document, donc par-dessus. */
.sujet__image {
  position: absolute;
  left: 0;
  top: 0;
  z-index: 1;
  display: block;
  pointer-events: none;
}

.sujet__toile {
  /* AU-DESSUS DU SOL, PARTOUT. Les deux toiles sont posees l'une sur
     l'autre ; celle du sol est absolue, donc dans l'etage des positionnes, et
     une toile restee dans le flux passerait dessous. Un cran suffit — la
     bande ouverte range son panneau au-dessus par le meme moyen. */
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  height: clamp(300px, 56vh, 620px);
  /* Le clic gauche/droite tourne la page : on veut le geste tout de suite,
     pas apres les 300 ms d'attente d'un double-tap. */
  touch-action: manipulation;
}
/* La main seulement la ou un clic repond — c'est la scene qui le dit
   (livre.js : sur le papier d'un livre ouvert). Une plaque n'a rien a
   cliquer ; ses fleches de tour portent leur propre main (.tour__prise). */
.sujet__toile[data-clic] { cursor: pointer; }

/* Le pendant du reglage `lissage: false` de livre.js. Le canvas est peint a
   la densite de l'ecran puis repose a la taille CSS : sans ceci, le
   navigateur re-lisse a l'affichage ce que le dessin avait garde net. */
.sujet__toile--pixels {
  image-rendering: crisp-edges;   /* Firefox */
  image-rendering: pixelated;     /* les autres — declare en dernier, il gagne */
}

.livre__barre {
  display: flex;
  align-items: baseline;
  gap: clamp(16px, 3vw, 30px);
  margin-top: clamp(18px, 3vh, 30px);
}

.livre__compte {
  font-family: var(--mono);
  font-size: 0.8em;
  letter-spacing: 0.14em;
  margin-right: auto;
}

.bouton:disabled {
  border-bottom-color: transparent;
  opacity: 0.3;
  cursor: default;
}

/* ---------------------------------------------------------
   Mouvement reduit — la ou l'animation raconte quelque chose,
   ce quelque chose doit rester lisible sans elle.
   --------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }

  .rang__ferme { --ferme-penche: 0deg; transform: none; }

  /* Le zoom, lui, ne disparait pas : ce n'est pas du mouvement, c'est un
     etat — on est pres de l'objet, ou on ne l'est pas. Seul le chemin pour
     y arriver se tait, et il se tait en JS (Epoque.apercu) : sans mouvement,
     le geste POSE sa fin, et le rayon s'eteint ou se rallume avec elle. Rien
     a couper ici — il n'y a plus de transition a taire. */
}

/* ---------------------------------------------------------
   Weave — le code de beta, le journal des versions
   --------------------------------------------------------- */

/* Le champ est un mot souligne, comme un bouton : pas de boite. */
.weave__beta { margin: clamp(28px, 4vh, 44px) 0; }
.weave__code { display: flex; flex-direction: column; gap: 8px; margin: 0 0 18px; max-width: 24ch; }
.weave__code input {
  font: inherit;
  font-family: var(--mono);
  letter-spacing: 0.12em;
  background: none;
  color: inherit;
  border: 0;
  border-bottom: 1px solid var(--encre);
  border-radius: 0;
  padding: 0 0 3px;
}
.weave__code input:focus-visible { outline: none; border-bottom-width: 2px; padding-bottom: 2px; }

.journal { list-style: none; margin: clamp(28px, 4vh, 44px) 0 0; padding: 0; }
.journal__version { margin: 0 0 clamp(24px, 3.5vh, 36px); }
.journal__version .prose { margin: 10px 0 0; padding-left: 1.1em; }
.journal__version .prose li { margin: 0 0 0.4em; }

/* =========================================================
   PANIER — la pile, puis le ticket (cf. panier.js, PAIEMENT.md).
   Noir et blanc : la couleur n'est qu'aux vignettes, qui sont les oeuvres.
   ========================================================= */
/* LE TALON : le bout du ticket qui depasse de la thermique, colle au haut
   de l'ecran, toujours la. Il deborde un peu plus quand on le vise — on le
   tire, il se deroule (le ticket part du meme bord, du meme cote). */
.talon, .pile { --talon-h: 54px; }
/* Le talon EST le haut du ticket : une seule largeur, lue par les deux. */
.talon, .ticket {
  --ticket-l: min(288px, calc(100vw - 32px));
  /* Un ticket de caisse, pas un rouleau : petites dents, tirets imprimes,
     une ombre courte et nette — le papier est pose, il ne flotte pas. */
  --ticket-dent: 4px;
  --ticket-tirets: repeating-linear-gradient(90deg, var(--encre) 0 5px, #0000 5px 9px);
  --ticket-ombre: drop-shadow(0 1px 1px rgb(0 0 0 / 0.28)) drop-shadow(0 3px 5px rgb(0 0 0 / 0.1));
  /* Un ticket de caisse est blanc, de jour comme de nuit : la nuit de la
     maison s'arrete a son bord. */
  --papier: var(--blanc);
  --encre:  var(--noir);
}
.talon {
  position: fixed;
  top: 0;
  right: max(16px, env(safe-area-inset-right));
  z-index: 20;
  width: var(--ticket-l);
  height: var(--talon-h);
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  /* L'ombre sur le bouton, le papier dessous : un masque couperait l'ombre. */
  filter: var(--ticket-ombre);
  transform: translateY(-6px);
  transition: transform 220ms cubic-bezier(.2, .8, .2, 1);
}
.talon:hover, .talon:focus-visible { transform: none; }
.talon__mot {
  --dent: var(--ticket-dent);
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 6px 18px var(--dent);
  background: var(--papier);
  color: var(--encre);
  font-family: var(--mono);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  -webkit-mask:
    linear-gradient(#000 0 0) top / 100% calc(100% - var(--dent)) no-repeat,
    conic-gradient(from -45deg at bottom, #000 90deg, #0000 0)
      50% 100% / calc(2 * var(--dent)) calc(2 * var(--dent)) repeat-x;
  mask:
    linear-gradient(#000 0 0) top / 100% calc(100% - var(--dent)) no-repeat,
    conic-gradient(from -45deg at bottom, #000 90deg, #0000 0)
      50% 100% / calc(2 * var(--dent)) calc(2 * var(--dent)) repeat-x;
}
/* Les tirets au-dessus du mot : la premiere ligne imprimee du ticket. */
.talon__mot::before {
  content: "";
  align-self: stretch;
  height: 1px;
  background: var(--ticket-tirets);
}
/* Sur un ecran etroit, le talon n'a que la largeur de son mot : a pleine
   largeur, il couvrait le nom de la maison. Le ticket deroule garde la sienne. */
@media (max-width: 600px) {
  .talon { width: auto; }
  .talon__mot { position: relative; height: 100%; box-sizing: border-box; padding-inline: 20px; }
}

/* La pile des choses prises se pose sous le talon. */
.pile {
  position: fixed;
  right: max(16px, env(safe-area-inset-right));
  top: calc(var(--talon-h) + 10px);
  z-index: 20;
  width: 76px;
  height: 76px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  color: var(--encre);
}
.pile[hidden] { display: none; }
.pile__tas { position: absolute; inset: 0; }

/* Chaque objet est jete sur le precedent : un peu plus haut, de travers. */
.pile__objet {
  position: absolute;
  left: 8px;
  bottom: calc(4px + var(--rang, 0) * 3px);
  width: 60px;
  height: 60px;
  display: grid;
  place-items: center;
  background: var(--papier);
  box-shadow: 0 1px 0 var(--encre), 0 4px 10px rgb(0 0 0 / 0.18);
  transform: translateX(var(--decale, 0)) rotate(var(--angle, 0));
  overflow: hidden;
}
.pile__objet img { width: 100%; height: 100%; object-fit: contain; }
.pile__nom {
  font-family: var(--mono);
  font-size: 9px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-align: center;
  padding: 4px;
  line-height: 1.2;
}
.pile__objet--tombe { animation: pile-tombe 420ms cubic-bezier(.2, .9, .3, 1.2); }
@keyframes pile-tombe {
  from { transform: translate(var(--decale, 0), -60px) rotate(calc(var(--angle, 0) * -3)); opacity: 0; }
  to   { transform: translateX(var(--decale, 0)) rotate(var(--angle, 0)); opacity: 1; }
}
.pile__compte {
  position: absolute;
  top: -6px;
  right: -6px;
  min-width: 22px;
  height: 22px;
  padding: 0 6px;
  border-radius: 11px;
  background: var(--encre);
  color: var(--papier);
  font-family: var(--mono);
  font-size: 11px;
  line-height: 22px;
  text-align: center;
}
.pile:hover .pile__tas, .pile:focus-visible .pile__tas { transform: translateY(-3px); }
.pile__tas { transition: transform 160ms ease; }

/* Le ticket se DEROULE depuis le haut, comme il sort de la thermique. */
.ticket {
  position: fixed;
  inset: 0;
  z-index: 30;
  pointer-events: none;
}
.ticket[data-ouvert] { pointer-events: auto; }
.ticket__voile {
  position: absolute;
  inset: 0;
  background: color-mix(in srgb, var(--encre) 35%, transparent);
  opacity: 0;
  transition: opacity 240ms ease;
}
.ticket[data-ouvert] .ticket__voile { opacity: 1; }

.ticket__papier {
  --dent: var(--ticket-dent);
  position: absolute;
  top: 0;
  right: max(16px, env(safe-area-inset-right));
  width: var(--ticket-l);
  max-height: calc(100% - 40px);
  overflow-y: auto;
  overscroll-behavior: contain;
  box-sizing: border-box;
  padding: 26px 18px calc(18px + var(--dent));
  background: var(--papier);
  color: var(--encre);
  font-family: var(--mono);
  font-size: 12px;
  line-height: 1.55;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  /* Le bord du bas est arrache : une dent de scie, decoupee dans le papier.
     Les dents ne vivent que dans la bande du bas ; au-dessus, le papier est
     plein (sans quoi les raccords des tuiles se voient en haut). Bord net :
     un bord fondu laissait la rangee du bas pleine — la ligne blanche. */
  -webkit-mask:
    linear-gradient(#000 0 0) top / 100% calc(100% - var(--dent)) no-repeat,
    conic-gradient(from -45deg at bottom, #000 90deg, #0000 0)
      50% 100% / calc(2 * var(--dent)) calc(2 * var(--dent)) repeat-x;
  mask:
    linear-gradient(#000 0 0) top / 100% calc(100% - var(--dent)) no-repeat,
    conic-gradient(from -45deg at bottom, #000 90deg, #0000 0)
      50% 100% / calc(2 * var(--dent)) calc(2 * var(--dent)) repeat-x;
  filter: var(--ticket-ombre);
  transform: translateY(-105%);
  transition: transform 520ms cubic-bezier(.2, .8, .2, 1);
}
.ticket[data-ouvert] .ticket__papier { transform: none; }

.ticket__entete {
  margin: 0 0 6px;
  text-align: center;
  font-size: 1.15em;
  letter-spacing: 0.22em;
  font-weight: 700;
}
.ticket__date { font-size: 0.9em; opacity: 0.7; }
.ticket__trait {
  height: 1px;
  border: 0;
  margin: 10px 0;
  background: var(--ticket-tirets);
}
.ticket__ligne {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  align-items: baseline;
}
.ticket__ligne > :first-child { overflow-wrap: anywhere; }
.ticket__prix { white-space: nowrap; }
.ticket__ligne--discret { opacity: 0.6; }
.ticket__ligne--total {
  margin-top: 4px;
  padding-top: 6px;
  border-top: 3px double var(--encre);
  font-size: 1.15em;
  font-weight: 700;
}
.ticket__qte {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 2px 0 10px;
  opacity: 0.7;
}
.ticket__pas {
  width: 24px;
  height: 24px;
  padding: 0;
  border: 1px solid var(--encre);
  background: none;
  color: inherit;
  font: inherit;
  line-height: 1;
  cursor: pointer;
}
.ticket__pas:hover { background: var(--encre); color: var(--papier); }
.ticket__vide { margin: 8px 0; text-align: center; opacity: 0.6; }

/* Rien de pris : le ticket s'imprime quand meme — l'entete, le mot du vide,
   un total a zero. Ce qui manque, c'est de quoi payer. */
.ticket__pied {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 20px;
}
/* Payer : plaque noire, mot blanc, en bas a droite — l'inversion au survol
   est la promesse du clic. */
.ticket__payer {
  margin-left: auto;
  padding: 9px 16px;
  border: 1px solid var(--encre);
  background: var(--encre);
  color: var(--papier);
  font: inherit;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  cursor: pointer;
}
.ticket__payer:hover, .ticket__payer:focus-visible { background: var(--papier); color: var(--encre); }
.ticket__payer:disabled { cursor: progress; opacity: 0.6; }
.ticket__note { margin: 8px 0 0; font-size: 11px; text-align: right; opacity: 0.6; }
.ticket__fermer {
  padding: 0 0 2px;
  border: 0;
  border-bottom: 1px solid var(--encre);
  background: none;
  color: inherit;
  font: inherit;
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  cursor: pointer;
}

@media (prefers-reduced-motion: reduce) {
  .pile__objet--tombe { animation: none; }
  .ticket__papier, .ticket__voile, .pile__tas, .talon { transition: none; }
}

/* Quand la piece se partage (le seuil de `.rang--ouvert`, --piece-partage),
   le dernier tiers est au panneau, du haut en bas : la pile reste en haut a
   droite, mais de la SCENE — juste avant le panneau, lue sur le meme
   partage (--piece-marge, --piece-tiers). Le talon, lui, ne bouge pas : il
   tient au-dessus du panneau, et le ticket se deroule sous lui, au bord de
   la fenetre — deplace, il tombait au milieu de la scene, loin de son papier. */
@media (min-width: 720px) {
  html[data-apercu] .pile {
    right: calc(var(--piece-marge) + var(--piece-tiers) + 16px);
  }
}
