
:root {
  --background: 0 0% 100%;
  --foreground: 240 10% 3.9%;
  --card: 0 0% 100%;
  --card-foreground: 240 10% 3.9%;
  --popover: 0 0% 100%;
  --popover-foreground: 240 10% 3.9%;
  --primary: 240 5.9% 10%;
  --primary-foreground: 0 0% 98%;
  --secondary: 240 4.8% 95.9%;
  --secondary-foreground: 240 5.9% 10%;
  --muted: 240 4.8% 95.9%;
  /* shadcn has 46.1%: that is 4.39:1 on --muted, just under AA. */
  --muted-foreground: 240 3.8% 43%;
  --accent: 240 4.8% 95.9%;
  --accent-foreground: 240 5.9% 10%;
  /* shadcn has 0 84.2% 60.2% (red-500): 3.76:1 against white, both as text
     on a page and as the button behind white text. red-600 gives 4.83:1. */
  --destructive: 0 72.2% 50.6%;
  --destructive-foreground: 0 0% 98%;
  --border: 240 5.9% 90%;
  --input: 240 5.9% 90%;
  --ring: 240 10% 3.9%;
  --radius: 0.5rem;
  --chart-1: 12 76% 61%;
  --chart-2: 173 58% 39%;
  --chart-3: 197 37% 24%;
  --chart-4: 43 74% 66%;
  --chart-5: 27 87% 67%;
  --sidebar: 0 0% 98%;
  --sidebar-foreground: 240 5.3% 26.1%;
  --sidebar-border: 220 13% 91%;
  --sidebar-accent: 240 4.8% 95.9%;
}
.dark {
  --background: 240 10% 3.9%;
  --foreground: 0 0% 98%;
  --card: 240 10% 3.9%;
  --card-foreground: 0 0% 98%;
  --popover: 240 10% 3.9%;
  --popover-foreground: 0 0% 98%;
  --primary: 0 0% 98%;
  --primary-foreground: 240 5.9% 10%;
  --secondary: 240 3.7% 15.9%;
  --secondary-foreground: 0 0% 98%;
  --muted: 240 3.7% 15.9%;
  --muted-foreground: 240 5% 64.9%;
  --accent: 240 3.7% 15.9%;
  --accent-foreground: 0 0% 98%;
  /* Same red as light for surfaces (white text on it: 4.83:1). shadcn's
     0 62.8% 30.6% is unreadable as text on a dark page; .text-destructive is
     lifted below for that use. */
  --destructive: 0 72.2% 50.6%;
  --destructive-foreground: 0 0% 98%;
  --border: 240 3.7% 15.9%;
  --input: 240 3.7% 15.9%;
  --ring: 240 4.9% 83.9%;
  --chart-1: 220 70% 50%;
  --chart-2: 160 60% 45%;
  --chart-3: 30 80% 55%;
  --chart-4: 280 65% 60%;
  --chart-5: 340 75% 55%;
  --sidebar: 240 5.9% 10%;
  --sidebar-foreground: 240 4.8% 95.9%;
  --sidebar-border: 240 3.7% 15.9%;
  --sidebar-accent: 240 3.7% 15.9%;
}

/* ---- base ---- */
* { border-color: hsl(var(--border)); }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0;
  background-color: hsl(var(--background));
  color: hsl(var(--foreground));
  font-family: var(--font-sans);
  font-feature-settings: "rlig" 1, "calt" 1;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
/* Geist + Geist Mono — la police de ui.shadcn.com (licence OFL 1.1).
   Auto-hebergee depuis /fonts : pas d'appel a fonts.gstatic.com au rendu, donc
   pas de FOUT lie a un tiers ni de dependance reseau externe.
   Ce sont des fichiers VARIABLES : un seul par famille couvre 100..900, d'ou
   font-weight: 100 900 — declarer un poids fixe figerait la graisse.
   Sous-ensembles latin + latin-ext uniquement (80 Ko en tout) ; le cyrillique
   et le grec servis par Google sont inutiles ici. */
@font-face {
  font-family: "Geist";
  src: url("/fonts/geist.woff2") format("woff2");
  font-weight: 100 900; font-style: normal; font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Geist";
  src: url("/fonts/geist-ext.woff2") format("woff2");
  font-weight: 100 900; font-style: normal; font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Geist Mono";
  src: url("/fonts/geist-mono.woff2") format("woff2");
  font-weight: 100 900; font-style: normal; font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Geist Mono";
  src: url("/fonts/geist-mono-ext.woff2") format("woff2");
  font-weight: 100 900; font-style: normal; font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Inter — proposee par le constructeur de theme (/create), pas utilisee par
   defaut. Variable comme Geist : un fichier par sous-ensemble couvre 100..900.
   Licence OFL 1.1, auto-hebergee pour la meme raison que les autres. */
@font-face {
  font-family: "Inter";
  src: url("/fonts/inter.woff2") format("woff2");
  font-weight: 100 900; font-style: normal; font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  src: url("/fonts/inter-ext.woff2") format("woff2");
  font-weight: 100 900; font-style: normal; font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* Mononoki — la police des blocs de code (licence OFL 1.1, via Fontsource).
   Absente de Google Fonts, donc les deux graisses sont servies depuis /fonts
   comme Geist. Sous-ensemble latin uniquement (65 Ko) : il couvre U+0000-00FF,
   donc tous les accents francais des commentaires.
   Contrairement a Geist ce n'est PAS une police variable — il faut une
   declaration par graisse, sans quoi le navigateur graisserait 400 par
   synthese et la chasse ne serait plus constante. */
@font-face {
  font-family: "Mononoki";
  src: url("/fonts/mononoki-regular.woff2") format("woff2");
  font-weight: 400; font-style: normal; font-display: swap;
}
@font-face {
  font-family: "Mononoki";
  src: url("/fonts/mononoki-bold.woff2") format("woff2");
  font-weight: 700; font-style: normal; font-display: swap;
}
:root {
  --font-sans: "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono: "Mononoki", "Geist Mono", ui-monospace, "SF Mono", "Menlo", "Monaco", "Cascadia Code", monospace;
}
/* Les utilitaires .font-sans / .font-mono generes par Tailwind portent leur
   PROPRE pile (Menlo, Monaco…), pas nos variables : tout element qui les porte
   echapperait a Geist. La feuille generee est injectee APRES celle-ci, donc a
   specificite egale c'est elle qui gagne — d'ou la classe doublee, qui passe a
   (0,2,0) sans recourir a !important.
   La correction est LOCALE a ui.bext.dev : la pile par defaut vit dans
   sites/shared/framework/src/tailwind-config.ts, partage par tous les sites
   bext, et la changer la imposerait Geist a Inklura et a l'onboarding. */
.font-sans.font-sans { font-family: var(--font-sans); }
.font-mono.font-mono { font-family: var(--font-mono); }
::selection { background: hsl(var(--foreground) / 0.12); }
a { color: inherit; text-decoration: none; }
svg { display: inline-block; vertical-align: middle; }

/* ---- semantic color utilities (token-aware, theme-agnostic) ---- */
.bg-background { background-color: hsl(var(--background)); }
.bg-foreground { background-color: hsl(var(--foreground)); }
.bg-card { background-color: hsl(var(--card)); }
.bg-popover { background-color: hsl(var(--popover)); }
.bg-primary { background-color: hsl(var(--primary)); }
.bg-secondary { background-color: hsl(var(--secondary)); }
.bg-muted { background-color: hsl(var(--muted)); }
.bg-muted\/50 { background-color: hsl(var(--muted) / 0.5); }
.bg-muted\/40 { background-color: hsl(var(--muted) / 0.4); }
.bg-accent { background-color: hsl(var(--accent)); }
.bg-accent\/50 { background-color: hsl(var(--accent) / 0.5); }
.bg-destructive { background-color: hsl(var(--destructive)); }
.bg-primary\/10 { background-color: hsl(var(--primary) / 0.1); }
.bg-primary\/20 { background-color: hsl(var(--primary) / 0.2); }
.bg-sidebar { background-color: hsl(var(--sidebar)); }

/* ---- variantes d'opacite manquantes -------------------------------------
   Ces utilitaires ne sont PAS produits par Tailwind : ils sont ecrits a la
   main, un par un, dans ce fichier. Une classe absente de cette liste ne
   produit donc AUCUNE regle — silencieusement. C'est ce qui laissait
   l'en-tete collant sans fond du tout (bg-background/95 inexistante) : il
   ne devait sa lisibilite qu'au backdrop-blur, et le contenu defilait a nu
   dessous.
   Releve par un audit comparant les classes presentes dans le DOM de chacune
   des 63 pages de composants aux selecteurs reellement generes pour elle.
   En ajouter une ici quand on en utilise une nouvelle dans une vue. */
.bg-background\/95 { background-color: hsl(var(--background) / 0.95); }
.bg-background\/60 { background-color: hsl(var(--background) / 0.6); }
.border-border\/40 { border-color: hsl(var(--border) / 0.4); }
.text-foreground\/60 { color: hsl(var(--foreground) / 0.6); }
.text-foreground\/70 { color: hsl(var(--foreground) / 0.7); }
.hover\:text-foreground\/80:hover { color: hsl(var(--foreground) / 0.8); }
.hover\:bg-accent\/50:hover { background-color: hsl(var(--accent) / 0.5); }
/* Le fond translucide n'a de sens que si le flou est reellement supporte ;
   sinon on garde le fond plein pose juste au-dessus. */
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .supports-\[backdrop-filter\]\:bg-background\/60 { background-color: hsl(var(--background) / 0.6); }
}
/* Le placeholder tombait sur le defaut du navigateur (texte a 50%) au lieu du
   jeton --muted-foreground : visible sur tous les champs du site. */
.placeholder\:text-muted-foreground::placeholder { color: hsl(var(--muted-foreground)); }

.text-foreground { color: hsl(var(--foreground)); }
.text-background { color: hsl(var(--background)); }
.text-card-foreground { color: hsl(var(--card-foreground)); }
.text-popover-foreground { color: hsl(var(--popover-foreground)); }
.text-primary { color: hsl(var(--primary)); }
.text-primary-foreground { color: hsl(var(--primary-foreground)); }
.text-secondary-foreground { color: hsl(var(--secondary-foreground)); }
.text-muted-foreground { color: hsl(var(--muted-foreground)); }
.text-accent-foreground { color: hsl(var(--accent-foreground)); }
.text-destructive { color: hsl(var(--destructive)); }
.dark .text-destructive { color: hsl(0 91% 71%); }
/* Red text on its own red tint (the destructive bubble) needs a darker red in
   light mode, and the form label's error state needs the lifted red in dark. */
.text-destructive.bg-destructive\/10 { color: hsl(0 74% 42%); }
.dark .text-destructive.bg-destructive\/10 { color: hsl(0 91% 71%); }
.dark [data-slot="form-label"][data-error="true"] { color: hsl(0 91% 71%); }
.text-destructive-foreground { color: hsl(var(--destructive-foreground)); }

.border-border { border-color: hsl(var(--border)); }
.border-input { border-color: hsl(var(--input)); }
.border-primary { border-color: hsl(var(--primary)); }
.border-primary\/50 { border-color: hsl(var(--primary) / 0.5); }
.border-destructive { border-color: hsl(var(--destructive)); }
.border-destructive\/50 { border-color: hsl(var(--destructive) / 0.5); }
.border-sidebar-border { border-color: hsl(var(--sidebar-border)); }
.ring-ring { --tw-ring-color: hsl(var(--ring)); }
.ring-offset-background { --tw-ring-offset-color: hsl(var(--background)); }

.fill-foreground { fill: hsl(var(--foreground)); }
.fill-primary { fill: hsl(var(--primary)); }

/* hover / focus variants for token utilities (route_css can't synth these) */
.hover\:bg-primary\/90:hover { background-color: hsl(var(--primary) / 0.9); }
.hover\:bg-secondary\/80:hover { background-color: hsl(var(--secondary) / 0.8); }
.hover\:bg-destructive\/90:hover { background-color: hsl(var(--destructive) / 0.9); }
.hover\:bg-accent:hover { background-color: hsl(var(--accent)); }
.hover\:bg-muted:hover { background-color: hsl(var(--muted)); }
.hover\:bg-muted\/50:hover { background-color: hsl(var(--muted) / 0.5); }
.hover\:text-accent-foreground:hover { color: hsl(var(--accent-foreground)); }
.hover\:text-foreground:hover { color: hsl(var(--foreground)); }
.hover\:text-primary:hover { color: hsl(var(--primary)); }
.hover\:border-foreground\/20:hover { border-color: hsl(var(--foreground) / 0.2); }
.hover\:underline:hover { text-decoration: underline; }
.hover\:opacity-100:hover { opacity: 1; }
.focus\:bg-accent:focus { background-color: hsl(var(--accent)); }
.focus\:text-accent-foreground:focus { color: hsl(var(--accent-foreground)); }
.focus-visible\:outline-none:focus-visible { outline: 2px solid transparent; outline-offset: 2px; }
.focus-visible\:ring-2:focus-visible { box-shadow: 0 0 0 2px hsl(var(--ring) / 0.35); }
.focus-visible\:ring-ring:focus-visible { box-shadow: 0 0 0 2px hsl(var(--ring) / 0.45); }

/* group + data-state helpers used by interactive components */
.group:hover .group-hover\:opacity-100 { opacity: 1; }
[data-state="open"] .data-\[state\=open\]\:rotate-180 { transform: rotate(180deg); }
[data-state="checked"] { }
[data-disabled] { pointer-events: none; opacity: 0.5; }

/* ---- animations (accordion / overlay / popover) ---- */
@keyframes accordion-down { from { height: 0; } to { height: var(--accordion-content-height); } }
@keyframes accordion-up { from { height: var(--accordion-content-height); } to { height: 0; } }
@keyframes ui-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes ui-fade-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes ui-zoom-in { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
@keyframes ui-slide-in-right { from { transform: translateX(100%); } to { transform: translateX(0); } }
@keyframes ui-slide-in-left { from { transform: translateX(-100%); } to { transform: translateX(0); } }
@keyframes ui-slide-in-bottom { from { transform: translateY(8px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
@keyframes ui-shimmer { 100% { transform: translateX(100%); } }
@keyframes ui-spin { to { transform: rotate(360deg); } }
.animate-in { animation: ui-fade-in 0.18s ease both; }
.animate-zoom-in { animation: ui-fade-in 0.15s ease, ui-zoom-in 0.15s ease both; }
.animate-slide-right { animation: ui-slide-in-right 0.3s cubic-bezier(0.32,0.72,0,1) both; }
.animate-slide-left { animation: ui-slide-in-left 0.3s cubic-bezier(0.32,0.72,0,1) both; }
.animate-slide-bottom { animation: ui-slide-in-bottom 0.2s ease both; }
.animate-spin { animation: ui-spin 1s linear infinite; }
.ui-skeleton { position: relative; overflow: hidden; }
.ui-skeleton::after {
  content: ""; position: absolute; inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, hsl(var(--foreground) / 0.06), transparent);
  animation: ui-shimmer 1.6s infinite;
}

/* ---- code blocks ----
   Carte facon ray.so : barre de titre (pastilles + nom de fichier centre +
   actions), puis le code sur fond sombre. Sobre a la lecture — le degrade
   colore est reserve a l'image exportee par le bouton de partage (islands/ui.js),
   sinon il prendrait le pas sur le code sur les 60+ pages de doc.
   .ui-code reste autonome : un <pre class="ui-code"> seul (page /themes) garde
   son cadre et ses coins ; c'est la carte qui les lui retire. */
pre, code { font-family: var(--font-mono); }
.ui-code {
  background: hsl(var(--foreground)); color: hsl(var(--background));
  border-radius: calc(var(--radius)); padding: 1rem; overflow-x: auto;
  font-size: 0.8125rem; line-height: 1.7; position: relative;
}
.dark .ui-code { background: #09090b; color: #e4e4e7; border: 1px solid hsl(var(--border)); }
.ui-code .tok-key { color: #c084fc; }
.ui-code .tok-str { color: #86efac; }
.ui-code .tok-cmt { color: #8b8b93; }
.ui-code .tok-fn  { color: #93c5fd; }
.ui-code .tok-tag { color: #f472b6; }

.ui-code-card {
  border-radius: calc(var(--radius) + 4px); overflow: hidden;
  background: #0c0c10; border: 1px solid hsl(var(--border));
  box-shadow: 0 10px 26px -16px rgb(0 0 0 / 0.45);
}
.dark .ui-code-card { background: #09090b; }
.ui-code-card .ui-code {
  border: 0; border-radius: 0; background: transparent; color: #e4e4e7;
  padding: 0.9rem 1.1rem 1.1rem;
}
.ui-code-bar {
  position: relative; display: flex; align-items: center; gap: 0.4rem;
  padding: 0.6rem 0.6rem 0.55rem 0.85rem;
  border-bottom: 1px solid rgb(255 255 255 / 0.06);
}
.ui-code-dot {
  width: 10px; height: 10px; border-radius: 9999px;
  background: rgb(255 255 255 / 0.14); flex: 0 0 auto;
}
/* Cale entre les pastilles et les actions plutot qu'inset 0 : sur un bloc
   etroit, un nom de fichier long passerait autrement SOUS les boutons. */
.ui-code-file {
  position: absolute; left: 5.5rem; right: 5.5rem; text-align: center;
  font-family: var(--font-mono); font-size: 0.72rem; line-height: 1.4;
  color: rgb(255 255 255 / 0.56); pointer-events: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ui-code-actions { margin-left: auto; display: flex; align-items: center; gap: 0.15rem; position: relative; }
.ui-code-btn {
  display: inline-flex; align-items: center; justify-content: center;
  height: 1.65rem; width: 1.65rem; border-radius: 0.375rem;
  color: rgb(255 255 255 / 0.42); transition: color .15s, background-color .15s;
}
.ui-code-btn:hover { color: #fafafa; background: rgb(255 255 255 / 0.08); }
.ui-code-btn:focus-visible { outline: 2px solid rgb(255 255 255 / 0.4); outline-offset: 1px; }
.ui-code-btn[data-busy] { opacity: .55; pointer-events: none; }
/* Une gouttiere separee du code par du blanc, pas par un filet : un rail a
   1px colle aux chiffres des que la chasse change.
   C'est la colonne de code qui defile, pas le <pre> : autrement une ligne
   longue passe sous les numeros puis a leur gauche, dans le padding du <pre>,
   ou rien ne la masque. Un position:sticky sur la gouttiere ne suffit pas —
   il laisse justement ce padding a decouvert. */
.ui-code-lines { display: flex; overflow: hidden; padding-right: 0; }
.ui-code-num {
  flex: 0 0 auto; text-align: right; user-select: none; -webkit-user-select: none;
  padding-right: 1rem; color: rgb(255 255 255 / 0.5);
}
.ui-code-scroll { flex: 1 1 auto; min-width: 0; overflow-x: auto; padding-right: 1.1rem; }

/* Repli du bloc de code sous un bouton (pages de composants).
   La carte est rendue entiere puis rognee : le code reste dans le document,
   donc trouvable par Ctrl+F et copiable, ce qu'un display:none interdirait.
   Le masque laisse depasser les premieres lignes — c'est ce depassement qui
   dit qu'il y a du code, la ou un onglet ne disait rien. */
.ui-reveal { position: relative; }
.ui-reveal-clip {
  position: relative; overflow: hidden;
  transition: max-height .38s cubic-bezier(.4, 0, .2, 1);
}
.ui-reveal[data-open="false"] .ui-reveal-clip { max-height: 9.25rem; }
/* L'estompe est un calque pose sur la carte, pas un mask-image : une image de
   masque ne se transitionne pas, une opacite si. Elle va vers la couleur du
   fond de page, donc elle suit le theme sans etre redite ici. */
.ui-reveal-fade {
  position: absolute; inset-inline: 0; bottom: 0; height: 5rem; pointer-events: none;
  background: linear-gradient(hsl(var(--background) / 0), hsl(var(--background)));
  transition: opacity .28s ease;
}
.ui-reveal[data-open="true"] .ui-reveal-fade { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  .ui-reveal-clip, .ui-reveal-fade { transition: none; }
}
/* Replie : le bouton se pose au centre de la bande visible. Deplie : il passe
   sous la carte, pour ne pas couvrir la derniere ligne. */
.ui-reveal[data-open="false"] .ui-reveal-bar {
  position: absolute; inset: 0; display: flex;
  align-items: center; justify-content: center; pointer-events: none;
}
.ui-reveal[data-open="true"] .ui-reveal-bar {
  display: flex; justify-content: center; margin-top: 0.6rem;
}
.ui-reveal-bar > button { pointer-events: auto; }
code.inline {
  background: hsl(var(--muted)); color: hsl(var(--foreground));
  padding: 0.15rem 0.35rem; border-radius: 0.3rem; font-size: 0.85em;
  border: 1px solid hsl(var(--border));
}

/* ---- chrome du site ----
   Plus d'accent colore ici. Un jeton nomme --bx-green portait en realite un
   bleu (#2563eb) et teintait le lien de nav actif, l'icone GitHub, le survol
   du pied de page et cinq details du bloc « Try it live ». Ce bleu ne venait
   d'aucun jeton de theme : il ne suivait ni --primary ni le mode sombre, et
   posait du contraste sur des commandes qui n'en demandaient pas — une icone
   GitHub en couleur pese autant qu'un bouton d'action alors qu'elle n'est
   qu'un lien. Tout retombe sur --foreground / --muted-foreground : la chrome
   suit desormais le theme comme le reste du site. */
.bx-nav-active { color: hsl(var(--foreground)); }
.bx-foot-link { color: hsl(var(--muted-foreground)); }
.bx-foot-link:hover { color: hsl(var(--foreground)); }

/* ---- misc fidelity helpers ---- */
.container-1400 { width: 100%; max-width: 1536px; margin-left: auto; margin-right: auto; }
/* Preview panel : blanc pur (comme shadcn.com) - juste bg-background. */
.story-grid { background-color: hsl(var(--background)); }
.scrollbar-thin::-webkit-scrollbar { width: 8px; height: 8px; }
.scrollbar-thin::-webkit-scrollbar-thumb { background: hsl(var(--border)); border-radius: 9999px; }
[hidden] { display: none !important; }
/* Arbitrary z-index utilities: route_css doesn't synth arbitrary values, so the
   toast region (z-[90]), command palette + mobile-nav drawer (z-[100]) would
   otherwise fall back to z-index:auto and render *under* the sticky z-50 header. */
.z-\[90\] { z-index: 90; }
.z-\[100\] { z-index: 100; }
/* AI components: streaming typing indicator + a11y-only text */
/* A demo wider than a phone (the OTP field, a navigation menu) scrolls inside
   its stage instead of widening the whole page. Auto margins rather than
   justify-content: a centred flex child that overflows is cut on the left,
   where no scrollbar can reach it. */
@media (max-width: 479px) {
  .story-grid.justify-center { overflow-x: auto; justify-content: flex-start; }
  .story-grid.justify-center > * { margin-left: auto; margin-right: auto; }
}
/* An unchecked switch takes shadcn's --input track. The shared primitive
   falls back to bg-muted (no bg-input utility is generated), which on a white
   card is close to invisible. */
[data-ui="switch"][data-state="unchecked"] { background-color: hsl(var(--input)); }
/* Active row of the search palette. Written here because the rows are drawn
   by the island, where route_css cannot see their classes. */
[data-ui="command-item"][data-active="true"] { background-color: hsl(var(--accent)); color: hsl(var(--accent-foreground)); }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
.ui-typing { display: inline-flex; align-items: center; gap: 3px; }
.ui-typing span { width: 5px; height: 5px; border-radius: 9999px; background: hsl(var(--muted-foreground)); animation: ui-typing-bounce 1.2s infinite ease-in-out; }
.ui-typing span:nth-child(2) { animation-delay: 0.15s; }
.ui-typing span:nth-child(3) { animation-delay: 0.3s; }
@keyframes ui-typing-bounce { 0%, 80%, 100% { transform: translateY(0); opacity: 0.4; } 40% { transform: translateY(-4px); opacity: 1; } }
@media (prefers-reduced-motion: reduce) { .ui-typing span { animation-duration: 2.4s; } }
/* AI: rich markdown response prose, streaming caret, running spinner, chevron */
.ai-prose { font-size: .875rem; line-height: 1.6; }
.ai-prose > :first-child { margin-top: 0; } .ai-prose > :last-child { margin-bottom: 0; }
.ai-prose p { margin: .5rem 0; }
.ai-prose ul, .ai-prose ol { margin: .5rem 0; padding-left: 1.3rem; }
.ai-prose ul { list-style: disc; } .ai-prose ol { list-style: decimal; }
.ai-prose li { margin: .15rem 0; }
.ai-prose h1, .ai-prose h2, .ai-prose h3 { font-weight: 600; margin: .8rem 0 .3rem; line-height: 1.3; }
.ai-prose h1 { font-size: 1.05rem; } .ai-prose h2 { font-size: 1rem; } .ai-prose h3 { font-size: .925rem; }
.ai-prose a { text-decoration: underline; text-underline-offset: 2px; }
.ai-prose strong { font-weight: 650; }
.ai-prose :not(pre) > code { background: hsl(var(--foreground) / 0.08); border-radius: .3rem; padding: .1em .35em; font-family: var(--font-mono); font-size: .85em; }
.ai-prose pre, .ai-prose figure { margin: .6rem 0; }
.ai-caret { display: inline-block; width: 2px; height: 1.05em; margin-left: 2px; background: currentColor; vertical-align: text-bottom; border-radius: 1px; animation: ai-caret-blink 1s steps(1) infinite; }
@keyframes ai-caret-blink { 50% { opacity: 0; } }
.ai-spin { display: inline-block; width: 13px; height: 13px; border: 2px solid hsl(var(--muted-foreground) / 0.3); border-top-color: hsl(var(--muted-foreground)); border-radius: 50%; animation: ui-spin .7s linear infinite; }
.ai-chevron { transition: transform .2s ease; }
[data-state="open"] > .ai-chevron, [data-state="open"] .ai-chevron { transform: rotate(180deg); }
[data-ui="block-tab"][data-state="active"] { background: hsl(var(--primary)); color: hsl(var(--primary-foreground)); border-color: transparent; }
/* AI: chain-of-thought timeline connector (vertical line between step dots) */
.ai-cot > .ai-cot-step { position: relative; }
.ai-cot > .ai-cot-step:not(:last-child)::before { content: ""; position: absolute; left: 9px; top: 22px; bottom: -2px; width: 1.5px; background: hsl(var(--border)); }
@media (prefers-reduced-motion: reduce) { .ai-caret, .ai-spin { animation: none; } }

/* ---- standard Tailwind utilities this server's route_css (encre-css) does NOT
   emit (verified via a puppeteer CSS audit 2026-07-08: it generates p-1/p-2/p-10
   but not p-4/p-6, no text-lg, shadow-md, max-w-md, truncate, …). Hand-authored
   here with the canonical Tailwind values so components render correctly
   regardless of route_css's coverage — the same "deterministic styling" reason
   the token utilities live here. route_css values (when present) match these. --- */
.p-4 { padding: 1rem; } .p-6 { padding: 1.5rem; } .p-8 { padding: 2rem; }
.pt-0 { padding-top: 0; } .pt-2 { padding-top: .5rem; }
.pl-8 { padding-left: 2rem; } .pr-2 { padding-right: .5rem; }
.pl-2\.5 { padding-left: .625rem; } .pr-2\.5 { padding-right: .625rem; }
.py-0\.5 { padding-top: .125rem; padding-bottom: .125rem; }
.mt-0\.5 { margin-top: .125rem; } .mt-1 { margin-top: .25rem; }
.-mx-1 { margin-left: -.25rem; margin-right: -.25rem; } .my-1 { margin-top: .25rem; margin-bottom: .25rem; }
.gap-3 { gap: .75rem; } .gap-x-3 { column-gap: .75rem; }
.space-y-1\.5 > :not([hidden]) ~ :not([hidden]) { margin-top: .375rem; }
.h-10 { height: 2.5rem; } .h-16 { height: 4rem; } .w-16 { width: 4rem; }
.h-1\.5 { height: .375rem; } .w-1\.5 { width: .375rem; } .h-px { height: 1px; }
.w-72 { width: 18rem; } .w-80 { width: 20rem; } .w-3\/4 { width: 75%; }
.max-w-md { max-width: 28rem; } .max-h-72 { max-height: 18rem; }
.min-h-\[440px\] { min-height: 440px; } .w-\[400px\] { width: 400px; } .w-\[180px\] { width: 180px; }
.text-lg { font-size: 1.125rem; line-height: 1.75rem; } .text-left { text-align: left; } .text-right { text-align: right; }
.shadow-md { box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); } .shadow-none { box-shadow: 0 0 #0000; }
.border-transparent { border-color: transparent; } .border-l { border-left-width: 1px; } .border-0 { border-width: 0; }
.rounded-2xl { border-radius: 1rem; }
.grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } .col-span-2 { grid-column: span 2 / span 2; }
.grid-cols-\[20px_1fr\] { grid-template-columns: 20px 1fr; }
.flex-row { flex-direction: row; } .flex-row-reverse { flex-direction: row-reverse; } .flex-col-reverse { flex-direction: column-reverse; }
.right-4 { right: 1rem; } .top-4 { top: 1rem; } .right-0 { right: 0; } .top-full { top: 100%; } .bottom-full { bottom: 100%; }
.cursor-pointer { cursor: pointer; } .truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.overflow-auto { overflow: auto; } .inline-block { display: inline-block; } .opacity-70 { opacity: .7; }
.transition-transform { transition-property: transform; transition-timing-function: cubic-bezier(.4,0,.2,1); transition-duration: .15s; }
.transition-opacity { transition-property: opacity; transition-timing-function: cubic-bezier(.4,0,.2,1); transition-duration: .15s; }
.duration-200 { transition-duration: .2s; } .caption-bottom { caption-side: bottom; }
.focus\:outline-none:focus { outline: 2px solid transparent; outline-offset: 2px; }
.peer:disabled ~ .peer-disabled\:opacity-70 { opacity: .7; }
@media (min-width: 640px) { .sm\:text-left { text-align: left; } .sm\:flex-row { flex-direction: row; } .sm\:justify-end { justify-content: flex-end; } }
@media (min-width: 1024px) { .lg\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } .lg\:flex { display: flex; } }
/* second batch (audit round 2) */
.shadow { box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); }
.rounded { border-radius: .25rem; } .rounded-sm { border-radius: .125rem; } .rounded-full { border-radius: 9999px; }
.text-xs { font-size: .75rem; line-height: 1rem; } .text-xl { font-size: 1.25rem; line-height: 1.75rem; }
.p-2 { padding: .5rem; } .pl-3 { padding-left: .75rem; } .px-8 { padding-left: 2rem; padding-right: 2rem; }
.px-2\.5 { padding-left: .625rem; padding-right: .625rem; } .px-3\.5 { padding-left: .875rem; padding-right: .875rem; }
.py-2\.5 { padding-top: .625rem; padding-bottom: .625rem; } .mt-8 { margin-top: 2rem; }
.max-w-sm { max-width: 24rem; } .align-middle { vertical-align: middle; }
.leading-relaxed { line-height: 1.625; } .resize-none { resize: none; }
.justify-start { justify-content: flex-start; } .antialiased { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
.backdrop-blur { -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px); }
.disabled\:cursor-not-allowed:disabled { cursor: not-allowed; }
.peer:disabled ~ .peer-disabled\:cursor-not-allowed { cursor: not-allowed; }
.focus-within\:ring-1:focus-within { box-shadow: 0 0 0 1px hsl(var(--ring) / 0.4); }

/* ---- "Try it live" — embedded play.bext.dev editor (demo.bext.dev pattern) ---- */
.playground { margin: 3.5rem 0 .5rem; padding-top: 2rem; border-top: 1px solid hsl(var(--border)); }
.pg-bar { display: flex; align-items: center; gap: 1rem; }
.pg-title { display: inline-flex; align-items: center; gap: .5rem; font-size: .72rem; text-transform: uppercase; letter-spacing: .07em; color: hsl(var(--muted-foreground)); font-weight: 700; }
.pg-title .pg-ic { display: inline-flex; color: hsl(var(--muted-foreground)); }
.pg-title .pg-ic svg { width: 15px; height: 15px; }
.pg-open { margin-left: auto; display: inline-flex; align-items: center; gap: .3rem; font-size: .8rem; font-weight: 600; color: hsl(var(--foreground)); padding: .35rem .7rem; border: 1px solid hsl(var(--border)); border-radius: 8px; background: hsl(var(--background)); box-shadow: 0 1px 2px rgba(0,0,0,.04); transition: color .15s, border-color .15s, transform .15s; }
.pg-open:hover { border-color: hsl(var(--foreground) / 0.35); transform: translateY(-1px); }
.pg-open .pg-arr { display: inline-flex; color: hsl(var(--muted-foreground)); }
.pg-open .pg-arr svg { width: 13px; height: 13px; }
.pg-open:hover .pg-arr { color: hsl(var(--foreground)); }
.pg-variants { display: inline-flex; gap: 2px; padding: 3px; border-radius: 9px; background: hsl(var(--muted)); }
.pg-var { appearance: none; border: 0; background: transparent; cursor: pointer; font: inherit; font-size: .78rem; font-weight: 600; color: hsl(var(--muted-foreground)); padding: .3rem .7rem; border-radius: 6px; transition: background .15s, color .15s; }
.pg-var:hover { color: hsl(var(--foreground)); }
.pg-var.on { background: hsl(var(--background)); color: hsl(var(--foreground)); box-shadow: 0 1px 2px rgba(0,0,0,.08); }
.pg-sub { font-size: .9rem; color: hsl(var(--muted-foreground)); line-height: 1.6; margin: .65rem 0 1rem; max-width: 48rem; }
.pg-stage { position: relative; border: 1px solid #1e2630; border-radius: 14px; overflow: hidden; background: #0b0f17; box-shadow: 0 1px 2px rgba(12,12,20,.06), 0 26px 50px -34px rgba(0,0,0,.5); height: clamp(520px, 64vh, 720px); }
.pg-facade { position: absolute; inset: 0; display: flex; flex-direction: column; background: radial-gradient(135% 120% at 50% -10%, #182030 0%, #0b0f17 58%); }
.pg-fauxbar { display: flex; align-items: center; gap: .7rem; padding: .55rem .85rem; border-bottom: 1px solid rgba(255,255,255,.06); background: rgba(13,18,32,.6); flex: 0 0 auto; }
.pg-dots { display: inline-flex; gap: 6px; flex: 0 0 auto; }
.pg-dots i { width: 11px; height: 11px; border-radius: 50%; background: #313a49; }
.pg-tab { display: inline-flex; align-items: center; gap: .4rem; min-width: 0; padding: .25rem .55rem; border-radius: 7px; background: rgba(255,255,255,.04); border: 1px solid rgba(255,255,255,.06); }
.pg-tab-ic { display: inline-flex; color: #64748b; flex: 0 0 auto; }
.pg-tab-ic svg { width: 13px; height: 13px; }
.pg-tab-name { font-family: var(--font-mono); font-size: .72rem; color: #cbd5e1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pg-fauxmeta { margin-left: auto; font-size: .7rem; color: #5b6675; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 0 1 auto; }
.pg-peek { flex: 1 1 auto; margin: 0; padding: 1.1rem 1.35rem 0; font-family: var(--font-mono); font-size: .78rem; line-height: 1.75; color: #4b566a; white-space: pre; overflow: hidden; user-select: none; pointer-events: none; tab-size: 2; -webkit-mask-image: linear-gradient(#000 28%, transparent 86%); mask-image: linear-gradient(#000 28%, transparent 86%); }
.pg-cta { position: absolute; inset: 0; top: 38px; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .85rem; padding: 1rem; background: radial-gradient(60% 52% at 50% 56%, hsl(var(--foreground) / 0.10) 0%, transparent 72%); }
.pg-launch { position: relative; display: inline-flex; align-items: center; gap: .5rem; font: inherit; font-size: .92rem; font-weight: 700; letter-spacing: -.01em; color: #0a0a0a; background: #fff; border: 0; border-radius: 11px; padding: .72rem 1.25rem; cursor: pointer; box-shadow: 0 16px 38px -14px rgba(0,0,0,.7); transition: transform .16s cubic-bezier(.2,.7,.2,1), box-shadow .16s; }
.pg-launch::after { content: ""; position: absolute; inset: 0; border-radius: inherit; box-shadow: 0 0 0 0 rgba(255,255,255,.4); animation: pg-pulse 2.6s ease-out infinite; pointer-events: none; }
.pg-launch:hover { transform: translateY(-2px); box-shadow: 0 24px 50px -16px rgba(0,0,0,.8); }
.pg-launch:active { transform: translateY(0); }
.pg-launch .pg-launch-ic { display: inline-flex; color: currentColor; }
.pg-launch .pg-launch-ic svg { width: 15px; height: 15px; }
.pg-launch.loading { pointer-events: none; opacity: .85; }
.pg-launch.loading::after { animation: none; }
.pg-tagline { font-size: .76rem; color: #8b95a5; text-align: center; max-width: 32rem; }
@keyframes pg-pulse { 0% { box-shadow: 0 0 0 0 rgba(255,255,255,.32); } 70% { box-shadow: 0 0 0 13px rgba(255,255,255,0); } 100% { box-shadow: 0 0 0 0 rgba(255,255,255,0); } }
.pg-stage.pg-live { background: #0b0f17; }
.pg-stage.pg-live .pg-facade { display: none; }
.pg-frame { width: 100%; height: 100%; border: 0; display: block; opacity: 0; transition: opacity .45s ease; }
.pg-stage.pg-ready .pg-frame { opacity: 1; }
.pg-loading { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .9rem; background: radial-gradient(135% 120% at 50% -10%, #182030 0%, #0b0f17 58%); transition: opacity .4s ease; }
.pg-stage.pg-ready .pg-loading { opacity: 0; pointer-events: none; }
.pg-spin { width: 26px; height: 26px; border: 2.5px solid rgba(255,255,255,.16); border-top-color: #fff; border-radius: 50%; animation: pg-spin .8s linear infinite; }
.pg-loading-tx { font-size: .82rem; color: #9aa4b2; letter-spacing: -.01em; }
@keyframes pg-spin { to { transform: rotate(360deg); } }
@media (max-width: 640px) { .pg-stage { height: 460px; } .pg-fauxmeta { display: none; } }
@media (prefers-reduced-motion: reduce) { .pg-launch::after { animation: none; } .pg-spin { animation-duration: 1.4s; } .pg-frame { transition: none; } .pg-loading { transition: none; } }

/* Grille de cartes-chiffres qui se decide sur la largeur DISPONIBLE et non
   sur celle de la fenetre : une carte descend a une colonne des que la place
   manque, au lieu de deborder d'un montant a deux virgules. */
.stat-grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); }

/* ---- /create : le constructeur de theme ----
   Le rail reste sombre dans les deux themes du site, et c'est voulu : c'est
   l'outil, pas l'echantillon. Un rail qui prendrait la couleur en cours de
   reglage empecherait de juger cette couleur — on comparerait le theme a
   lui-meme. Il garde donc un cadre neutre constant, comme la table lumineuse
   d'un photographe. C'est la seule divergence assumee avec .agents/theming :
   partout ailleurs les tokens gouvernent. */
.create-shell { display: grid; gap: 1.25rem; grid-template-columns: 1fr; align-items: start; }

/* Disposition d'application, a partir de lg : la page occupe exactement la
   fenetre et ne defile pas — ce sont le rail et le canevas qui defilent, chacun
   chez lui. Rien ne se deplace donc quand on parcourt les composants : les
   reglages restent sous le curseur, ou qu'on en soit dans le canevas.
   Sous lg les deux panneaux s'empilent et la page redevient une page : deux
   zones defilantes l'une au-dessus de l'autre sur un telephone, ce serait
   deux prisons de 300px au lieu d'un document.
   Les min-height: 0 ne sont pas superflus — un enfant de flex refuse par
   defaut de descendre sous sa hauteur de contenu, et la chaine entiere
   (body > div > main > section) doit ceder pour que le canevas puisse borner
   sa hauteur et defiler au lieu de pousser la page. */
@media (width >= 64rem) {
  body.app-shell { height: 100vh; overflow: hidden; }
  body.app-shell > .flex-1 { min-height: 0; }
  body.app-shell main { min-height: 0; display: flex; flex-direction: column; }
  .create-section { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
  .create-shell { flex: 1 1 auto; min-height: 0; grid-template-columns: 17rem minmax(0, 1fr); }
  .create-rail { position: static; height: 100%; max-height: none; overflow-y: auto; }
  .create-canvas { height: 100%; min-height: 0; overflow-y: auto; }
}
.create-rail {
  min-width: 0; border-radius: 0.9rem; background: #17171a; color: #fafafa;
  padding: 0.7rem; display: flex; flex-direction: column; gap: 0.35rem;
  border: 1px solid #26262b;
}
.create-rail-head {
  position: relative; display: flex; align-items: center; justify-content: space-between;
  padding: 0.35rem 0.55rem 0.6rem; font-size: 0.9rem; font-weight: 600;
}
.create-burger {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.7rem; height: 1.7rem; border-radius: 0.4rem; color: #c9c9d1;
  transition: background-color .15s, color .15s;
}
.create-burger:hover { background: #26262b; color: #fafafa; }
.create-burger:focus-visible { outline: 2px solid #6366f1; outline-offset: 1px; }

/* Le menu du rail. Il s'ouvre PAR-DESSUS le canevas et non dans le rail :
   c'est un menu de commandes, pas une septieme ligne de reglage, et le rail
   n'a pas la largeur d'afficher un libelle et son raccourci cote a cote.
   Il passe par data-overlay/data-light, donc le routeur de clics du site ferme
   les autres flottants et gere le clic exterieur — pas de second mecanisme. */
/* position: fixed et non absolute — le rail defile, et un enfant absolu s'y
   ferait rogner net au bord droit. C'est l'ilot qui pose left/top au moment de
   l'ouverture, depuis la position du bouton. */
.create-menu {
  position: fixed; z-index: 60;
  min-width: 15rem; padding: 0.3rem;
  border-radius: 0.7rem; background: #2b2b30; border: 1px solid #3a3a42;
  box-shadow: 0 18px 40px -16px rgb(0 0 0 / 0.6);
}
.create-mi {
  width: 100%; display: flex; align-items: center; justify-content: space-between;
  gap: 1.5rem; border-radius: 0.4rem; padding: 0.4rem 0.55rem;
  font-size: 0.82rem; color: #e6e6ea; transition: background-color .12s;
}
.create-mi:hover:not(:disabled) { background: #3a3a42; }
.create-mi:disabled { color: #6f6f79; }
.create-mi kbd {
  font-family: var(--font-mono); font-size: 0.7rem; color: #8b8b94;
  background: none; border: 0; padding: 0;
}
.create-mi-sep { height: 1px; margin: 0.3rem 0.2rem; background: #3a3a42; }
.create-sep { height: 1px; margin: 0.3rem 0.15rem; background: #26262b; }
.create-preset-btn { font-family: var(--font-mono); font-size: 0.74rem; color: #c9c9d1; }
.create-row {
  width: 100%; display: flex; align-items: center; gap: 0.6rem; text-align: left;
  border-radius: 0.6rem; background: #202024; padding: 0.5rem 0.7rem;
  transition: background-color .15s;
}
.create-row:hover { background: #27272c; }
.create-row:focus-visible { outline: 2px solid #6366f1; outline-offset: 1px; }
.create-row-label { display: block; font-size: 0.68rem; color: #8b8b94; line-height: 1.35; }
.create-row-value { display: block; font-size: 0.86rem; font-weight: 600; line-height: 1.35; }
.create-row-txt { min-width: 0; flex: 1 1 auto; }
.create-row-txt > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.create-row-aff { flex: 0 0 auto; display: flex; align-items: center; justify-content: center; width: 1.4rem; height: 1.4rem; color: #8b8b94; }
.create-dot { flex: 0 0 auto; width: 1.05rem; height: 1.05rem; border-radius: 9999px; box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.18); }

/* Le panneau d'options est un popover, pose A COTE du rail comme le menu, et
   non deplie sous sa ligne : deplie, il repoussait les six autres reglages
   hors de l'ecran, et on choisissait une couleur sans plus voir celles d'a
   cote. Meme mecanique que le menu — fixed, coordonnees posees par l'ilot a
   l'ouverture, parce que le rail defile et rognerait un enfant absolu.
   Sous lg le rail n'est plus une colonne fixe : le popover s'y replie en
   panneau dans le flux, ou il ne masque rien. */
/* Hors popover (sous lg), le panneau reste dans le flux : il lui faut sa
   propre respiration, la regle du popover ne s'appliquant pas la. */
.create-panel { display: none; padding: 0.15rem 0.2rem 0.45rem; }
.create-row[aria-expanded="true"] + .create-panel { display: block; }
@media (width >= 64rem) {
  .create-panel {
    position: fixed; z-index: 60; min-width: 12.5rem; max-height: 70vh; overflow-y: auto;
    padding: 0.3rem; border-radius: 0.7rem;
    background: #2b2b30; border: 1px solid #3a3a42;
    box-shadow: 0 18px 40px -16px rgb(0 0 0 / 0.6);
  }
}
.create-opts { display: flex; flex-direction: column; gap: 0.1rem; }
.create-opt {
  display: flex; align-items: center; gap: 0.5rem; min-width: 0; width: 100%;
  border-radius: 0.4rem; padding: 0.4rem 0.55rem; font-size: 0.82rem;
  color: #e6e6ea; transition: background-color .12s;
}
.create-opt:hover { background: #3a3a42; }
/* Vise le libelle par sa classe, pas par « tout span sauf la coche » : cette
   negation attrapait aussi la pastille de couleur, qui heritait alors de
   flex: 1 et s'etirait en rectangle. Meme cause que les pastilles ovales de
   /themes — un enfant de flex sans base fixe s'etale des qu'on lui donne le
   droit de grandir. */
.create-opt-label {
  flex: 1 1 auto; min-width: 0; text-align: left;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.create-check { flex: 0 0 auto; visibility: hidden; color: #fafafa; display: inline-flex; }
.create-opt[data-active="true"] .create-check { visibility: visible; }
.create-actions { margin-top: 0.5rem; display: flex; flex-direction: column; gap: 0.35rem; }
.create-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.4rem;
  border-radius: 0.55rem; padding: 0.5rem 0.7rem; font-size: 0.8rem; font-weight: 500;
  border: 1px solid #32323a; color: #e6e6ea; transition: background-color .15s, border-color .15s;
}
.create-btn:hover { background: #26262b; }
.create-btn.is-primary { background: #fafafa; color: #17171a; border-color: #fafafa; }
.create-btn.is-primary:hover { background: #e4e4e7; }
.create-preset { font-family: var(--font-mono); font-size: 0.72rem; color: #8b8b94; }

/* Le canevas porte le theme en cours de reglage : c'est LUI qui recoit les
   variables, pas :root, sinon regler un theme repeindrait le rail et la
   barre du site avec. */
.create-canvas {
  min-width: 0; border-radius: 0.9rem; border: 1px solid hsl(var(--border));
  background: hsl(var(--muted) / 0.45); padding: 1rem;
  font-family: var(--font-sans);
}
/* [data-slot="card-title"] autant que les <h*> : les titres de carte sont des
   div chez shadcn comme ici, donc sans ce crochet la police de titre ne
   s'appliquerait a rien dans le canevas — il n'y a pas un seul <h*> dedans. */
.create-canvas :is(h1, h2, h3, h4, h5, h6, [data-slot="card-title"]) {
  font-family: var(--font-heading, var(--font-sans));
}
.create-page[hidden] { display: none; }
.create-cols { columns: 1; column-gap: 1rem; }
@media (width >= 48rem) { .create-cols { columns: 2; } }
@media (width >= 96rem) { .create-cols { columns: 3; } }
.create-cols > * { break-inside: avoid; margin-bottom: 1rem; }
/* Collee en bas du canevas : les deux pages font plusieurs ecrans, et un
   selecteur relegue tout en bas ne se decouvre qu'apres avoir fait defiler
   ce qu'il sert justement a remplacer. */
/* Le dialogue « Your theme ».
   Classe doublee pour passer devant .max-w-lg du dialogue de base : meme
   specificite, et la feuille generee est injectee apres celle-ci.
   min-width: 0 sur le contenu est la vraie correction du debordement — un
   enfant de grille refuse par defaut de descendre sous la largeur de son
   contenu, donc la carte de code sortait de 117px par la droite au lieu de
   laisser sa colonne defiler. Le plafond de hauteur suit la meme logique :
   un theme a beaucoup de variables. */
.create-code-dialog.create-code-dialog {
  max-width: 44rem; max-height: 85vh; overflow-y: auto;
}
.create-code-dialog #create-code-slot { min-width: 0; }

/* Le panneau de code des graphiques. Classe doublee pour passer devant le
   max-w-sm du primitif Sheet — meme specificite, et la feuille generee est
   injectee apres celle-ci. 384px pour un extrait de douze lignes, ce serait un
   couloir. */
.chart-sheet.chart-sheet { max-width: min(46rem, 92vw); }
.chart-sheet .ui-code-card { min-width: 0; }

.create-pager {
  position: sticky; bottom: 0.4rem; z-index: 2;
  display: flex; justify-content: flex-end; gap: 0.3rem; padding-top: 0.6rem;
}
.create-pg {
  border-radius: 0.45rem; padding: 0.25rem 0.6rem; font-family: var(--font-mono);
  font-size: 0.72rem; color: hsl(var(--muted-foreground));
  border: 1px solid hsl(var(--border)); background: hsl(var(--background));
  box-shadow: 0 2px 8px -4px rgb(0 0 0 / 0.25);
}
.create-pg[data-active="true"] { background: hsl(var(--foreground)); color: hsl(var(--background)); border-color: hsl(var(--foreground)); }

/* ---- Loader spin fiable (route_css genere .animate-spin avec un @keyframes
       ui-spin incomplet : to seul, sans from -> l'animation peut ne pas
       demarrer sur certains repaint. Cette classe garantit la rotation.) ---- */
@keyframes bx-spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
.bx-spin { transform-origin: 50% 50%; animation: bx-spin 1s linear infinite; }

/* ---- Message data-align variants (Tailwind Rust ne compile pas les
       arbitrary variants sur data-attributs comme data-[align=end]:...) ---- */
[data-slot="message"][data-align="end"] { flex-direction: row-reverse; }
/* Aligner uniquement les vraies bulles (pas Header/Footer qui gerent leur
   propre justify) sur le bord droit quand le message est envoye. */
[data-slot="message"][data-align="end"] [data-slot="message-content"] > [data-slot="bubble"],
[data-slot="message"][data-align="end"] [data-slot="message-content"] > [data-slot="bubble-group"],
[data-slot="message"][data-align="end"] [data-slot="message-content"] > [data-slot="marker"] { align-self: flex-end; }
[data-slot="message"][data-align="end"] [data-slot="message-header"],
[data-slot="message"][data-align="end"] [data-slot="message-footer"] { justify-content: flex-end; }

/* ---- Auto-shimmer sur les Markers role=status (chargement / typing).
       Applique le meme effet que .shimmer sans devoir l'ajouter manuellement
       partout ou un Marker signale un etat en cours. Ne s'applique pas si
       .shimmer-none est deja present sur le MarkerContent. */
[data-slot="marker"][role="status"] [data-slot="marker-content"]:not(.shimmer-none) {
  --_spread: var(--shimmer-spread, calc(3ch + 40px));
  --_base: currentColor;
  --_highlight: var(--shimmer-color, color-mix(in oklch, currentColor 20%, transparent));
  background-image: var(--shimmer-image, linear-gradient(
    calc(90deg + var(--shimmer-angle, 20deg)),
    var(--_base) calc(50% - var(--_spread)),
    color-mix(in oklch, var(--_highlight), var(--_base) 50%) calc(50% - var(--_spread) * 0.5),
    var(--_highlight) 50%,
    color-mix(in oklch, var(--_highlight), var(--_base) 50%) calc(50% + var(--_spread) * 0.5),
    var(--_base) calc(50% + var(--_spread))
  ));
  background-repeat: no-repeat;
  background-size: calc(200% + var(--_spread) * 2) 100%;
  background-position: 0 0;
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: var(--shimmer-text-fill, transparent);
  animation: tw-shimmer var(--shimmer-duration, 2s) linear infinite;
}
.dark [data-slot="marker"][role="status"] [data-slot="marker-content"]:not(.shimmer-none) {
  --_highlight: var(--shimmer-color, color-mix(in oklch, currentColor 60%, white 20%));
}
@media (prefers-reduced-motion: reduce) {
  [data-slot="marker"][role="status"] [data-slot="marker-content"] {
    animation: none; background-image: none; -webkit-text-fill-color: currentColor;
  }
}

/* ============================================================ utilities ==
   scroll-fade + shimmer — ports fideles de shadcn/ui packages/shadcn/src/tailwind.css
   (voir /docs/utils/scroll-fade et /docs/utils/shimmer). Ecrits en CSS natif
   au lieu du Tailwind v4 utility-custom, parce que route_css bext ne compile
   pas @property ni @utility name-*. */

/* ---- scroll-fade ---- */
@property --scroll-fade-t { syntax: "<length-percentage>"; inherits: false; initial-value: 0px; }
@property --scroll-fade-b { syntax: "<length-percentage>"; inherits: false; initial-value: 0px; }
@property --scroll-fade-s { syntax: "<length-percentage>"; inherits: false; initial-value: 0px; }
@property --scroll-fade-e { syntax: "<length-percentage>"; inherits: false; initial-value: 0px; }
@property --scroll-fade-mask { syntax: "*"; inherits: false; }

@keyframes scroll-fade-reveal-t {
  from { --scroll-fade-t: 0px; }
  to   { --scroll-fade-t: var(--_scroll-fade-size-t, min(12%, 40px)); }
}
@keyframes scroll-fade-reveal-b {
  from { --scroll-fade-b: var(--_scroll-fade-size-b, min(12%, 40px)); }
  to   { --scroll-fade-b: 0px; }
}
@keyframes scroll-fade-reveal-s {
  from { --scroll-fade-s: 0px; }
  to   { --scroll-fade-s: var(--_scroll-fade-size-s, min(12%, 40px)); }
}
@keyframes scroll-fade-reveal-e {
  from { --scroll-fade-e: var(--_scroll-fade-size-e, min(12%, 40px)); }
  to   { --scroll-fade-e: 0px; }
}

.scroll-fade, .scroll-fade-y {
  --_scroll-fade-size-t: var(--scroll-fade-t-size, var(--scroll-fade-size, min(12%, 40px)));
  --_scroll-fade-size-b: var(--scroll-fade-b-size, var(--scroll-fade-size, min(12%, 40px)));
  --scroll-fade-block: linear-gradient(to bottom, transparent 0, #000 var(--scroll-fade-t, 0px), #000 calc(100% - var(--scroll-fade-b, 0px)), transparent 100%);
  -webkit-mask-image: var(--scroll-fade-mask, var(--scroll-fade-block));
          mask-image: var(--scroll-fade-mask, var(--scroll-fade-block));
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
}
@supports (animation-timeline: scroll()) {
  .scroll-fade, .scroll-fade-y {
    animation: scroll-fade-reveal-t 1ms ease-in-out, scroll-fade-reveal-b 1ms ease-in-out;
    animation-timeline: scroll(self y), scroll(self y);
    animation-range: 0 var(--scroll-fade-reveal, 96px), calc(100% - var(--scroll-fade-reveal, 96px)) 100%;
    animation-fill-mode: both;
  }
}
@supports not (animation-timeline: scroll()) {
  .scroll-fade, .scroll-fade-y { --scroll-fade-t: var(--_scroll-fade-size-t); --scroll-fade-b: var(--_scroll-fade-size-b); }
}

.scroll-fade-x {
  --_scroll-fade-size-s: var(--scroll-fade-s-size, var(--scroll-fade-size, min(12%, 40px)));
  --_scroll-fade-size-e: var(--scroll-fade-e-size, var(--scroll-fade-size, min(12%, 40px)));
  --scroll-fade-inline: linear-gradient(to right, transparent 0, #000 var(--scroll-fade-s, 0px), #000 calc(100% - var(--scroll-fade-e, 0px)), transparent 100%);
  -webkit-mask-image: var(--scroll-fade-mask, var(--scroll-fade-inline));
          mask-image: var(--scroll-fade-mask, var(--scroll-fade-inline));
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
}
.scroll-fade-x:where([dir="rtl"], [dir="rtl"] *) {
  --scroll-fade-inline: linear-gradient(to left, transparent 0, #000 var(--scroll-fade-s, 0px), #000 calc(100% - var(--scroll-fade-e, 0px)), transparent 100%);
}
@supports (animation-timeline: scroll()) {
  .scroll-fade-x {
    animation: scroll-fade-reveal-s 1ms ease-in-out, scroll-fade-reveal-e 1ms ease-in-out;
    animation-timeline: scroll(self inline), scroll(self inline);
    animation-range: 0 var(--scroll-fade-reveal, 96px), calc(100% - var(--scroll-fade-reveal, 96px)) 100%;
    animation-fill-mode: both;
  }
}
@supports not (animation-timeline: scroll()) {
  .scroll-fade-x { --scroll-fade-s: var(--_scroll-fade-size-s); --scroll-fade-e: var(--_scroll-fade-size-e); }
}

.scroll-fade-t {
  --_scroll-fade-size-t: var(--scroll-fade-t-size, var(--scroll-fade-size, min(12%, 40px)));
  --scroll-fade-mask: linear-gradient(to bottom, transparent 0, #000 var(--scroll-fade-t, 0px), #000 100%);
  -webkit-mask-image: var(--scroll-fade-mask); mask-image: var(--scroll-fade-mask);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
}
@supports (animation-timeline: scroll()) {
  .scroll-fade-t {
    animation: scroll-fade-reveal-t 1ms ease-in-out;
    animation-timeline: scroll(self y);
    animation-range: 0 var(--scroll-fade-reveal, 96px);
    animation-fill-mode: both;
  }
}
@supports not (animation-timeline: scroll()) { .scroll-fade-t { --scroll-fade-t: var(--_scroll-fade-size-t); } }

.scroll-fade-b {
  --_scroll-fade-size-b: var(--scroll-fade-b-size, var(--scroll-fade-size, min(12%, 40px)));
  --scroll-fade-mask: linear-gradient(to bottom, #000 0, #000 calc(100% - var(--scroll-fade-b, 0px)), transparent 100%);
  -webkit-mask-image: var(--scroll-fade-mask); mask-image: var(--scroll-fade-mask);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
}
@supports (animation-timeline: scroll()) {
  .scroll-fade-b {
    animation: scroll-fade-reveal-b 1ms ease-in-out;
    animation-timeline: scroll(self y);
    animation-range: calc(100% - var(--scroll-fade-reveal, 96px)) 100%;
    animation-fill-mode: both;
  }
}
@supports not (animation-timeline: scroll()) { .scroll-fade-b { --scroll-fade-b: var(--_scroll-fade-size-b); } }

.scroll-fade-l {
  --_scroll-fade-size-s: var(--scroll-fade-s-size, var(--scroll-fade-size, min(12%, 40px)));
  --scroll-fade-mask: linear-gradient(to right, transparent 0, #000 var(--scroll-fade-s, 0px), #000 100%);
  -webkit-mask-image: var(--scroll-fade-mask); mask-image: var(--scroll-fade-mask);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
}
@supports not (animation-timeline: scroll()) { .scroll-fade-l { --scroll-fade-s: var(--_scroll-fade-size-s); } }

.scroll-fade-r {
  --_scroll-fade-size-e: var(--scroll-fade-e-size, var(--scroll-fade-size, min(12%, 40px)));
  --scroll-fade-mask: linear-gradient(to right, #000 0, #000 calc(100% - var(--scroll-fade-e, 0px)), transparent 100%);
  -webkit-mask-image: var(--scroll-fade-mask); mask-image: var(--scroll-fade-mask);
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
}
@supports not (animation-timeline: scroll()) { .scroll-fade-r { --scroll-fade-e: var(--_scroll-fade-size-e); } }

.scroll-fade-none { --scroll-fade-mask: none; }

/* ---- shimmer ---- */
@property --shimmer-angle { syntax: "<angle>"; inherits: true; initial-value: 20deg; }
@property --shimmer-image { syntax: "*"; inherits: false; }
@property --shimmer-text-fill { syntax: "*"; inherits: false; }

@keyframes tw-shimmer {
  from { background-position: 100% 0; }
  to   { background-position: 0 0; }
}

.shimmer {
  --_spread: var(--shimmer-spread, calc(3ch + 40px));
  --_base: currentColor;
  --_highlight: var(--shimmer-color, color-mix(in oklch, currentColor 20%, transparent));
  background-image: var(--shimmer-image, linear-gradient(
    calc(90deg + var(--shimmer-angle)),
    var(--_base) calc(50% - var(--_spread)),
    color-mix(in oklch, var(--_highlight), var(--_base) 50%) calc(50% - var(--_spread) * 0.5),
    var(--_highlight) 50%,
    color-mix(in oklch, var(--_highlight), var(--_base) 50%) calc(50% + var(--_spread) * 0.5),
    var(--_base) calc(50% + var(--_spread))
  ));
  background-repeat: no-repeat;
  background-size: calc(200% + var(--_spread) * 2) 100%;
  background-position: 0 0;
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: var(--shimmer-text-fill, transparent);
  animation: tw-shimmer var(--shimmer-duration, 2s) linear infinite;
}
.dark .shimmer {
  --_highlight: var(--shimmer-color, color-mix(in oklch, currentColor 60%, white 20%));
}
.shimmer:where([dir="rtl"], [dir="rtl"] *) { animation-direction: reverse; }

.shimmer-once     { animation-iteration-count: 1; }
.shimmer-reverse  { animation-direction: reverse; }
.shimmer-none     { --shimmer-image: none; --shimmer-text-fill: currentColor; }

@media (prefers-reduced-motion: reduce) {
  .shimmer { animation: none; background-image: none; -webkit-text-fill-color: currentColor; }
}
