/* ====================== Cockpit — cockpit.css (S30) ======================
   Feuille PROPRE au kiosk (grille, panneau, carrousel, échelles). Charte
   visuelle (couleurs, police, socle) : voir socle.css, chargé avant celle-ci
   par index.html — aucun token de couleur/police n'est redéfini ici (§5.8).
   Couleurs exprimées via var(...) et color-mix() sur les tokens du socle :
   pas de code hexadécimal, pas de rgb()/rgba() littéral dans ce fichier. */

:root{
  /* Layout */
  --agenda-w:78%; --agenda-h:calc(100vh - 32px);
  --carousel-h:36vh;

  /* Carrousel */
  --carousel-fit: cover;
  --carousel-align: 50% 50%;
  --carousel-interval: 10000ms;
  --carousel-fade: 1200ms;

  /* A9 — socle d'échelle fluide (S147). UNITÉ UNIQUE : chaque taille du
     panneau (horloge, température, citation, météo, agenda) s'exprime
     désormais comme calc(var(--u) * N) au lieu d'un clamp(…vw…) propre
     à chaque propriété — un seul curseur à régler, jamais vingt.
     vmin (le plus petit des deux axes du viewport) REMPLACE vw : il
     unifie en une formule ce que deux seuils séparés faisaient avant
     (1024px de large ET 700px de haut) — un écran COURT (box TV
     960×539, paysage) réduit --u par la hauteur, un écran ÉTROIT
     (téléphone en portrait) le réduit par la largeur, sans qu'aucun des
     deux axes ne soit un cas particulier codé à part. Un écran à la
     fois large ET court (aucun format connu aujourd'hui, mais c'est
     précisément le "quatrième seuil" que le brief nomme) obtient
     automatiquement la bonne taille, sans qu'un troisième point de
     rupture soit ajouté pour lui.
     Bornes ancrées sur deux formats RÉELS mesurés (jamais choisies au
     jugé) : 8px sature à vmin≈1081 (1920×1080, où le calcul precedent
     saturait déjà par la largeur) ; 2.6px est un plancher de sûreté
     pour un format plus exigu qu'aucun appareil connu aujourd'hui,
     jamais atteint par la box (vmin 539 → --u≈3.99px, formule vivante,
     pas flooré) ni par l'iPhone 12 mini (vmin 375 → --u≈2.78px, vivant).
     Chaque propriété qui suit garde SON PROPRE plancher/plafond
     (clamp()) : ce ne sont pas des seuils d'écran (qui changeraient
     plusieurs propriétés à la fois, à une largeur choisie au jugé),
     mais des bornes de LISIBILITÉ par propriété, l'équivalent de la
     cible tactile ≥44px déjà en usage ailleurs dans ce produit — la
     différence de nature que A9 vise à éliminer est le SEUIL D'ÉCRAN,
     jamais la borne de lisibilité elle-même. */
  --u: clamp(2.6px, 0.74vmin, 8px);
}

html{ height:100%; margin:0; }

/* === Fond — source unique (S13) === */
body{
  position:relative; overflow:hidden;
  min-height:100vh;
}
/* Fond = masque vectoriel (vague-cockpit.svg, S34), teinté par --gold
   (jamais une couleur en dur ici). Remplace un masque bitmap qui
   pixellisait structurellement une fois étiré sur 2560-3840px — un SVG
   n'a pas cette limite. Goulot inchangé : --text-secondary, plus aucun
   usage de --text-muted sur le kiosk. */
body::before{
  content:'';
  position:fixed;
  inset:0;
  z-index:0;
  /* S38 lot 0 : réglable par profil depuis panel.html (Écran), plus une
     valeur en dur (§5.13) — index.html pose --bg-opacity depuis
     cfg.bgOpacity ; repli 0.55 si jamais posée (page hors kiosk, config
     absente). Borne haute calculée côté admin (config-store.js,
     BG_OPACITY_MAX) — jamais laissée libre. */
  opacity: var(--bg-opacity, 0.55);
  background-color: var(--gold);
  background-image: none;
  /* S34/S37 : le bitmap (vague-masque-v3.webp, 1672x941) étiré sur
     2560-3840px pixellisait structurellement — remplacé par un SVG
     vectoriel (fourni, testé net en direct sur la production). Motif
     porté par l'ALPHA des traits (fond transparent), pas la luminance —
     mode par défaut (match-source/alpha), pas de mask-mode: luminance
     ici (ça viderait quasiment le masque sur ce fichier).
     vague-masque-v3.webp gardé dans le dossier par arbitrage explicite
     (§5.8 exception), plus aucune règle CSS ne le référence. */
  -webkit-mask-image: url('../img/vague-cockpit.svg');
          mask-image: url('../img/vague-cockpit.svg');
  -webkit-mask-size: cover;
          mask-size: cover;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
}

/* Repli : appareil dont le navigateur ne supporte pas mask-image (à
   vérifier sur la box réelle, pas supposé) — image pré-teintée, couleur
   figée, compromis assumé (brief "Fond du kiosk"). */
@supports not ((mask-image: none) or (-webkit-mask-image: none)) {
  body::before{
    background-color: transparent;
    background-image: url('../img/vague-or-transparent.webp');
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
  }
}

/* A11 (S148 T5) — vague animée, ÉTEINTE PAR DÉFAUT (réglage par profil,
   bgAnimated). Aucun de ces éléments n'existe dans le DOM tant que le
   réglage n'est pas activé (index.html ne les crée pas) — zéro
   régression possible sur le kiosque actuel. Le principe : PLUSIEURS
   CALQUES de la même image (vague-cockpit.svg, jamais recalculée),
   chacun un élément fixe plein écran, translatés en CSS pur à des
   vitesses et opacités différentes — le compositeur du navigateur
   déplace des textures déjà en mémoire, sur le GPU, sans redessiner un
   seul pixel. Interdits explicites du brief, aucun ici : pas de filtre
   SVG animé, pas de canvas redessiné en continu, pas de shader.
   `body.bg-animated::before` s'efface (le calque 2 le remplace au même
   endroit visuel) — jamais superposé au fond personnalisé d'un profil
   (A8) : `.wave-layer` n'est créée que si aucune image de fond n'est
   réglée pour ce profil (voir index.html). */
body.bg-animated::before { opacity: 0; }
.wave-layer {
  content: '';
  position: fixed;
  inset: -10% -10% -10% -10%; /* déborde le viewport : la translation ne découvre jamais un bord vide */
  z-index: 0;
  pointer-events: none;
  background-color: var(--gold);
  background-image: none;
  -webkit-mask-image: url('../img/vague-cockpit.svg');
          mask-image: url('../img/vague-cockpit.svg');
  -webkit-mask-size: cover;
          mask-size: cover;
  -webkit-mask-position: center;
          mask-position: center;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  will-change: transform;
}
/* Trois calques, trois vitesses, trois opacités — jamais le même
   mouvement deux fois (le motif ne se répète pas à l'œil). Opacité
   totale répartie entre les calques pour rester proche de la lecture
   visuelle du fond statique à --bg-opacity équivalent. */
.wave-layer-1 { opacity: calc(var(--bg-opacity, 0.55) * 0.5); animation: wave-drift-1 68s ease-in-out infinite alternate; }
.wave-layer-2 { opacity: calc(var(--bg-opacity, 0.55) * 0.35); animation: wave-drift-2 91s ease-in-out infinite alternate; }
.wave-layer-3 { opacity: calc(var(--bg-opacity, 0.55) * 0.3); animation: wave-drift-3 124s ease-in-out infinite alternate; }
@keyframes wave-drift-1 { from { transform: translate(0, 0) scale(1.06); } to { transform: translate(-3%, 2%) scale(1.06); } }
@keyframes wave-drift-2 { from { transform: translate(0, 0) scale(1.1); } to { transform: translate(2.5%, -2%) scale(1.1); } }
@keyframes wave-drift-3 { from { transform: translate(0, 0) scale(1.14); } to { transform: translate(-2%, -2.5%) scale(1.14); } }
@media (prefers-reduced-motion: reduce) {
  .wave-layer { animation: none !important; }
}

/* A8 (S148 T2) — fond d'écran PAR PROFIL : remplace le masque vectoriel
   par la photo envoyée, jamais les deux superposés (une photo vue à
   travers la vague ne montrerait presque rien d'elle). --bg-opacity
   RESTE le seul réglage d'intensité (§5.13, "le fond se range au même
   endroit") — jamais un second curseur pour la même idée. index.html
   pose .has-bg-image ET --custom-bg-image ENSEMBLE (jamais l'un sans
   l'autre) dès que cfg.bgImageUrl existe ; en son absence, cette règle
   ne s'applique jamais et le masque ci-dessus reste actif tel quel
   (dégradation propre, §8.5 : un profil sans fond envoyé garde le fond
   actuel, jamais un écran noir ni une zone vide). */
body.has-bg-image::before{
  background-color: var(--bg);
  background-image: var(--custom-bg-image);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  -webkit-mask-image: none;
          mask-image: none;
}

/* Calque d'assombrissement séparé */
body::after{
  content:'';
  position:fixed;
  inset:0;
  z-index:0;
  background: radial-gradient(
    ellipse at 20% 50%,
    color-mix(in srgb, var(--gold) 6%, transparent) 0%,
    transparent 60%
  ),
  linear-gradient(
    to right,
    color-mix(in srgb, var(--bg) 55%, transparent) 0%,
    color-mix(in srgb, var(--bg) 20%, transparent) 60%,
    color-mix(in srgb, var(--bg) 45%, transparent) 100%
  );
  pointer-events: none;
}

/* tout le contenu passe au-dessus du décor */
.wrap, .agenda, .right, .grid { position:relative; z-index:1; }
.wrap{
  display:grid; grid-template-columns:minmax(0,var(--agenda-w)) minmax(0,1fr);
  /* grid-template-rows dimensionne la piste, pas la boîte de .wrap : sans
     hauteur explicite sur l'élément lui-même, le padding vertical s'ajoute
     PAR-DESSUS 100vh (103vh mesurés). Déduire le padding réel (1.5vh haut
     + 1.5vh bas), même principe que --agenda-h en calc(100vh - 32px). */
  grid-template-rows:calc(100vh - 3vh); align-items:start; gap:1.1vw;
  padding:1.5vh 1.5vw; box-sizing:border-box; overflow:hidden;
  /* Fondu court sur les 4 bords (brief "Finitions du kiosk") : 1% de la
     boîte de .wrap sur chaque axe, strictement sous le padding (1,5vh/
     1,5vw) qui protège le contenu réel — ne peut jamais toucher le texte
     (qui commence à ~14px du bord au format cuisine, la référence la
     plus serrée). Un masque CSS n'utilise que le canal alpha des arrêts
     de dégradé, jamais leur couleur : var(--gold) ici est interchangeable
     avec n'importe quel autre token opaque, aucune couleur littérale
     introduite. Statique (aucun coût de rendu continu). */
  -webkit-mask-image:
    linear-gradient(to right, transparent, var(--gold) 1%, var(--gold) 99%, transparent),
    linear-gradient(to bottom, transparent, var(--gold) 1%, var(--gold) 99%, transparent);
  -webkit-mask-composite: source-in;
  mask-image:
    linear-gradient(to right, transparent, var(--gold) 1%, var(--gold) 99%, transparent),
    linear-gradient(to bottom, transparent, var(--gold) 1%, var(--gold) 99%, transparent);
  mask-composite: intersect;
}

/* LOT "écran tablette" (S125 T2) — un TYPE D'AFFICHAGE de plus, jamais
   une page à part (voir web/index.html pour le détail de réutilisation
   des composants). Trois colonnes en paysage, deux en portrait (brief
   explicite) — orientation réelle de l'appareil, jamais une largeur en
   pixels devinée (un écran mural peut avoir n'importe quelle diagonale). */
.tablet-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.5vh 1.5vw;
  padding: 1.5vh 1.5vw;
  box-sizing: border-box;
  width: 100vw; height: 100vh;
  position: relative; z-index: 1;
}
@media (orientation: portrait) {
  .tablet-grid { grid-template-columns: repeat(2, 1fr); }
}
.tablet-card {
  background: var(--bg-lift);
  border: 1px solid var(--hair);
  border-radius: var(--radius-md);
  padding: 24px;
  overflow: auto;
  box-sizing: border-box;
  min-width: 0; min-height: 0;
}
.tablet-card-title {
  font-size: 1.1rem;
  font-weight: 600;
  margin-bottom: 12px;
  opacity: .85;
}
.tablet-card-pending {
  font-size: .85rem;
  opacity: .6;
  line-height: 1.5;
}
/* S130 T3 — régime lecture seule, TOUJOURS visible sur la grille
   tablette (§5.12quater, "rien de caché sans signal") : un jeton
   d'écran ne porte jamais de droit d'écriture, ce mur le dit plutôt
   que de le laisser deviner. Coin, discret mais permanent — jamais un
   message qui disparaît, jamais une couleur qui alarme (ce n'est pas
   une erreur). */
.tablet-readonly-badge {
  position: absolute;
  top: 1vh; right: 1.5vw;
  z-index: 2;
  font-size: .75rem;
  padding: 4px 10px;
  border-radius: var(--radius-pill);
  background: var(--bg-lift);
  border: 1px solid var(--hair);
  color: var(--text-muted);
}
/* Cartes Agenda/Courses (S130 T3) — rendu minimal en lecture seule,
   voir le commentaire de loadTabletAgendaCard()/loadTabletCoursesCard()
   (web/index.html) : jamais une reprise du composant téléphone. */
.tablet-list-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 0;
  border-bottom: 1px solid var(--hair);
  font-size: .9rem;
}
.tablet-list-row:last-child { border-bottom: none; }
.tablet-list-row-title { flex: 1; min-width: 0; }
.tablet-list-row-meta { color: var(--text-muted); white-space: nowrap; flex-shrink: 0; }
/* La carte citation+météo REÇOIT .ambient-panel tel quel (déplacé par
   applyConfig(), jamais copié). `.ambient-panel` porte des contraintes
   pensées pour son emplacement D'ORIGINE (flex item d'un parent flex
   de hauteur fixe, colonne droite du kiosque) : `flex:1` devient un
   no-op ici (.tablet-card n'est pas un conteneur flex, sans effet
   négatif) mais `max-height:100%`/`overflow:hidden` couperaient son
   contenu dans une carte plus petite — neutralisés ICI seulement,
   jamais dans le fichier qui définit .ambient-panel lui-même (§2.8,
   portée additive, un seul emplacement continue d'en dépendre tel quel). */
#tabletCardCitationMeteo .ambient-panel {
  max-height: none;
  overflow: visible;
}

/* S43 — appairage des écrans. .wrap reste en layout (visibility, pas
   display:none) pendant que l'appairage est résolu — pas de flash au
   moment de la révélation, et les mesures de mise en page (miniature
   panel.html, diagnostics) restent valables même écran non affiché. */
.wrap.pairing-pending {
  visibility: hidden;
}

.pairing-screen {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 28px;
  background: var(--bg);
  text-align: center;
  padding: 40px;
  box-sizing: border-box;
}
.pairing-screen.hidden {
  display: none;
}
.pairing-label {
  font-size: clamp(14px, 1.6vw, 22px);
  letter-spacing: 0.3em;
  text-transform: uppercase;
  color: var(--text-secondary);
}
/* Lisible à trois mètres (brief explicite) : très grand, espacé pour
   distinguer chaque caractère, alphabet déjà sans ambiguïté visuelle
   (posé côté serveur — jamais O/0/I/1/L). */
.pairing-code {
  font-size: clamp(64px, 12vw, 180px);
  font-weight: 100;
  letter-spacing: 0.25em;
  color: var(--gold);
  line-height: 1;
}
.pairing-hint {
  font-size: clamp(13px, 1.3vw, 20px);
  color: var(--text-secondary);
  line-height: 1.6;
}

/* S71 — stockage qui ne persiste pas : lisible à trois mètres (même
   exigence que .pairing-code), mais c'est une PHRASE, pas 6 caractères
   — taille intermédiaire entre .pairing-code et .pairing-hint. Jamais
   la couleur --gold (réservée à un code d'action) : un rouge d'alerte,
   déjà utilisé ailleurs dans ce produit pour un état bloquant. */
.pairing-warning {
  font-size: clamp(28px, 4vw, 56px);
  font-weight: 300;
  line-height: 1.35;
  color: var(--red);
  max-width: 900px;
}

/* S71 — URL complète à recopier après un appairage réussi : assez grand
   pour se lire à distance normale (pas trois mètres, l'opérateur est
   devant l'écran à ce moment précis pour la recopier), cassure de mot
   autorisée (une URL longue ne doit jamais déborder). */
.pairing-url {
  font-size: clamp(18px, 2.4vw, 34px);
  color: var(--gold);
  word-break: break-all;
  max-width: 900px;
  line-height: 1.5;
}

/* CORRECTIF racine — choix explicite (connexion / appairage) sur "/"
   quand ni jeton ni session ne sont présents. Cible tactile ≥44px
   (standard S50) : cet écran peut être atteint depuis un téléphone. */
.choice-actions {
  display: flex;
  flex-direction: column;
  gap: 20px;
  width: min(420px, 90vw);
}
.choice-btn {
  display: block;
  box-sizing: border-box;
  min-height: 44px;
  padding: 22px 32px;
  font-size: clamp(16px, 1.8vw, 24px);
  letter-spacing: 0.03em;
  text-align: center;
  text-decoration: none;
  border-radius: var(--radius-md);
}
.choice-btn-primary {
  background: var(--gold);
  color: var(--bg);
  font-weight: 600;
}
.choice-btn-secondary {
  background: transparent;
  border: 1px solid var(--text-secondary);
  color: var(--text-secondary);
}

/* ===== AGENDA (S24 — ambient, zéro bordure verticale) ===== */

/* Navigation période */
.cal-nav {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
  padding: 0 4px;
  flex-shrink: 0;
}

.nav-btn {
  background: transparent;
  border: none;
  color: var(--text-faint);
  font-size: 13px;
  cursor: pointer;
  padding: 2px 8px;
  transition: color 0.2s;
}

.nav-btn:hover {
  color: var(--gold);
}

/* Accès réglages (S29) — discrète par défaut (or pâle, pas le gris sourd
   des flèches), sans bordure ni fond : un écran qu'on regarde, pas une
   application qu'on pilote. Même registre optique que .nav-btn (padding,
   taille), simplement un <a> plutôt qu'un <button>. */
.nav-gear {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  color: color-mix(in srgb, var(--gold) 45%, transparent);
  margin-left: auto;
}
.nav-gear:hover, .nav-gear:focus-visible {
  color: var(--gold);
}

.month-title {
  font-size: clamp(13px, calc(var(--u) * 2), 16px);
  font-weight: 300;
  color: var(--gold);
  letter-spacing: 0.04em;
}

/* Grille */
.grid {
  display: grid;
  grid-template-columns: 22px repeat(7, 1fr);
  /* 8 pistes reelles : en-tete + (semaine 1fr, separateur auto) x3, semaine 4
     — pas "auto repeat(4,1fr)" (5 pistes) : .week-separator (S24) est un
     item de grille a part entiere entre chaque semaine, sans quoi les 2
     dernieres semaines retombent en lignes implicites ecrasees. */
  grid-template-rows: auto 1fr auto 1fr auto 1fr auto 1fr;
  gap: 0;
  overflow: hidden;
  flex: 1;
  min-height: 0;
}

/* En-têtes colonnes */
.cell.header {
  font-size: clamp(9px, calc(var(--u) * 1.375), 11px);
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: 1px;
  text-align: left;
  padding: 0 0 10px 6px;
  font-weight: 500;
  background: transparent;
  border: none;
}

.cell.header.weekno {
  color: transparent;
  padding: 0;
}

/* Numéros de semaine */
.cell.weekno {
  font-size: clamp(8px, calc(var(--u) * 1.375), 11px);
  color: var(--text-secondary);
  display: flex;
  align-items: flex-start;
  justify-content: flex-end;
  padding-top: 7px;
  padding-right: 5px;
  border: none;
  background: transparent;
  font-weight: 300;
}

/* Séparateur entre semaines — fade or */
.week-separator {
  grid-column: 1 / -1;
  height: 2px;
  background: linear-gradient(
    to right,
    transparent,
    color-mix(in srgb, var(--gold) 45%, transparent) 15%,
    color-mix(in srgb, var(--gold) 45%, transparent) 85%,
    transparent
  );
}

.day{
  display:flex; flex-direction:column; gap:.22vh; line-height:1.18;
  font-size:clamp(11.5px,calc(var(--u) * 2.125),17px); font-weight:300;
  color: var(--text-primary);
  overflow:hidden;
}

/* Cellules jours */
.cell.day {
  min-height: 0;
  padding: 5px 6px 4px;
  position: relative;
  border: none;
  background: transparent;
  overflow: hidden;
}

.cell.day.other-month .dnum {
  opacity: 0.2;
}

.cell.day.today {
  background: color-mix(in srgb, var(--gold) 3%, transparent);
}

.cell.day.today::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(
    to right,
    transparent,
    color-mix(in srgb, var(--gold) 60%, transparent),
    transparent
  );
}

/* LOT "citation, et ajout d'événement depuis le calendrier" §2 — le
   kiosque devient interactif, navigable au D-pad (télécommande) :
   focus TOUJOURS visible, jamais un anneau discret (CLAUDE.md, "ÉCRAN
   MURAL"). Curseur pointeur aussi : le kiosque reste consultable à la
   souris/au doigt (préréglage panel.html "Aperçu" en iframe, tablette). */
.cell.day[data-date] { cursor: pointer; }
.cell.day.qam-kiosk-focus {
  outline: 3px solid var(--gold);
  outline-offset: -3px;
  border-radius: var(--radius-sm);
}

/* Numéro du jour */
.dnum {
  font-size: calc(clamp(11px, calc(var(--u) * 1.75), 14px) * var(--agenda-grid-scale, 1));
  color: var(--text-secondary);
  display: block;
  margin-bottom: 3px;
  line-height: 1;
  font-weight: 400;
}

.cell.today .dnum {
  color: var(--gold);
  font-weight: 600;
  font-size: calc(clamp(12px, calc(var(--u) * 2), 16px) * var(--agenda-grid-scale, 1));
}

/* Événements all-day */
.allday {
  font-size: calc(clamp(9px, calc(var(--u) * 1.5), 12px) * var(--agenda-grid-scale, 1));
  /* Déjà --text-primary avant cette ligne (hérité de body) — rendu
     explicite plutôt que de dépendre de la cascade (brief "Texte du
     calendrier et opacité du fond" : .evt-title ET .allday visés,
     mesuré que .allday y était déjà). */
  color: var(--text-primary);
  line-height: 1.25;
  margin-top: 1px;
  font-weight: 400;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* CORRECTIF (S99, "saturation d'une case", trouvé en mesurant) —
     .cell.day est display:flex column ; SANS flex-shrink:0, un jour
     dense (plus d'événements que la case n'a de place) faisait
     RÉTRÉCIR chaque .evt/.allday au lieu de déborder — le texte
     continuait de peindre à sa taille réelle par-dessus une boîte
     comprimée à quelques pixels, illisible et superposé, PENDANT que
     scrollHeight restait égal à clientHeight (rien à mesurer : le
     flex-shrink avait déjà tout fait "tenir" en écrasant le contenu).
     flex-shrink:0 : chaque événement garde sa taille naturelle, la case
     déborde RÉELLEMENT quand il y en a trop — c'est CE débordement que
     renderGridEvents() (web/index.html) mesure pour décider quoi garder
     et signaler le reste ("+N"), jamais un rétrécissement muet. */
  flex-shrink: 0;
}

/* Événements avec heure */
.evt {
  display: flex;
  flex-direction: column;
  margin-top: 2px;
  max-width: 100%;
  overflow: hidden;
  flex-shrink: 0; /* voir commentaire .allday ci-dessus — même correctif, même raison */
}

.evt-time {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: calc(clamp(8px, calc(var(--u) * 1.25), 10px) * var(--agenda-grid-scale, 1));
  color: var(--text-secondary);
  line-height: 1.3;
}

.dot {
  display: inline-block;
  width: 4px;
  height: 4px;
  border-radius: var(--radius-full);
  flex-shrink: 0;
}

.evt-title {
  font-size: calc(clamp(9px, calc(var(--u) * 1.5), 12px) * var(--agenda-grid-scale, 1));
  color: var(--text-primary);
  padding-left: 8px;
  line-height: 1.25;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.evt-more {
  font-size: calc(clamp(8px, calc(var(--u) * 1.25), 10px) * var(--agenda-grid-scale, 1));
  color: var(--text-secondary);
  margin-top: 2px;
}

/* S52 lot 6 — calendrier en liste, profil mobile uniquement. Une grille
   de 28 jours sur 375px de large est illisible quelle que soit la taille
   de police (arbitrage explicite, même principe que le bloc météo réduit
   par retrait plutôt que par rétrécissement). .evt-title/.allday/.evt-time
   réutilisés tels quels (même structure, même génération JS que la
   grille) — seule leur taille change ici, jamais un second balisage. */
.agenda-list {
  display: flex;
  flex-direction: column;
  gap: 20px;
  height: 100%;
  overflow-y: auto;
  padding-right: 4px;
  box-sizing: border-box;
}
.agenda-day {
  padding-bottom: 16px;
  border-bottom: 1px solid var(--hair);
}
.agenda-day:last-child {
  border-bottom: none;
}
.agenda-day-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  margin-bottom: 10px;
}
.agenda-day-weekday {
  font-size: 16px;
  color: var(--text-primary);
  text-transform: capitalize;
  font-weight: 400;
}
.agenda-day.today .agenda-day-weekday {
  color: var(--gold);
  font-weight: 600;
}
.agenda-day-date {
  font-size: 13px;
  color: var(--text-secondary);
}
.agenda-day-badges {
  display: flex;
  gap: 8px;
  margin-left: auto;
}
/* Corps lisible au doigt (S50, seuil iOS 16px) — la grille tronque à
   deux lignes par manque de place, la liste a la place, donc pas de
   -webkit-line-clamp ici. */
.agenda-list .evt {
  margin-top: 8px;
}
.agenda-list .evt-time {
  font-size: 13px;
}
.agenda-list .evt-title,
.agenda-list .allday {
  font-size: 16px;
  -webkit-line-clamp: initial;
  padding-left: 10px;
}
.agenda-list .allday {
  padding-left: 0;
  margin-top: 8px;
}

/* Supprimer .agenda border — le cadre faisait laid */
.agenda {
  overflow: hidden;
  border: none;
  border-radius: var(--radius-none);
  display: flex;
  flex-direction: column;
  height: var(--agenda-h);
  max-height: 96vh;
}

/* ===== COLONNE DROITE (S21 — ambient-panel unifié) ===== */
.right {
  display: flex;
  flex-direction: column;
  height: var(--agenda-h, 92vh);
  max-height: var(--agenda-h, 92vh);
  overflow: hidden;
  box-sizing: border-box;
}

.hero{
  position:relative; flex:0 0 auto; height:var(--carousel-h); overflow:hidden; max-width:100%;
  background: transparent !important;
  background-position:center; background-size:cover; background-repeat:no-repeat;
  border:none !important; box-shadow:none !important; outline:none !important;
}
.hero::before{ display:none; }

/* Carrousel sans image (S18 : /api/manifest renvoie une liste vide) —
   aucun emplacement réservé, quel que soit le format. Spécificité
   (body.no-carousel) volontairement au-dessus de :root pour l'emporter
   sur toutes les media queries qui fixent --carousel-h. */
body.no-carousel{ --carousel-h: 0px; }

/* Blocs commutables (S28, A8 2.4) — c'est le réglage admin qui décide,
   jamais une media query : ces classes s'appliquent identiquement à tous
   les formats. Posées par index.html depuis /api/config → blocks. */
body.no-citation .panel-quote{ display:none; }
body.no-previsions .weather-daily{ display:none; }
body.no-meteo-details .weather-details{ display:none; }

/* Slides */
.slide{
  position:absolute; inset:0; z-index:1; width:100%; height:100%;
  object-fit: var(--carousel-fit); object-position: var(--carousel-align);
  opacity:0; transition:opacity var(--carousel-fade) ease;
  background: transparent !important; border:none !important; box-shadow:none !important; outline:none !important;
}
.slide.show{ opacity:1; }

.ambient-panel {
  flex: 1;
  min-height: 0;
  max-height: 100%;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  /* Ni bordure ni rayon (brief "Finitions du kiosk") : c'était la seule
     carte du produit. Le panneau se sépare du calendrier par le vide
     (.wrap gap) — jamais par un contour fermé. */
  position: relative;
  box-sizing: border-box;
}

.ambient-panel::before {
  content: '';
  position: absolute;
  /* S48 — bord à bord, jamais en dehors (piège de mesure, brief S48) :
     un ::before qui déborde du panneau gonfle scrollWidth/scrollHeight
     sans rien changer à l'écran (déjà masqué par overflow:hidden sur
     .ambient-panel) — un faux positif permanent pour toute mesure
     future, pas un défaut visible. width/height plafonnés à 100% : sur
     le profil moyen (194px), le halo (220px) dépasserait encore la
     largeur même ancré à right:0. */
  top: 0;
  right: 0;
  width: min(220px, 100%);
  height: min(220px, 100%);
  border-radius: var(--radius-full);
  background: radial-gradient(circle, color-mix(in srgb, var(--gold) 6%, transparent) 0%, transparent 65%);
  pointer-events: none;
  z-index: 0;
}

/* LOT "correctif, les icônes vont dans la grille" (S99, constat
   d'Arnaud : "les icônes de collecte apparaissent sous la photo, dans
   le bandeau de gauche — leur place est dans la case du jour concerné,
   dans la grille du calendrier, comme sur mobile") — remplace
   .panel-badges (bandeau détaché de toute date, retiré) : la ligne
   d'icônes vit maintenant DANS chaque case (web/index.html::
   renderGridEvents, juste après le numéro du jour, avant les
   événements). flex-shrink:0 — même correctif que .evt/.allday
   ci-dessous, une case dense ne doit jamais écraser cette ligne au lieu
   de déborder proprement. */
.grid-day-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin-bottom: 2px;
  flex-shrink: 0;
}
/* LOT "icônes sur les écrans" (S99) — remplace .badge-pill (disque de
   couleur par type + losange doré pour les rappels) : MÊME catalogue
   type→icône que mobile (event-icons.js, une seule source), rendu
   MONOCHROME ici — variante de style, jamais une seconde
   implémentation. Aucune couleur par type sur le kiosque : la
   silhouette seule distingue une collecte d'une autre, ou d'un rappel
   (brief explicite, "précision au lot en cours" — "aucune couleur
   n'indique la nature d'un événement, ni sur mobile ni sur les
   écrans"). Icône nue (pas de fond en chip, contrairement à mobile) —
   pas de cible tactile à ménager sur un écran mural non interactif ici,
   la silhouette seule doit porter à distance ; --text-primary (déjà le
   contraste de base de tout le texte du produit sur --bg) plutôt qu'une
   nouvelle valeur. Taille vérifiée à distance sur /diag-tv.html (M10). */
.badge-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  color: var(--text-primary);
  flex-shrink: 0;
  position: relative;
}
/* Passage inhabituel (brief S41 : "c'est précisément le cas où
   l'habitude trompe") — même mécanisme que mobile (badge-icon.unusual,
   voir mobile.css), jamais une seconde version de ce signal. */
.badge-icon.unusual::after {
  content: "!";
  position: absolute;
  top: -4px; right: -4px;
  width: 13px; height: 13px;
  border-radius: var(--radius-full);
  background: var(--red);
  color: var(--white);
  font-size: 9px;
  font-weight: 700;
  line-height: 13px;
  text-align: center;
  box-shadow: 0 0 0 1.5px var(--bg);
}
/* Case de grille : plus étroite qu'une ligne de liste ou le bandeau
   retiré — 20px par défaut, jamais moins de 18px (event-icons.js
   documente le plancher de lisibilité mesuré : "en dessous, un trait à
   2 unités sur une grille de 24 devient illisible, y compris de près" —
   vaut aussi pour l'écran mural, un px CSS y correspond à une taille
   physique bien plus grande que sur un téléphone). LOT "taille des
   icônes, par écran" (S99) — --icon-grid-size (socle.css::.icon-size-*,
   posée par web/index.html::applyConfig depuis profiles.<slug>.
   iconSize, jamais en dur ici, §5.8) : un réglage PAR ÉCRAN, mais
   JAMAIS lié à --agenda-grid-scale — ce plancher est un absolu de
   lisibilité de l'icône, pas un confort de lecture de texte. */
.grid-day-badges .badge-icon {
  width: var(--icon-grid-size, 20px);
  height: var(--icon-grid-size, 20px);
}
/* Marqueur "!" proportionnel à l'icône (calc(), jamais une valeur fixe
   qui deviendrait disproportionnée aux crans extrêmes) — même ratio
   qu'avant ce lot (11px de marqueur pour 20px d'icône, 0.55). */
.grid-day-badges .badge-icon.unusual::after {
  width: calc(var(--icon-grid-size, 20px) * 0.55);
  height: calc(var(--icon-grid-size, 20px) * 0.55);
  font-size: calc(var(--icon-grid-size, 20px) * 0.4);
  line-height: calc(var(--icon-grid-size, 20px) * 0.55);
}

.panel-clock {
  /* Le padding vertical (haut/bas) suit --u, l'horizontal (24px) reste
     fixe — un panneau étroit n'a pas besoin de moins d'air latéral, il a
     besoin de moins d'air VERTICAL pour laisser la place au reste
     (météo, citation). Bornes ancrées sur la box réelle, même origine
     que .panel-time (S147, A9) : l'ancien seuil de 1024px forçait
     8px/6px sur cet écran, jamais choisi au jugé. */
  padding: clamp(8px, calc(var(--u) * 2), 28px) 24px clamp(6px, calc(var(--u) * 1.5), 20px);
  text-align: center;
  flex-shrink: 0;
  position: relative;
  z-index: 1;
}

.panel-date {
  font-size: clamp(10px, calc(var(--u) * 1.625), 13px);
  color: var(--text-secondary);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  margin-bottom: 6px;
}

.panel-time {
  /* Plancher abaissé de 52 à 48 (S147, A9) : c'est la valeur que
     l'ancien seuil de 1024px forçait sur la box réelle (960×539) —
     jamais choisie au jugé, reprise de la mesure qui a produit ce
     seuil. La formule --u atteint ce plancher elle-même sur la box
     (≈43,9px avant plancher), aucun media query n'est plus nécessaire
     pour l'y amener. */
  font-size: clamp(48px, calc(var(--u) * 11), 88px);
  font-weight: 100;
  letter-spacing: -4px;
  color: var(--gold);
  line-height: 1;
}

.panel-sep {
  height: 1px;
  background: linear-gradient(to right, transparent, color-mix(in srgb, var(--gold) 35%, transparent), transparent);
  margin: 0 20px;
  flex-shrink: 0;
}

/* LOT "police et taille de la citation" (S99) — padding élargi : brief
   explicite, "la saturation de l'espace nuit plus à la lecture que la
   petitesse". De l'air autour du bloc, jamais seulement autour du
   texte. */
.panel-quote {
  /* Même principe que .panel-clock ci-dessus : vertical fluide, horizontal
     fixe. Plancher vertical ancré sur l'ancien seuil de 1024px (6px). */
  padding: clamp(6px, calc(var(--u) * 1.5), 18px) 24px clamp(6px, calc(var(--u) * 1.5), 16px);
  flex-shrink: 0;
  border-bottom: 1px solid var(--hair);
}

.quote-text {
  font-family: var(--font-quote);
  font-size: var(--quote-font-size, clamp(13px, calc(var(--u) * 2.25), 18px));
  color: var(--text-primary);
  line-height: var(--quote-line-height, 1.6);
  opacity: 0.85;
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.quote-author {
  font-size: clamp(8px, calc(var(--u) * 1.25), 10px);
  color: var(--gold);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  margin-top: 4px;
  opacity: 0.7;
}

.panel-weather {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  /* PAS de justify-content:space-between (S30bis) : un bloc composé, pas
     étalé — écarts fixes entre groupes, l'espace qui reste s'accumule
     en bas plutôt que d'être réparti entre eux. */
  /* Plancher vertical ancré sur l'ancien seuil de 1024px (6px haut/bas),
     même origine que .panel-clock/.panel-quote (S147, A9). */
  padding: clamp(6px, calc(var(--u) * 1.5), 16px) 8px clamp(6px, calc(var(--u) * 1.5), 14px);
  position: relative;
  z-index: 1;
  box-sizing: border-box;
}

/* === MÉTÉO (S20 — v3 ; S35 : 3 colonnes — icône+température / ville-
   ciel-ressenti / 4 valeurs — colonnes 2 et 3 au même corps, alignées en
   lignes en regard, d'où align-items:flex-start plutôt que flex-end). === */
.weather-row {
  display: flex;
  /* S41 lot 7, point 2 : les colonnes n'ont plus toujours le même nombre
     de lignes (3e ligne de .weather-details conditionnelle) — centrées
     sur un axe commun plutôt qu'alignées en haut, sinon la colonne la
     plus courte décroche visuellement de l'autre selon le cas de données
     du jour. */
  align-items: center;
  justify-content: center;
  gap: 2px;
}

.weather-lead {
  display: flex;
  /* S41 lot 7, point 5 : icône centrée sur la hauteur totale du bloc
     (elle-même centrée par .weather-row ci-dessus), pas calée sur la
     ligne de base du chiffre. */
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}

.weather-icon svg {
  width: clamp(26px, calc(var(--u) * 6.5), 52px);
  height: clamp(26px, calc(var(--u) * 6.5), 52px);
  stroke: var(--gold);
  color: var(--gold);
  opacity: 0.9;
}

.weather-temp-block {
  display: flex;
  align-items: flex-start;
  line-height: 1;
}

.weather-temp {
  /* Plancher abaissé de 64 à 44 (S147, A9) — même origine que
     .panel-time ci-dessus : valeur mesurée sur la box réelle par
     l'ancien seuil de 1024px, jamais choisie au jugé. */
  font-size: clamp(44px, calc(var(--u) * 11), 96px);
  font-weight: 100;
  line-height: 1;
  letter-spacing: -6px;
  color: var(--gold);
}

.weather-temp-deg {
  font-size: clamp(20px, calc(var(--u) * 5), 40px);
  font-weight: 200;
  color: var(--gold);
  opacity: 0.7;
  margin-top: clamp(4px, calc(var(--u) * 1.25), 12px);
}

/* Colonnes 2 et 3 : même corps, même interligne (S35) — sinon les lignes
   ne se répondent plus d'une colonne à l'autre. Ne redéfinir ces deux
   variables qu'ensemble. S41 lot 7, point 6 : interligne ouvert (1.5→1.85)
   maintenant que la 3e colonne ne peut plus dépasser 3 lignes (au lieu de
   4) — l'espace libéré va à l'air entre les lignes, pas à agrandir le
   bloc (hauteur totale vérifiée stable, §voir JOURNAL). */
:root {
  --weather-col-size: clamp(6px, calc(var(--u) * 1.85), 17px);
  --weather-col-lh: 1.85;
}

.weather-info {
  display: flex;
  flex-direction: column;
  font-size: var(--weather-col-size);
  line-height: var(--weather-col-lh);
  min-width: 0;
  flex-shrink: 0;
}

/* Ville = en-tête de la colonne 2 (S38b) : corps propre, découplé de
   --weather-col-size — elle ne doit jamais lire comme une ligne parmi
   trois. S41 lot 7, point 3 : plus de font-weight ici — une seule
   graisse pour toute la colonne (héritée du corps, 300) ; la hiérarchie
   vient du corps de cette étiquette et de la couleur des trois lignes,
   jamais de trois épaisseurs empilées. */
.weather-location {
  font-size: clamp(11px, calc(var(--u) * 2.5), 20px);
  letter-spacing: 0.03em;
  line-height: 1.25;
  color: var(--gold);
  opacity: 1;
}

.weather-desc {
  color: var(--text-primary);
}

.weather-feels {
  color: var(--text-secondary);
}

/* Colonne 3 : une icône trait + une valeur par ligne (S38b — remplace la
   grille 2x2 de 665abf8, §5.8 : plus de coexistence des deux mises en
   page). Icône en em : elle suit --weather-col-size, ne peut jamais
   dépasser le chiffre qu'elle précède. align-items:baseline (pas
   center) pour un calage optique sur la ligne de base du texte, pas sur
   la boîte de l'icône. */
.weather-details {
  display: flex;
  flex-direction: column;
  font-size: var(--weather-col-size);
  line-height: var(--weather-col-lh);
  color: var(--text-secondary);
  white-space: nowrap;
  flex-shrink: 0;
}

.weather-details .wd-item {
  display: flex;
  align-items: baseline;
  gap: 0.3em;
}

/* S41 lot 7 — 3e ligne conditionnelle (précipitation/UV/rien) : rangée
   par display:none, jamais un espace vide réservé. */
.weather-details .wd-item.hidden {
  display: none;
}

.wd-icon {
  width: 0.85em;
  height: 0.85em;
  flex-shrink: 0;
  opacity: 0.85;
  transform: translateY(0.1em);
}

.weather-sep-plain {
  height: 1px;
  background: var(--hair);
  /* Plancher ancré sur l'ancien seuil de 1024px (4px), S147/A9. */
  margin: clamp(4px, calc(var(--u) * 1), 16px) 0 clamp(4px, calc(var(--u) * 1), 14px);
}

.weather-sun {
  display: flex;
  justify-content: center;
  gap: 36px;
  /* Plancher ancré sur l'ancien seuil de 1024px (10px), S147/A9. */
  margin-top: clamp(10px, calc(var(--u) * 2.5), 18px);
}

.sun-item {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.sun-label {
  font-size: clamp(7px, calc(var(--u) * 1.125), 9px);
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.15em;
}

.sun-item span:last-child {
  font-size: clamp(14px, calc(var(--u) * 2.25), 18px);
  color: var(--gold);
  font-weight: 300;
  letter-spacing: -0.5px;
}

.weather-daily {
  display: flex;
  justify-content: space-between;
  /* Plancher ancré sur l'ancien seuil de 1024px (1px, 5 colonnes dans
     194,32px réels), S147/A9. */
  gap: clamp(1px, calc(var(--u) * 0.25), 3px);
  margin-top: 0;
  flex-shrink: 0;
}

.weather-day {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(3px, calc(var(--u) * 0.75), 5px);
  padding: clamp(5px, calc(var(--u) * 1.25), 10px) 2px;
  background: transparent;
  border-radius: var(--radius-none);
}

.weather-day .wd-label {
  /* Plancher abaissé 8→7 (S147, A9) : valeur mesurée sur la box réelle
     par l'ancien seuil de 1024px (5 colonnes devaient tenir dans
     194,32px de large, voir weather-daily plus haut). */
  font-size: clamp(7px, calc(var(--u) * 1.25), 10px);
  color: var(--text-secondary);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  font-weight: 500;
}

.weather-day .wd-icon {
  display: flex;
  align-items: center;
  justify-content: center;
}

.weather-day .wd-icon svg {
  /* Plancher abaissé 16→13, même origine que .wd-label ci-dessus. */
  width: clamp(13px, calc(var(--u) * 2.5), 20px);
  height: clamp(13px, calc(var(--u) * 2.5), 20px);
  stroke: var(--text-secondary);
  color: var(--text-secondary);
  fill: none;
}

.weather-day .wd-temps {
  display: flex;
  gap: 3px;
  align-items: baseline;
}

.weather-day .wd-max {
  /* Planchers de ce groupe (wd-max/min/precip) abaissés 12→10, 10→8,
     8→7 — même origine que .wd-label ci-dessus. */
  font-size: clamp(10px, calc(var(--u) * 1.875), 15px);
  color: var(--text-primary);
  font-weight: 400;
}

.weather-day .wd-min {
  font-size: clamp(8px, calc(var(--u) * 1.5), 12px);
  color: var(--text-secondary);
}

.weather-day .wd-precip {
  font-size: clamp(7px, calc(var(--u) * 1.25), 10px);
  color: var(--text-secondary);
  opacity: 0.8;
}

/* Ultra-larges */
@media (min-aspect-ratio: 21/9){ body::before{ background-size: 140% auto; } }

/* Carrousel — anti-bordures */
.hero, .carousel, .slide, .slider, .carousel-inner,
.hero img, .hero .slide, #hero, #hero img {
  border: none !important; box-shadow: none !important; background: transparent !important; outline: none !important;
}

/* KILL CADRE (carrousel uniquement — S21 : ne plus s'appliquer à .ambient-panel,
   dont la bordure est désormais voulue par le design) */
.hero, .hero *, .hero::before, .hero::after, .hero *::before, .hero *::after {
  border: 0 !important; outline: 0 !important; box-shadow: none !important;
}

/* S147 (A9) — les deux media queries qui vivaient ici (max-height:700px
   paysage, "format cuisine" max-width:1024px paysage) sont RETIRÉES,
   remplacées par --u ci-dessus (voir :root). Vérifié avant retrait,
   propriété par propriété (§E, "mesurer avant de corriger") :
   --agenda-h et le corps de .allday/.evt de la première étaient déjà
   des doublons exacts de leur valeur par défaut (aucun effet réel) ;
   --carousel-h de la même règle est de toute façon recalculé par
   web/index.html::sizeHeroToPanel() à l'exécution, la valeur CSS n'étant
   qu'un repli avant ce calcul (commentaire à sizeHeroToPanel()) ; les
   dix propriétés de la seconde (panel-time, weather-temp,
   panel-weather, weather-daily/day, panel-clock/quote, weather-sep-plain,
   weather-sun) sont désormais des planchers de clamp() portés directement
   sur chaque propriété, atteints par la formule --u sans qu'aucun seuil
   de largeur ne soit plus nécessaire pour les y amener — voir chaque
   propriété plus haut, chacune commentée à l'endroit où son plancher a
   changé. Preuve : docs/AUDIT-SOCLE-ECHELLE-S147.md (page de comparaison
   isolée + sabotage réel). */

@media (max-width: 1024px) and (orientation: portrait) {
  /* Ce seuil-ci RESTE : ce n'est pas une proportion qui se resserre en
     continu, c'est un CHANGEMENT DE MISE EN PAGE (deux colonnes → une
     seule, le panneau ambiant disparaît faute de place utilisable) —
     de même nature qu'un point de bascule tablette/téléphone déjà
     tranché ailleurs dans ce produit, jamais un "seuil de taille" au
     sens visé par A9. */
  :root {
    --agenda-w: 100%;
    --carousel-h: 0px;
  }
  .right {
    display: none;
  }
  /* S43 — à 390px, le plancher de clamp() (64px) + le grand espacement
     de lettres débordait du viewport (mesuré : "W" et "3" hors cadre).
     Plancher désormais porté par --u (S147) : mult 22.5 pour rester à
     180px de plafond identique à avant ; à 375px de large (iPhone 12
     mini, vmin 375 ici puisque la largeur est l'axe le plus petit en
     portrait), --u≈2,78px → 62,5px, sous l'ancien plancher de 64px sans
     avoir besoin d'un second seuil pour l'y amener. */
  .pairing-code {
    font-size: clamp(40px, calc(var(--u) * 22.5), 180px);
    letter-spacing: calc(0.12em + var(--u) * 0.0165);
  }
}
