/* ===== Polices ===================================================== Inter (interface) + JetBrains Mono (chiffres, chemins, code). Servies par Loki (/fonts/, embarquées dans le binaire) et NON par Google Fonts : une instance locale ou coupée d'internet doit s'afficher correctement, sans qu'un tiers apprenne qui consulte l'interface. Sous-ensemble LATIN uniquement (~48 + ~31 Ko) : il couvre le français, y compris œ et les guillemets. Les symboles de l'UI (↻ ⌫ ☰ 🗑) n'ont jamais été dessinés par la police d'interface — ils viennent du système, ici comme avant. swap : le texte s'affiche tout de suite dans la police de repli, puis bascule — jamais de blanc au chargement. */ @font-face{font-family:'Inter';src:url('/fonts/inter.woff2') format('woff2');font-weight:100 900;font-display:swap;font-style:normal} @font-face{font-family:'JetBrains Mono';src:url('/fonts/jetbrainsmono.woff2') format('woff2');font-weight:100 800;font-display:swap;font-style:normal} /* ===== Palette « Sober Tech » ====================================== Ardoise et sauge. Deux variantes — claire par défaut (celle de la maquette), sombre « Deep Dark » via data-theme="dark" sur . Toutes les couleurs de l'UI passent par ces variables ; rien d'autre ne code une couleur en dur. Rôles : --accent = sauge (sélection, marche, liens, bulle utilisateur) · --warn = ambre (chiffres, mesures, avertissements) · --err = rouille (arrêt, danger). --row-bg / --code-bg sont les deux fonds encastrés d'un panneau. --on-accent : ce qui s'écrit SUR un aplat de sauge. Il change avec la variante — blanc sur la sauge foncée du thème clair, ardoise sur la sauge claire du thème sombre — sinon un des deux thèmes écrit du texte à 2:1 de contraste sur la bulle utilisateur et le bouton d'envoi. */ :root{color-scheme:light; --font:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif; --mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; --bg:#eceff0;--panel:#ffffff;--bubble:#f6f8f7;--border:rgba(30,41,59,.11); --text:#1e293b;--dim:#64748b; --accent:#5e7f5a;--accent-bg:rgba(94,127,90,.11);--on-accent:#ffffff;--on-err:#ffffff; --accent-soft:#8fab8b; --side:#fafbfa;--row-bg:#f5f7f6;--row-bg-on:#eef2f0; --ok:#5e7f5a;--warn:#a86a2a;--err:#c0503f;--mag:#a86a2a; --code-bg:#f4f6f5;--danger-bg:#faeae7;--warn-bg:#f7f0e5; --add:#5e7f5a;--add-bg:rgba(94,127,90,.11);--del:#c0503f;--del-bg:rgba(192,80,63,.12); --shadow:0 1px 2px rgba(15,23,42,.05);--shadow-lift:0 4px 16px rgba(15,23,42,.08); --shadow-card:0 2px 10px rgba(15,23,42,.07),0 1px 3px rgba(15,23,42,.05)} /* « Deep Dark » : fond #0F172A, cartes #1E293B. */ [data-theme="dark"]{color-scheme:dark; --bg:#0f172a;--panel:#1e293b;--bubble:#1e293b;--border:rgba(148,163,184,.17); --text:#e2e8f0;--dim:#94a3b8; --accent:#8fb08c;--accent-bg:rgba(143,176,140,.16);--on-accent:#0f172a;--on-err:#0f172a; --accent-soft:#6d8f6a; --side:#0d1424;--row-bg:#172033;--row-bg-on:#243349; --ok:#8fb08c;--warn:#d9a55f;--err:#e0705c;--mag:#d9a55f; --code-bg:#131c2f;--danger-bg:#3a1f1c;--warn-bg:#33291b; --add:#8fb08c;--add-bg:rgba(143,176,140,.16);--del:#e0705c;--del-bg:rgba(224,112,92,.16); --shadow:none;--shadow-lift:0 4px 16px rgba(0,0,0,.35); --shadow-card:0 2px 12px rgba(0,0,0,.30),0 1px 3px rgba(0,0,0,.22)} /* Safari iOS peint un rectangle gris/noir sur TOUT élément touché (bouton, lien, résumé de section, bulle repliable…). C'est un retour tactile par défaut, hors de notre charte et impossible à styler : on le coupe. Nos états :active/:focus restent, eux, gérés élément par élément. */ *{box-sizing:border-box;-webkit-tap-highlight-color:transparent} /* ⚠️ overflow:hidden sur html/body est INTERDIT EN PWA (tenté puis retiré le 2026-07-28) : en PWA iOS, si le document ne peut plus défiler, WebKit décale TOUTE la mise en page à l'ouverture du clavier pour montrer le champ actif — la carte de saisie se retrouve plaquée en haut de l'écran. Le rebond élastique du document est le prix à payer ; il est cosmétique, le clavier ne l'est pas. Laisser le document défiler se paie cher, et EN PWA AUSSI : la coque y fait 956 pt (obligatoire pour que la carte atteigne le bas de l'écran) alors que la fenêtre de mise en page n'en fait que 894 → il reste 62 pt de document défilable en permanence, même avec un fil VIDE, et la carte de saisie — posée en bottom:0 dans le body — part avec au lieu de rester collée en bas. On coupe donc le défilement du document PARTOUT (2026-07-29). Le fil, lui, défile toujours dans #chat, qui borne son rebond (overscroll:contain). ⚠️ SI LE CLAVIER iOS SE REMET À TOUT DÉCALER EN PWA, c'est ce overflow:hidden qu'il faut rouvrir en premier — c'était le compromis d'origine. */ /* Hauteur de la coque : elle DIFFÈRE entre PWA et navigateur, il faut les deux. - PWA (écran d'accueil) : 100vh, et SURTOUT ni 100dvh ni 100%. Mesuré sur iPhone (2026-07-28) : la vue web couvre tout l'écran (956 pt — le contenu passe bien sous l'horloge), mais la fenêtre de mise en page ne fait que 894 pt et reste calée en haut. Or 100dvh ET 100% valent tous deux 894 → il restait une bande morte de 62 pt (= la zone sûre du haut) en bas de l'écran, sous la carte de saisie. 100vh se réfère au GRAND viewport (956) : la seule unité juste dans ce cas. - Navigateur (barre d'URL + barre d'outils visibles) : 100vh vaut justement ce GRAND viewport, donc la coque dépasse la zone réellement visible → le document défile alors que le fil est vide, et le bas de la carte de saisie (modèle / contexte) est coupé. Ici c'est 100dvh qu'il faut : la hauteur visible du moment. D'où : 100dvh par défaut, et 100vh UNIQUEMENT en PWA. ⚠️ Le tri se fait sur l'attribut html[data-pwa] (pose par le script du ), PAS sur le media `display-mode:standalone` : WebKit adosse ce media au `display` du MANIFESTE, or cette app n'en a pas (elle vit en apple-mobile-web-app-capable). En PWA iOS il repond donc `browser`, la branche navigateur s'appliquait, et la bande morte de 62 pt sous la carte de saisie etait de retour (constate le 2026-07-29). ⚠️ Ne pas y ajouter overflow:hidden : voir plus haut, ça casse le clavier iOS. */ body{margin:0;font-family:var(--font);background:var(--bg);color:var(--text);height:100vh;height:100dvh;overscroll-behavior:none;display:flex;font-size:14px} html[data-pwa="1"] body{height:100vh} /* Seul #chat défile ; le document, lui, est figé (cf. l'avertissement ci-dessus). Exception PWA : on lui rend son défilement, sinon le clavier iOS casse tout. */ html,body{overflow:hidden} /* PWA plein écran (viewport-fit=cover) : on décale le contenu des bords pour ne pas passer sous l'encoche / la barre système / l'indicateur d'accueil. Les env(safe-area-inset-*) valent 0 hors PWA, donc aucun impact ailleurs. ⚠️ PAS de padding-bas ici : le composer est posé en bottom:0 DANS le body, donc une marge basse le décollerait du bord de l'écran et laisserait une bande de fond visible sous la carte (visible seulement en PWA, où l'inset vaut ~34 px). L'indicateur d'accueil est esquivé bloc par bloc, en padding INTERNE. */ body{padding:env(safe-area-inset-top) env(safe-area-inset-right) 0 env(safe-area-inset-left)} /* Listes déroulantes : la LISTE ouverte n'hérite pas du fond de son est posé par-dessus, piste transparente : sinon on voyait deux barres superposées (la barre de proportion + la piste blanche native du navigateur). Deux teintes du MÊME accent, pas deux couleurs — la barre exprime une proportion, pas deux catégories. */ .gpu-split{position:relative;display:flex;align-items:center;height:22px} .gpu-bar{display:flex;width:100%;height:10px;border-radius:999px;overflow:hidden;background:color-mix(in srgb,var(--dim) 20%,transparent)} .gpu-bar>span{background:var(--accent);flex:none;min-width:0;transition:width .12s} .gpu-bar>span.alt{background:color-mix(in srgb,var(--accent) 42%,transparent)} .gpu-range{position:absolute;inset:0;width:100%;height:22px;margin:0;padding:0;background:transparent;-webkit-appearance:none;appearance:none;cursor:ew-resize} .gpu-range:focus{outline:none} .gpu-range::-webkit-slider-runnable-track{height:22px;background:transparent;border:none} .gpu-range::-moz-range-track{height:22px;background:transparent;border:none} .gpu-range::-moz-range-progress{background:transparent} .gpu-range::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:16px;height:16px;margin-top:3px;border-radius:50%;background:var(--bg);border:2px solid var(--accent);box-shadow:0 1px 3px rgba(0,0,0,.3);cursor:ew-resize} .gpu-range::-moz-range-thumb{width:16px;height:16px;border-radius:50%;background:var(--bg);border:2px solid var(--accent);box-shadow:0 1px 3px rgba(0,0,0,.3);cursor:ew-resize} .gpu-range:focus-visible::-webkit-slider-thumb{box-shadow:0 0 0 3px var(--accent-bg)} .gpu-range:focus-visible::-moz-range-thumb{box-shadow:0 0 0 3px var(--accent-bg)} .gpu-legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:11.5px;color:var(--dim)} .gpu-legend i{display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:5px} .gpu-legend i.alt{background:color-mix(in srgb,var(--accent) 42%,transparent)} .gpu-shares{display:flex;flex-wrap:wrap;gap:10px} .gpu-share{display:flex;align-items:center;gap:5px;font-size:12px;color:var(--dim)} .gpu-share input{width:62px;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:7px;padding:5px 7px;font:inherit;text-align:right} .gpu-share input:focus{outline:none;border-color:var(--accent)} .pe-inp{width:100%;box-sizing:border-box;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:8px;padding:9px 11px;font:inherit;transition:border-color .12s,box-shadow .12s} .pe-inp:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-bg)} .pe-row.stack.plain .pe-inp{border:none;background:transparent;padding:6px 0;font-size:15px} .pe-row.stack.plain .pe-inp:focus{box-shadow:none} /* Champ + bouton accolé (téléchargement) — même trait que les autres champs, sans pastille colorée qui ressort. */ .pe-inpbtn{display:flex;align-items:stretch} .pe-inpbtn .pe-inp{border-top-right-radius:0;border-bottom-right-radius:0;border-right:0} /* Le bouton reprend EXACTEMENT le cadre du champ (mêmes coins, même bordure) : sans ça, un thème qui arrondit tous les boutons (ChatGPT) le détachait du champ, avec un coin rond au milieu de la paire. */ .pe-inpbtn button{flex:none;margin:0;border:1px solid var(--border);border-radius:0 8px 8px 0;background:var(--panel);color:var(--dim);padding:0 15px;cursor:pointer;font-size:15px;transition:color .12s,border-color .12s,background .12s} .pe-inpbtn button:hover:not(:disabled){color:var(--bg);background:var(--accent);border-color:var(--accent)} .pe-inpbtn:focus-within .pe-inp,.pe-inpbtn:focus-within button{border-color:var(--accent)} .pe-inpbtn button:disabled{opacity:.5;cursor:default} /* Contrôle segmenté (moteur) façon iOS. */ .pe-seg{display:flex;gap:3px;background:color-mix(in srgb,var(--dim) 16%,transparent);border-radius:9px;padding:3px;width:100%} .pe-seg>label{flex:1;text-align:center;font-size:13px;font-weight:500;padding:7px 4px;border-radius:7px;cursor:pointer;color:var(--dim);transition:background .12s,color .12s} .pe-seg>label:has(input:checked){background:var(--panel);color:var(--text);box-shadow:0 1px 2px rgba(0,0,0,.22)} .pe-seg input{display:none} .pe-seg .be-note{display:none} .pe-seg-note{font-size:11px;color:var(--dim);padding:0 2px;min-height:14px} .pe-note{font-size:11px;color:var(--dim);line-height:1.45} /* Barre de progression (téléchargement de modèle). Sans total connu, elle défile en boucle via .indet. */ .pe-bar{height:4px;border-radius:999px;background:color-mix(in srgb,var(--dim) 26%,transparent);overflow:hidden} .pe-bar>i{display:block;height:100%;width:0;border-radius:999px;background:var(--accent);transition:width .35s linear} .pe-bar.indet>i{width:35%;animation:pe-bar-slide 1.2s ease-in-out infinite;transition:none} .pe-bar.done>i{width:100%} @keyframes pe-bar-slide{0%{margin-left:-35%}100%{margin-left:100%}} /* Action discrète en texte (annuler un téléchargement…). */ .pe-link{align-self:flex-start;border:none;background:none;padding:0;font:inherit;font-size:11px;color:var(--dim);text-decoration:underline;cursor:pointer} .pe-link:hover{color:var(--text)} .pe-link:disabled{opacity:.5;cursor:default;text-decoration:none} /* Interrupteur à glissière. */ .pe-switch{position:relative;flex:none;width:40px;height:24px} .pe-switch input{position:absolute;opacity:0;width:0;height:0} .pe-sl{position:absolute;inset:0;background:color-mix(in srgb,var(--dim) 55%,transparent);border-radius:999px;transition:background .18s} .pe-sl::before{content:"";position:absolute;width:18px;height:18px;left:3px;top:3px;background:#fff;border-radius:50%;transition:transform .18s;box-shadow:0 1px 3px rgba(0,0,0,.25)} .pe-switch input:checked+.pe-sl{background:var(--accent)} .pe-switch input:checked+.pe-sl::before{transform:translateX(16px);background:var(--bg)} .pe-switch input:focus-visible+.pe-sl{box-shadow:0 0 0 3px var(--accent-bg)} /* Repli « avancé » / config brute : une ligne cliquable dans un groupe. */ .pe-adv{background:var(--panel);border:1px solid var(--border);border-radius:12px;overflow:hidden} .pe-adv>summary{cursor:pointer;font-size:14px;color:var(--text);padding:13px 15px;list-style:none;display:flex;align-items:center;user-select:none} .pe-adv>summary::-webkit-details-marker{display:none} .pe-adv>summary::after{content:"";margin-left:auto;width:7px;height:7px;border-right:1.5px solid var(--dim);border-bottom:1.5px solid var(--dim);transform:translateY(-2px) rotate(45deg);transition:transform .15s} .pe-adv[open]>summary::after{transform:translateY(1px) rotate(-135deg)} .pe-adv[open]>summary{border-bottom:1px solid var(--border)} .pe-adv .pe-adv-body{padding:14px 15px;display:flex;flex-direction:column;gap:10px} .pe-adv textarea.pe-inp{min-height:210px;resize:vertical;line-height:1.5;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;white-space:pre-wrap;overflow-wrap:anywhere;overflow-x:hidden} /* Ligne « éditer le .env » : chevron de disclosure + textarea de config brute. */ .pe-caret{display:inline-block;width:7px;height:7px;border-right:1.5px solid var(--dim);border-bottom:1.5px solid var(--dim);transform:rotate(-45deg);transition:transform .15s} .pe-caret.open{transform:rotate(45deg)} #m-content{min-height:220px;resize:vertical;line-height:1.5;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;white-space:pre-wrap;overflow-wrap:anywhere;overflow-x:hidden} /* Nombres avancés : chaque paire label/valeur sur sa propre ligne (réutilise pe-row). */ .pe-adv .pe-list{margin:0} /* Variante « champ en clair » : page mémoire → le contenu est le champ principal. */ .pe-adv.pe-raw-plain{border:none;background:transparent} .pe-adv.pe-raw-plain>summary{display:none} .pe-adv.pe-raw-plain .pe-adv-body{padding:0} /* Pied de modale. */ .pe-foot{padding:9px 12px;border-top:1px solid var(--border);display:flex;justify-content:space-between; align-items:center;gap:8px;flex-wrap:nowrap} .pe-foot .foot-l{display:flex;align-items:center;gap:8px;min-width:0} .pe-foot .foot-r{display:flex;gap:8px;flex:none} .pe-btn{margin:0;padding:7px 15px;font-size:12.5px;border-radius:8px;font-weight:600;cursor:pointer; border:1px solid var(--border);background:var(--panel);color:var(--text);white-space:nowrap} .pe-btn:hover{border-color:var(--dim)} .pe-btn.primary{border-color:var(--accent);background:var(--accent);color:var(--bg)} .pe-btn.primary:hover{filter:brightness(1.08)} /* Suppression : icône seule, en retrait. Un bouton plein « supprimer » pesait autant que « enregistrer » alors que c'est l'action rare — et à trois boutons larges, le pied passait à deux lignes sur téléphone. */ .pe-del{flex:none;display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px; margin:0;padding:0;border:1px solid transparent;border-radius:8px;background:transparent;color:var(--err);cursor:pointer} .pe-del svg{width:15px;height:15px;display:block} .pe-del:hover{background:var(--danger-bg);border-color:color-mix(in srgb,var(--err) 40%,transparent)} /* Option d'une confirmation : une PASTILLE à cocher, pas une case système suivie d'un bout de texte. Éteinte : contour discret. Allumée : elle passe au rouge, comme l'action destructrice qu'elle étend. Vit dans la boîte de confirmation (#ask-check), plus dans le pied de l'éditeur. */ .pe-delmodel{align-self:flex-start;margin-top:14px;display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:600; letter-spacing:.02em;color:var(--dim);cursor:pointer;white-space:nowrap; padding:5px 11px;border:1px solid var(--border);border-radius:999px;background:var(--panel); transition:color .12s,border-color .12s,background .12s} .pe-delmodel input{position:absolute;opacity:0;width:0;height:0} /* Le point tient lieu de case à cocher : creux au repos, plein une fois coché. */ .pe-delmodel::before{content:"";width:6px;height:6px;border-radius:50%;flex:none; border:1px solid currentColor;background:transparent} .pe-delmodel:hover{color:var(--text);border-color:var(--dim)} .pe-delmodel:has(input:checked){color:var(--err);border-color:color-mix(in srgb,var(--err) 45%,transparent);background:var(--danger-bg)} .pe-delmodel:has(input:checked)::before{background:currentColor} /* Éditeur de preset/mémoire — mise en forme propre du modale */ .modal-ov{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:30;align-items:center;justify-content:center;padding:20px} /* Ouverture/fermeture animées. L'état par défaut est l'état FERMÉ ; la classe .show (posée par showModal/hideModal) fait jouer la transition dans les deux sens — une @keyframes n'aurait animé que l'ouverture. */ .modal-ov,.ask-overlay{opacity:0;transition:opacity .16s ease} .modal-ov.show,.ask-overlay.show{opacity:1} .modal-card,.ask-box{transform:translateY(10px) scale(.985);opacity:0; transition:transform .18s cubic-bezier(.2,.8,.3,1),opacity .18s ease} .modal-ov.show .modal-card,.ask-overlay.show .ask-box{transform:none;opacity:1} @media (prefers-reduced-motion:reduce){ .modal-ov,.ask-overlay,.modal-card,.ask-box{transition:none} } .modal-card{position:relative;background:var(--bg);border:1px solid var(--border);border-radius:12px;width:100%;max-width:600px;max-height:90vh;display:flex;flex-direction:column;box-shadow:0 16px 50px rgba(0,0,0,.18)} .modal-head{padding:14px 16px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center} .modal-head strong{font-size:14px} /* Croix de fermeture : un CARRÉ, pas un rectangle. Avec un padding asymétrique (3px vertical / 10px horizontal) le glyphe paraissait écrasé et la cible de clic était basse. 32px, glyphe centré, taille de cible correcte au doigt. */ .modal-x{margin:0;padding:0;width:32px;height:32px;flex:none;display:inline-flex;align-items:center;justify-content:center;font-size:19px;line-height:1;color:var(--dim)} .modal-body{padding:16px;overflow:auto;flex:1} /* Modale ouverte mais pas encore remplie : on affiche le voile de chargement À LA PLACE du formulaire et du pied. Estomper le formulaire ne suffisait pas — on voyait quand même les champs s'écrire un par un derrière le voile. */ /* Voile OPAQUE, posé sous l'en-tête (le titre reste lisible) : `visibility:hidden` sur le formulaire suffirait en théorie, mais un fond plein garantit qu'aucun reste du preset précédent (moteur coché, réglages) ne puisse apparaître une fraction de seconde. --mh = hauteur mesurée de l'en-tête, posée à l'ouverture. */ /* ⚠️ Aucune transition tant que le formulaire est caché, NI pendant la frame où il réapparaît : WebKit diffère les transitions des éléments non rendus et les joue à leur retour à l'écran. Le sélecteur de moteur glissait donc de l'ancien preset au nouveau SOUS LES YEUX, une fois le chargement terminé. */ .modal-ov.loading .pe-body *,.modal-ov.no-anim .pe-body *{transition:none!important} .modal-load{display:none;position:absolute;inset:var(--mh,52px) 0 0 0;z-index:2; align-items:center;justify-content:center;background:var(--bg);border-radius:0 0 12px 12px} .modal-ov.loading .modal-load{display:flex} /* visibility (et non display) : le formulaire garde sa place, donc la carte a déjà sa taille finale — la révéler ne provoque plus d'à-coup de hauteur. */ .modal-ov.loading .modal-body, .modal-ov.loading .pe-body, .modal-ov.loading .pe-foot, .modal-ov.loading .modal-foot{visibility:hidden} /* Rond qui tourne — les trois points sont la signature du CHAT, pas des modales. */ .spinner{width:22px;height:22px;border-radius:50%;border:2px solid var(--border); border-top-color:var(--dim);animation:spin .7s linear infinite} @keyframes spin{to{transform:rotate(360deg)}} @media (prefers-reduced-motion:reduce){.spinner{animation-duration:2s}} .modal-foot{padding:12px 16px;border-top:1px solid var(--border);display:flex;justify-content:space-between;align-items:center;gap:8px;flex-wrap:wrap} .foot-l{display:flex;align-items:center;gap:10px;flex-wrap:wrap} .foot-r{display:flex;gap:8px} .mform{display:flex;flex-direction:column;gap:14px} .fld{display:flex;flex-direction:column;gap:5px} .fld-l{font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--dim)} .fld-l .opt{text-transform:none;letter-spacing:0;font-weight:400;opacity:.65} .fld-hint{font-size:11px;color:var(--dim);margin-top:2px} .ctl{width:100%;box-sizing:border-box;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:7px;padding:9px 11px;font:inherit} .ctl:focus{outline:none;border-color:var(--accent)} .ta{min-height:220px;resize:vertical;line-height:1.5} .card{border:1px solid var(--border);border-radius:10px;padding:13px;display:flex;flex-direction:column;gap:13px} .card-h{font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--accent)} .row{display:flex;gap:6px} .row .ctl{flex:1;min-width:0} .btn-dl{flex-shrink:0;margin:0} .input-btn{display:flex} .input-btn .ctl{flex:1;min-width:0;border-top-right-radius:0;border-bottom-right-radius:0} .input-btn .btn-in{flex-shrink:0;margin:0;padding:9px 13px;border-left:0;border-top-left-radius:0;border-bottom-left-radius:0} .input-btn .btn-in:hover{border-left:0} .seg{display:flex;gap:6px} .seg>label{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;text-align:center;min-height:46px;padding:6px 4px;border:1px solid var(--border);border-radius:8px;cursor:pointer;font-size:12px;background:var(--panel);transition:border-color .12s,background .12s,color .12s} .seg>label:hover{border-color:var(--accent)} .seg>label:has(input:checked){border-color:var(--accent);background:var(--accent-bg);color:var(--accent)} .seg input{display:none} .seg .be-note{font-size:10px;color:var(--dim);margin:0} .btn-danger{background:var(--danger-bg);border-color:var(--err);color:var(--err);margin:0} .btn-save{border-color:var(--accent);color:var(--accent);margin:0} .del-model{align-items:center;gap:5px;font-size:11px;color:var(--dim)} .del-model input{margin:0} .lc-logbar{display:flex;align-items:center;gap:6px;cursor:pointer;user-select:none;font-size:12px;color:var(--dim);font-weight:600;padding:6px 0;margin-top:2px} .lc-logbar #lc-log-caret{font-size:9px;transition:transform .12s;display:inline-block} .lc-logbar.open #lc-log-caret{transform:rotate(90deg)} #lc-log{background:var(--panel);border:1px solid var(--border);border-radius:6px;padding:8px;font-size:10px;line-height:1.45;max-height:220px;overflow:auto;white-space:pre-wrap;word-break:break-all;margin:0} .lc-spin{display:inline-block;animation:spin 1s linear infinite} /* Serveurs MCP */ .mcp-list{display:flex;flex-direction:column;gap:4px} .mcp-row{display:flex;align-items:center;gap:8px;padding:6px 10px;border:1px solid var(--border);border-radius:6px;background:var(--panel);cursor:pointer} .mcp-row:hover{border-color:var(--accent)} .mcp-row.off{opacity:.6} .mcp-dot{flex:none;width:8px;height:8px;border-radius:50%} .mcp-dot-ok{background:var(--accent)} .mcp-dot-err{background:var(--err)} .mcp-dot-off{background:var(--dim)} .mcp-info{flex:1;min-width:0} .mcp-name{font-size:12px;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .mcp-meta{display:flex;align-items:center;gap:6px;margin-top:2px} .mcp-tag{font-size:10px;padding:1px 7px;border-radius:9px;border:1px solid var(--border);color:var(--dim);text-transform:uppercase;letter-spacing:.04em} .mcp-tools{font-size:10px;color:var(--accent)} .mcp-err{font-size:10px;color:var(--err)} .mcp-switch{flex:none} .mcp-edit{flex:none;padding:2px 5px;font-size:13px;line-height:1;background:transparent;border:none;opacity:.5} .mcp-edit:hover{opacity:1;color:var(--accent)} /* Liste des outils MCP dans la modale */ .mcp-tools-list{display:flex;flex-direction:column;gap:2px;max-height:240px;overflow:auto;border:1px solid var(--border);border-radius:6px;padding:6px} .mcp-tool-row{display:flex;align-items:center;gap:10px;padding:4px 6px;border-radius:5px;cursor:pointer} .mcp-tool-row:hover{background:var(--panel)} .mcp-tool-switch{flex:none;transform:scale(.85)} .mcp-tool-name{font-size:12px;color:var(--text);word-break:break-all} /* Accès OpenAI / Accès distant : champs lecture seule (URL, clé) + libellés compacts, pour une présentation homogène et sans surcharge visuelle. */ .io-input{flex:1;min-width:0;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:9px;padding:8px 10px;font:inherit;font-size:13px} /* Modale MCP (ajout/édition) : les champs des groupes stdio/http n'avaient aucun espacement vertical (le conteneur n'est pas un flex à gap) → ils se touchaient. On aligne leur rythme sur celui du reste du formulaire (.mform). */ #mcp-stdio-fields,#mcp-http-fields{display:flex;flex-direction:column;gap:14px} /* ===== Mise en page de la conversation ============================= Colonne de lecture centrée, réponses SANS bulle (texte nu sur le fond), question de l'utilisateur en pastille arrondie à droite, composer en « pilule », pas d'étiquettes VOUS/Loki. Ces règles surchargent la mise en page générique plus haut : elles doivent donc rester APRÈS elle. */ body{font-size:15px} h1{color:var(--text);font-weight:600;letter-spacing:-.01em} /* Barre latérale : plus sombre/atténuée que la zone de conversation. */ .side{background:var(--panel);border-right-color:transparent} @media (max-width:720px){.side{background:var(--panel)}} /* Colonne de lecture centrée (comme le fil ChatGPT), pleine largeur sur mobile. ⚠️ JAMAIS le raccourci `padding` ici : le rembourrage BAS réserve la hauteur de la carte de saisie (voir #chat plus bas) et il est réglé ailleurs. Une règle en raccourci le remet silencieusement à sa valeur du raccourci — c'est exactement ce qui, jusqu'à ce correctif, laissait la fin du fil coincée sous la carte au-delà de 721 px de large. Longhands uniquement. */ #chat{padding-top:28px;padding-inline:max(16px,calc(50% - 380px));gap:22px;scrollbar-gutter:stable both-edges} /* Réponses de l'assistant : pas de cadre, pas de fond — juste du texte. */ #chat .msg.assistant{background:transparent;border:none;padding:0;max-width:100%;line-height:1.7} /* Question : pastille arrondie à droite, texte lisible sur fond clair OU sombre. */ #chat .msg.user{background:var(--bubble);color:var(--text);border-radius:20px;padding:11px 18px;max-width:75%;line-height:1.6} #chat .msg.user .label{color:var(--dim)} /* Libellés : l'amont les masquait tous ici, les mots « user » / « assistant » n'ajoutant rien à l'alignement. Ils portent désormais un avatar et un prénom (voir 17-identity.js), qui eux identifient vraiment l'interlocuteur — on les réaffiche donc, en ligne et discrets. Restent masqués les libellés SANS avatar, c'est-à-dire les bulles techniques non repliables. */ #chat .msg .label{display:none} #chat .msg.collapsible .label{display:block} #chat .msg.user .label,#chat .msg.assistant .label{display:flex;align-items:center;gap:0;opacity:.85} /* Côté question, le libellé s'aligne à droite comme la bulle. */ #chat .msg.user .label{justify-content:flex-end} /* Mesures de génération sous la réponse (prefill / decode). Assez discrètes pour ne pas concurrencer le texte, assez lisibles pour être utiles. Escamotables depuis Apparence → Affichage. */ #chat .msg .statline{margin-top:10px;font-family:var(--mono);font-size:10.5px;letter-spacing:.02em; color:var(--dim);opacity:.75} #chat .msg .statline:empty{display:none} html[data-hide-stats="1"] #chat .msg .statline{display:none} /* Arrivée en fondu montant plutôt qu'un surgissement sec. L'opacité finale (.75) est celle de la règle ci-dessus : l'animation doit y ATTERRIR, sinon la ligne clignote en repassant à sa valeur de repos. */ #chat .msg .statline-in{animation:statin .28s cubic-bezier(.2,.7,.3,1) both} @keyframes statin{from{opacity:0;transform:translateY(3px)}to{opacity:.75;transform:none}} @media (prefers-reduced-motion:reduce){#chat .msg .statline-in{animation:none}} /* Raisonnement / outils : encart gris discret plutôt qu'un cadre pointillé. */ #chat .msg.reasoning,#chat .msg.tool{background:var(--panel);border:1px solid var(--border);border-radius:14px;color:var(--dim);padding:12px 16px} #chat .msg.reasoning{color:var(--dim)} #chat .msg.tool .tool-head{color:var(--text)} #chat .msg.assistant .body pre,#chat .msg.reasoning .body pre,#chat .msg.tool pre{border-radius:12px;padding:12px 14px} #chat .msg.assistant .body a{text-decoration:underline;text-underline-offset:2px} /* Composer = UNE seule carte posée PAR-DESSUS le fil : la conversation continue de défiler DERRIÈRE elle et s'efface en fondu (dégradé vers le fond) au lieu de s'arrêter net au-dessus. Dans la carte, de haut en bas : la saisie et ses boutons, un pied discret (préréglage · contexte · compacter), puis la jauge de contexte qui FORME le bord bas — elle épouse les coins arrondis (overflow:hidden sur le rayon). Les trois blocs sont des frères dans le HTML : on les recolle en dessinant les bordures par morceaux et l'ordre visuel vient de l'ordre flex — aucun changement de markup. */ /* Le padding BAS dégage l'indicateur d'accueil de l'iPhone en PWA : la carte flotte au-dessus de lui, son arrondi bas reste donc bien visible. Hors PWA l'inset vaut 0 → strictement les 8 px d'origine. */ /* --fade = la bande transparente au-dessus de la carte, celle où le fil doit s'effacer. Le dégradé s'exprime dans la MÊME unité (et non en % de la hauteur totale du composeur, qui varie) : l'opacité est donc pleine exactement au bord haut de la carte. Avec un %, le fondu finissait SOUS ce bord et le texte restait lisible au ras de la carte — l'impression de « passer derrière ». */ #composer{position:absolute;left:0;right:0;bottom:0;z-index:4;border-top:none;display:flex;flex-direction:column;--fade:34px;padding:var(--fade) 0 calc(8px + env(safe-area-inset-bottom));--col:calc(max(16px,calc(50% - 380px)) + var(--sbw,0px));background:linear-gradient(to bottom,transparent 0,var(--bg) var(--fade))} /* Réserve sous le fil = hauteur RÉELLE du composeur, mesurée et publiée par syncComposer() (08-chat-render.js). Une constante redevient fausse dès que la saisie grandit ou qu'une pièce jointe s'affiche, et la fin du fil repasse hors d'atteinte. 170px n'est que le repli du tout premier rendu, avant mesure. Pas de env(safe-area-inset-bottom) ici : il est déjà DANS le padding bas du composeur, donc dans sa hauteur mesurée — le compter deux fois creusait un trou en PWA iOS. */ #chat{padding-bottom:calc(var(--composer-h,170px) + 12px)} #scrollbtn{bottom:calc(var(--composer-h,170px) + 10px);border-radius:999px} #inputbar{order:1;margin:0 var(--col);padding:8px 8px 8px 16px;gap:8px;align-items:flex-end;background:var(--panel);border:1px solid var(--border);border-bottom:none;border-radius:26px 26px 0 0} #input{background:transparent;border:none;border-radius:0;padding:9px 0;min-height:36px;line-height:18px;max-height:160px} /* Pas de cadre qui s'assombrit au focus : la carte garde exactement la même bordure, seule la saisie change (curseur). */ #input:focus{border-color:transparent} #send,#stop{border-radius:999px;height:36px;min-height:36px;padding:0 18px;margin:0} #send{background:var(--text);color:var(--bg);border-color:var(--text);font-weight:600} #send:hover:not(:disabled){opacity:.85;color:var(--bg);border-color:var(--text)} /* Pied de carte : c'est LUI qui porte l'arrondi bas (26 px, comme en haut) et qui clippe la jauge. Un bandeau de 7 px ne peut pas dessiner un rayon de 26 px (le navigateur l'écrase, d'où les coins bizarres) : on laisse donc la carte découper la jauge dans sa propre forme — la barre est bien épaisse ET rognée exactement sur la courbe, symétrique du haut. */ #ctxmeta{order:2;margin:0 var(--col);padding:6px 16px 15px;gap:10px;background:var(--panel);border:1px solid var(--border);border-top:none;border-radius:0 0 26px 26px;overflow:hidden} /* ⚠️ NE PAS piloter la position du composer en JS quand le clavier s'ouvre (tenté puis retiré le 2026-07-28, deux fois) : ni asservir la hauteur de la coque à visualViewport.height, ni décaler le composer de la hauteur du clavier. Dans les deux cas la carte part en haut de l'écran en PWA iOS. iOS gère déjà correctement le clavier tout seul — on le laisse faire. */ #ctx-text,#status-preset{color:var(--dim);font-size:10px;letter-spacing:.02em;white-space:nowrap} #ctx-compact{border-radius:999px;padding:2px 10px;font-size:10px;background:transparent;border-color:var(--border);color:var(--dim)} #ctx-compact:hover{border-color:var(--dim);color:var(--text)} /* Jauge = bord bas de la carte : épaisse, plaquée au ras du bord, rognée par l'arrondi du pied de carte. */ #ctxtrack{top:auto;bottom:0;height:7px;background:var(--border)} #ctx-fill{background:var(--text);opacity:.75} #sendhint{order:4;padding:12px var(--col) 2px} /* ===== Pièces jointes ============================================== Un fichier joint n'est jamais lu par la page : il est déposé dans le dossier de travail de l'IA et seul son nom revient. L'affichage se limite donc à une pastille — pas d'aperçu, pas de vignette, rien qui promette plus que ça. Le trombone vit dans le PIED de la carte, à droite avec le décompte de contexte et « compacter ». Il est à LEUR échelle (une ligne d'outils de 10 px), pas à celle de « send » : joindre un fichier est un geste occasionnel. */ #attach{width:18px;height:18px;min-height:18px;flex:none;margin:0;padding:0; display:flex;align-items:center;justify-content:center;border-radius:999px; background:transparent;border:1px solid transparent;color:var(--dim)} #attach:hover{background:var(--row-bg-on);border-color:transparent;color:var(--text)} #attach:active{transform:scale(.9)} #attach svg{display:block} /* Fichiers en attente : de petites bulles POSÉES AU-DESSUS de la carte. Le composeur réserve déjà 34 px transparents au-dessus de lui (la bande de dégradé qui fond vers le fil) : les pastilles s'y logent, et on retire d'autant le rembourrage. La carte ne bouge donc pas — c'est la bande vide qui se remplit, au lieu du composeur qui grandit vers le haut. Elles ne vont PAS dans le pied : cette ligne appartient au nom du modèle. */ #attach-list{order:0;display:none;gap:6px;margin:0 var(--col);padding:0 2px 8px; overflow-x:auto;scrollbar-width:none} #attach-list::-webkit-scrollbar{display:none} #attach-list.show{display:flex} #composer:has(#attach-list.show){--fade:6px} /* Posées sur le fil (et non sur la carte) : il leur faut un fond opaque et un cadre, sinon le texte de la conversation transparaît derrière. */ #attach-list .chip-file{flex:0 0 auto;background:var(--panel);border-color:var(--border); box-shadow:0 2px 10px rgba(0,0,0,.10)} /* Pastille de fichier. Le × n'apparaît qu'au survol : au repos la pastille doit rester lisible d'un coup d'œil (nom + poids), pas encombrée d'un bouton. */ .chip-file{display:flex;align-items:center;gap:8px;max-width:230px;min-width:0; padding:4px 12px;border-radius:999px;background:var(--row-bg-on); border:1px solid transparent;font-size:12px;line-height:1.3} /* Avec un × à droite, la pastille se ferme SUR lui : le rembourrage de droite revient à l'épaisseur du rond, sinon le bouton flotte loin du bord et la pastille paraît de travers. */ .chip-file:has(button){padding-right:4px} .chip-file .cf-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .chip-file .cf-size{flex:none;color:var(--dim);font-size:11px} /* Le × est TOUJOURS visible : le révéler au survol le rendait inatteignable sur mobile, où il n'y a pas de survol — donc impossible de retirer un fichier. */ .chip-file button{flex:none;margin:0;padding:0;width:20px;height:20px;font-size:14px; display:flex;align-items:center;justify-content:center;line-height:1; border-radius:50%;border:none;background:transparent;color:var(--dim)} .chip-file button:hover{background:var(--bubble);color:var(--text)} /* Transfert en cours : un liseré qui tourne, plutôt qu'une pastille grisée dont on ne sait pas si elle travaille ou si elle a échoué. */ .chip-file.up .cf-size::after{content:"";display:inline-block;vertical-align:-2px; margin-left:7px;width:10px;height:10px;border-radius:50%; border:1.5px solid var(--border);border-top-color:var(--text);animation:spin .7s linear infinite} .chip-file.err{background:var(--danger-bg);color:var(--err)} .chip-file.err .cf-size{color:var(--err)} @media (prefers-reduced-motion:reduce){.chip-file.up .cf-size::after{animation:none}} /* Fichiers d'un message envoyé : leur propre rangée AU-DESSUS de la bulle, alignée à droite comme elle. Rangée et bulle restent deux objets distincts, séparés par l'espacement normal du fil — les rapprocher les faisait lire comme une seule bulle difforme qui déborde vers le haut. */ #chat .msg-files{align-self:flex-end;display:flex;flex-wrap:wrap;justify-content:flex-end; gap:6px;max-width:75%} #chat .msg-files .chip-file{background:var(--bubble);max-width:100%} /* Lien de téléchargement dans une réponse : un lien normal, mais la flèche dit qu'il ramène un fichier plutôt qu'il n'ouvre une page. */ #chat .msg .filelink{cursor:pointer} #chat .msg .filelink::after{content:"↓";margin-left:5px;font-size:.9em;opacity:.7;text-decoration:none;display:inline-block} #chat .msg .filelink.busy{opacity:.55;pointer-events:none} /* Voile de dépôt. Il couvre la zone de conversation, curseur inclus : c'est la réponse à « est-ce que je peux lâcher ici ? », elle doit être sans ambiguïté. */ #dropveil{position:absolute;inset:0;z-index:20;display:none;align-items:center; justify-content:center;padding:24px;background:rgba(0,0,0,.28);pointer-events:none} #dropveil.show{display:flex} #dropveil>div{padding:22px 30px;border:2px dashed var(--border);border-radius:20px; background:var(--panel);color:var(--text);font-size:14px;box-shadow:0 8px 30px rgba(0,0,0,.18)} /* Reste de l'UI : coins arrondis et contrastes doux, dans le même esprit. */ button,.mem-search{border-radius:10px} /* Champ + bouton accolés : on ne garde l'arrondi que sur les coins extérieurs. */ .pe-inpbtn .pe-inp{border-radius:10px 0 0 10px} .pe-inpbtn button{border-radius:0 10px 10px 0} .input-btn .ctl{border-radius:10px 0 0 10px} .input-btn .btn-in{border-radius:0 10px 10px 0} .preset{border-radius:12px} /* Les presets restent des cartes (fond + bordure) : à plat, sans fond ni cadre, la liste ne se lisait plus comme des lignes distinctes. L'actif se signale par sa bordure — un simple voile d'accent (7 % de blanc sur fond noir) était invisible en mode sombre. */ .preset{background:var(--panel);border-color:var(--border)} .preset:hover{background:var(--accent-bg);border-color:var(--dim)} .preset.active{background:var(--panel);border-color:var(--text);color:var(--text)} .subhead,.lc-mode-go,.lc-mode-active-tag{color:var(--dim)} button:hover{border-color:var(--dim);color:var(--text)} /* Encart de garde-fou : c'est le serveur qui parle (limite d'appels d'outils, boucle détectée), pas le modèle. Pastille discrète plutôt qu'une erreur — la génération s'est arrêtée proprement, rien n'a échoué. */ .stopnote{display:inline-flex;align-items:center;gap:7px;margin-top:4px;padding:4px 12px; border:1px solid var(--border);border-radius:999px;background:var(--panel); color:var(--dim);font-size:12px;line-height:1.5} .stopnote::before{content:"";flex:none;width:6px;height:6px;border-radius:50%;background:var(--dim)} /* ===== Fenêtres et sélecteurs ===================================== Coins arrondis et ombre douce plutôt qu'un cadre marqué. */ .modal-card,.ask-box,.modal-box,.pe-list,.pe-adv{border-radius:12px} .modal-card,.ask-box{box-shadow:0 8px 30px rgba(0,0,0,.12)} /* Sélecteurs segmentés : noir plein quand c'est coché, pas de couleur. */ .pe-seg>label:has(input:checked),.seg>label:has(input:checked){background:var(--text);border-color:var(--text);color:var(--bg)} /* Curseur de portée de l'export. Même vocabulaire visuel que la répartition GPU (piste arrondie + pastille cerclée), mais une seule valeur : la position tout à droite vaut « toute la conversation ». Le libellé sous la piste donne le compte RÉEL d'échanges et le poids du fichier, la question qu'on se pose vraiment avant d'exporter. */ .xr{position:relative;display:flex;align-items:center;height:22px} .xr-track{width:100%;height:10px;border-radius:999px;overflow:hidden;background:color-mix(in srgb,var(--dim) 20%,transparent)} .xr-fill{height:100%;background:var(--accent);border-radius:999px;transition:width .12s} .xr-range{position:absolute;inset:0;width:100%;height:22px;margin:0;padding:0;background:transparent;-webkit-appearance:none;appearance:none;cursor:ew-resize} .xr-range:focus{outline:none} .xr-range::-webkit-slider-runnable-track{height:22px;background:transparent;border:none} .xr-range::-moz-range-track{height:22px;background:transparent;border:none} .xr-range::-moz-range-progress{background:transparent} .xr-range::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:16px;height:16px;margin-top:3px;border-radius:50%;background:var(--bg);border:2px solid var(--accent);box-shadow:0 1px 3px rgba(0,0,0,.3);cursor:ew-resize} .xr-range::-moz-range-thumb{width:16px;height:16px;border-radius:50%;background:var(--bg);border:2px solid var(--accent);box-shadow:0 1px 3px rgba(0,0,0,.3);cursor:ew-resize} .xr-range:focus-visible::-webkit-slider-thumb{box-shadow:0 0 0 3px var(--accent-bg)} .xr-range:focus-visible::-moz-range-thumb{box-shadow:0 0 0 3px var(--accent-bg)} .xr-range:disabled{cursor:default;opacity:.5} /* Conversation vide : un seul message centré à la place des réglages. */ .xempty{display:flex;flex-direction:column;align-items:center;gap:6px;padding:44px 20px;text-align:center;color:var(--text);font-size:14px} .xempty-i{font-size:30px;line-height:1;color:var(--dim);opacity:.7} /* ===== Direction « Terre » : surfaces en cartes ======================= La charte pose des CARTES arrondies séparées par une gouttière, au lieu d'une colonne à filets. Bloc en fin de feuille pour prendre le pas sur les règles de structure d'origine sans les réécrire. Bureau UNIQUEMENT : sur téléphone la barre latérale est un tiroir plein écran et le fil occupe toute la largeur — y poser des cartes ne ferait que grignoter des pixels précieux. */ @media (min-width:721px){ /* Coque : le fond « terre » se voit dans la gouttière entre les deux cartes. */ body{padding:12px;gap:12px} html[data-pwa="1"] body{padding-top:calc(12px + env(safe-area-inset-top))} /* Barre latérale : pile de cartes, sans filet ni bordure de colonne. */ .side{width:312px;border-right:0;padding:0;background:transparent; display:flex;flex-direction:column;gap:12px;overflow-y:auto;overscroll-behavior:contain} .sidecard,.side>details{background:var(--panel);border:0;border-top:0;border-radius:20px; box-shadow:var(--shadow);margin:0;padding:2px 16px} .sidecard{padding:15px 18px 14px;display:flex;flex-direction:column;gap:2px} .side>details[open]{padding-bottom:14px} .side>details>summary{height:42px} /* L'étiquette « Paramètres » n'est plus un filet mais un intertitre entre deux cartes : le fond de la coque fait déjà la séparation. */ .side-sep{border-top:0;margin:10px 0 0;padding:0 6px} /* Blocs encastrés DANS une carte : un ton plus chaud, sans bordure — deux cadres imbriqués alourdiraient inutilement. */ .side .slist{background:var(--row-bg);border-color:transparent;border-radius:14px} /* Colonne principale : une seule grande carte qui contient fil et saisie. */ .main{background:var(--panel);border-radius:20px;box-shadow:var(--shadow);overflow:hidden} #chat{padding-top:26px;padding-inline:max(20px,calc(50% - 360px))} /* Le dégradé qui fait disparaître le fil sous la carte de saisie doit fondre vers le PANNEAU, pas vers le fond de la coque. */ #composer{background:linear-gradient(to bottom,transparent 0,var(--panel) var(--fade))} /* Carte de saisie : posée SUR le panneau, donc un ton au-dessus, et sans bordure — c'est l'élévation qui la détache. */ #inputbar,#ctxmeta{background:var(--row-bg-on);border-color:transparent} #input{background:transparent} } /* --- Terre : densité et accents ------------------------------------- */ @media (min-width:721px){ /* Gouttière resserrée : la charte empile beaucoup de cartes, 12 px entre chacune repoussait « Actions » loin sous la ligne de flottaison. */ .side{gap:8px} .side>details>summary{height:38px} /* Discussion active : un cadre sauge COMPLET, mais fin (1 px) — le liseré de 3 px sur le seul bord gauche se lisait mal et déséquilibrait la ligne. La bordure transparente est portée par tous les états : la sélection ne décale donc pas le contenu d'un pixel. */ .side .preset{border:1px solid transparent;border-radius:14px;background:transparent} .side .preset.active{background:var(--row-bg-on);border-color:var(--accent)} .side .preset:hover{background:var(--row-bg)} /* Bulle de question : coin bas-droit rentré, comme une bulle qui pointe vers son auteur. */ #chat .msg.user{border-radius:20px 20px 6px 20px} } /* Action principale en sauge : c'est la couleur d'action de la charte, et elle se distingue enfin du texte (le bouton reprenait --text, donc un brun sombre indistinct du reste). L'arrêt, lui, reste en terre cuite. */ #send{background:var(--accent);border-color:var(--accent);color:var(--on-accent)} #send:hover:not(:disabled){background:var(--accent);border-color:var(--accent);color:var(--on-accent);opacity:.88} #stop{background:var(--err);border-color:var(--err);color:var(--on-err)} #stop:hover{background:var(--err);border-color:var(--err);color:var(--on-err);opacity:.88} /* --- Terre : largeur de lecture -------------------------------------- La mesure était plafonnée à 760 px et centrée sur la fenêtre : sur un écran large, le fil se réduisait à une colonne étroite entourée de vide, alors que la charte fait courir le texte sur toute la largeur du panneau. Une seule variable pilote désormais le fil ET la carte de saisie — ils étaient réglés séparément (450 / 380 / 360 px), donc condamnés à diverger. La borne haute reste : au-delà, une ligne devient pénible à lire (l'œil perd le début de la suivante). 1080 px ≈ 110 caractères, large mais tenable. */ @media (min-width:721px){ :root{--measure:min(1080px,100%)} #chat{padding-top:26px;padding-inline:max(20px,calc(50% - var(--measure)/2))} #composer{--col:calc(max(20px,calc(50% - var(--measure)/2)) + var(--sbw,0px))} } /* --- Terre : rangée de bulles techniques ------------------------------ Raisonnement, outils et mémoire ne portent qu'une étiquette tant qu'ils sont repliés : les empiler gaspillait une ligne entière par appel et repoussait la réponse hors de l'écran. Ils défilent donc en rangée, et reprennent la pleine largeur dès qu'on en déplie un. */ .toolrow{display:flex;flex-wrap:wrap;gap:8px;align-items:flex-start} .toolrow>.msg.collapsible{margin:0;max-width:100%} .toolrow>.msg.collapsible.collapsed{width:auto} /* Dépliée : la bulle passe seule sur sa ligne, sinon son contenu serait comprimé dans une colonne étroite à côté d'une pastille. */ .toolrow>.msg.collapsible:not(.collapsed){flex:1 0 100%} /* --- Terre : sections groupées en cartes ------------------------------ Une carte par section empilait onze arrondis et autant de gouttières : la barre latérale débordait de l'écran alors que la plupart de ces lignes sont des titres repliés d'une seule ligne. Les sections d'IA d'un côté, celles de l'application de l'autre, tiennent maintenant chacune dans UNE carte, avec un filet discret entre les lignes. */ @media (min-width:721px){ .sidegroup{background:var(--panel);border-radius:20px;box-shadow:var(--shadow); padding:2px 16px;display:flex;flex-direction:column} /* Dans un groupe, les sections ne sont plus des cartes : elles redeviennent des lignes, séparées par un filet — sauf la première. */ .sidegroup>details{background:transparent;box-shadow:none;border-radius:0;padding:0; border-top:1px solid var(--border)} .sidegroup>details:first-child{border-top:0} .sidegroup>details[open]{padding-bottom:12px} } /* --- Recherche Hugging Face ------------------------------------------- Deux listes successives dans la même zone (dépôts, puis fichiers du dépôt choisi) : une seule mise en forme sert aux deux, sinon elles divergent. La liste est bornée en hauteur et défile — un dépôt publie facilement vingt quantifications, et la modale ne doit pas s'étirer jusqu'à sortir de l'écran. */ .hf-list{display:flex;flex-direction:column;max-height:220px;overflow-y:auto; border:1px solid var(--border);border-radius:12px;margin-top:6px} .hf-row{display:flex;align-items:center;gap:10px;padding:7px 12px;cursor:pointer; border-top:1px solid var(--border);font-size:12px} .hf-row:first-child{border-top:0} .hf-row:hover{background:var(--row-bg-on)} /* Le nom prend la place restante et se tronque : un identifiant de dépôt long ne doit pas pousser la taille et le verdict hors de la ligne. */ .hf-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text)} .hf-meta{flex:none;color:var(--dim);font-family:var(--mono);font-size:10.5px;white-space:nowrap} /* Verdict mémoire. La couleur seule ne suffit pas — le mot est écrit dedans, et l'explication complète est dans l'infobulle. */ .hf-fit{flex:none;padding:1px 8px;border-radius:999px;font-size:10px;letter-spacing:.03em; text-transform:uppercase;border:1px solid transparent} .hf-fit.ok{color:var(--ok);border-color:var(--ok)} .hf-fit.juste{color:var(--warn);border-color:var(--warn)} .hf-fit.trop{color:var(--err);border-color:var(--err)} .hf-head{display:flex;align-items:center;gap:10px;margin-top:6px;font-size:12px} .hf-mm{display:flex;align-items:center;gap:6px;margin-top:8px;font-size:11.5px; color:var(--dim);cursor:pointer} .hf-mm.muted{cursor:default} /* --- Panneau « Fichiers » (droite) ------------------------------------ Symétrique des réglages à gauche : une colonne fixe qui pousse la conversation au lieu de la recouvrir. C'est possible ici parce que la largeur de lecture est pilotée par --measure et que #chat se recentre tout seul dans la place qui reste — le fil se resserre, il ne passe pas dessous. Le panneau est retiré du flux quand il est fermé (et non simplement décalé) : sinon il conserverait sa gouttière et le fil resterait décentré. */ #files{display:none;width:320px;flex-shrink:0;border-left:1px solid var(--border); background:var(--panel);padding:16px;padding-bottom:calc(16px + env(safe-area-inset-bottom)); overflow-y:auto;flex-direction:column;gap:10px} html[data-files="1"] #files{display:flex} #files-backdrop{display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:14} .files-head{display:flex;align-items:center;gap:8px} .files-head .stitle{flex:1} #files-refresh,#files-close{margin:0;padding:2px 8px;min-height:0;font-size:12px; background:transparent;border:1px solid transparent;color:var(--dim);border-radius:8px} #files-refresh:hover,#files-close:hover{border-color:var(--border);color:var(--text)} /* Fil d'Ariane : il doit pouvoir revenir à la ligne — un identifiant de discussion dans le chemin dépasse largement 320 px. */ .files-crumb{display:flex;flex-wrap:wrap;align-items:center;gap:2px;font-size:11px} .files-crumb-b{margin:0;padding:2px 6px;min-height:0;font-size:11px;border:0;border-radius:7px; background:transparent;color:var(--dim);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .files-crumb-b:hover:not(:disabled){background:var(--row-bg-on);color:var(--text)} .files-crumb-b.on{color:var(--text);cursor:default} .files-crumb-s{color:var(--dim);opacity:.5} .files-list{display:flex;flex-direction:column;flex:1;min-height:0} .files-empty{font-size:12px;line-height:1.5;padding:10px 2px} /* Une ligne = pastille, nom + méta, actions. Les actions n'apparaissent qu'au survol : au repos on veut lire des noms de fichiers, pas une grille de boutons. Sur écran tactile (pas de survol) elles restent visibles. */ .files-row{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:12px;font-size:12px} .files-row:hover{background:var(--row-bg-on)} .files-ic{flex:none;width:12px;text-align:center;color:var(--dim)} .files-row.isdir .files-ic{color:var(--accent)} .files-mid{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px} .files-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text)} .files-meta{font-family:var(--mono);font-size:10px;color:var(--dim);white-space:nowrap} .files-acts{flex:none;display:flex;gap:2px;opacity:0;transition:opacity .12s} .files-row:hover .files-acts,.files-row:focus-within .files-acts{opacity:1} @media (hover:none){.files-acts{opacity:1}} .files-acts .iconbtn{font-size:11px;line-height:1;padding:4px 6px;background:transparent; border:1px solid transparent;border-radius:6px;color:var(--dim);cursor:pointer;margin:0;min-height:0} .files-acts .iconbtn:hover{border-color:var(--border);color:var(--text)} .files-foot{font-family:var(--mono);font-size:10px;padding-top:2px;border-top:1px solid var(--border); display:flex;align-items:center;gap:6px;flex-wrap:wrap} /* Détour « hors discussion » : discret, en pied de panneau — c'est une sortie de secours pour les fichiers d'avant le rangement par discussion, pas une vue qu'on propose au même rang que la discussion ouverte. */ .files-scope{margin:0;padding:2px 6px;min-height:0;font-family:var(--mono);font-size:10px; background:transparent;border:1px solid var(--border);border-radius:7px;color:var(--dim)} .files-scope:hover{color:var(--text);background:var(--row-bg-on)} /* Téléchargement en cours : downloadWorkspaceFile pose .busy sur la ligne. */ .files-row.busy{opacity:.55;pointer-events:none} /* Bouton dossier du composeur : même gabarit que le trombone, et teinté quand le panneau est ouvert — l'état se lit sans regarder à droite. */ #files-btn{width:18px;height:18px;min-height:18px;flex:none;margin:0;padding:0; display:flex;align-items:center;justify-content:center;border-radius:999px; background:transparent;border:1px solid transparent;color:var(--dim)} #files-btn:hover{background:var(--row-bg-on);border-color:transparent;color:var(--text)} #files-btn:active{transform:scale(.9)} #files-btn svg{display:block} html[data-files="1"] #files-btn{color:var(--accent)} /* Petit écran : tiroir par la droite, comme les réglages par la gauche. Pousser une colonne de 320 px sur un téléphone ne laisserait rien à la conversation. */ @media (max-width:720px){ #files{position:fixed;inset:0 0 0 auto;width:85%;max-width:340px;z-index:15; background:var(--bg);padding-top:calc(16px + env(safe-area-inset-top)); padding-right:calc(16px + env(safe-area-inset-right))} html[data-files="1"] #files-backdrop{display:block} } @media (min-width:721px){ /* Dans la charte « Terre », le panneau est une carte comme les autres. */ #files{background:var(--panel);border-left-color:transparent;border-radius:20px; box-shadow:var(--shadow);margin:0} } /* ===================================================================== ===== Refonte « Sober Tech » ======================================== ===================================================================== Dernière section du fichier, donc dernier mot de la cascade : elle porte la coque de la refonte (barre d'en-tête, zones de la barre latérale, moniteur machine) et redresse les quelques règles historiques qu'elle contredit. Trois principes, tirés de la maquette : 1. Le fil est la seule chose qui compte : tout le reste est atténué, sans cadre superflu, et la barre latérale s'escamote d'un clic. 2. Les données machine sont TOUJOURS visibles mais jamais au premier plan — un pied de barre latérale, trois jauges, pas un tableau. 3. Aucun contour permanent sur les boutons : l'affordance vient du survol (fond qui apparaît, léger enfoncement au clic). ===================================================================== */ /* --- Coque ---------------------------------------------------------- */ .side{background:var(--side);display:flex;flex-direction:column;gap:0;padding:0;overflow:hidden} /* Les trois zones de la barre : en-tête fixe, contenu qui défile, moniteur posé en pied. Sans ça, la liste des discussions poussait les jauges hors de l'écran dès la dixième discussion. */ .sidehead{flex:none;display:flex;align-items:center;gap:8px;margin:0; padding:14px 14px 10px;padding-top:calc(14px + env(safe-area-inset-top))} #side-scroll{flex:1;min-height:0;overflow-y:auto;padding:0 12px 12px; scrollbar-gutter:stable;display:flex;flex-direction:column;gap:2px} #side-perf{flex:none;border-top:1px solid var(--border);padding:12px 14px; padding-bottom:calc(12px + env(safe-area-inset-bottom))} @media (max-width:720px){ .side{padding:0} #side-scroll{padding-left:calc(12px + env(safe-area-inset-left))} .sidehead{padding-left:calc(14px + env(safe-area-inset-left))} } /* Escamotage sur grand écran : la barre disparaît, le fil reprend la place. L'état est persisté (localStorage) et posé sur avant le rendu, comme le thème — sinon la barre apparaît puis se referme sous les yeux. */ @media (min-width:721px){ html[data-side="0"] .side{display:none} html[data-side="0"] #sidetoggle svg{transform:none} } #sidetoggle svg{transform:scaleX(-1)} /* --- Barre d'en-tête du fil ----------------------------------------- */ /* Elle porte ce qui décrit la conversation en cours : de quoi on parle, et avec QUEL modèle. Le choix du modèle vivait au fond de la barre latérale, dans une liste de presets — trois clics et un défilement pour une action qu'on fait plusieurs fois par session. */ #topbar{flex:none;display:flex;align-items:center;gap:10px;padding:10px 16px; padding-top:calc(10px + env(safe-area-inset-top)); border-bottom:1px solid var(--border);background:var(--bg);z-index:5} #topbar-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; font-size:13.5px;font-weight:500;color:var(--text)} #topbar .iconbtn{width:30px;height:30px;flex:none;display:inline-flex;align-items:center; justify-content:center;border:0;border-radius:9px;background:transparent;color:var(--dim); cursor:pointer;margin:0;padding:0;transition:background .15s,color .15s,transform .1s} #topbar .iconbtn:hover{background:var(--row-bg-on);color:var(--text)} #topbar .iconbtn:active{transform:scale(.92)} #topbar .iconbtn svg{display:block} /* Sélecteur de modèle : le libellé n'est PAS une étiquette flottante mais le nom du preset actif, lisible d'un coup d'œil. */ #model-switch{max-width:230px;font:inherit;font-size:12.5px;color:var(--text); background:var(--panel);border:1px solid var(--border);border-radius:999px; padding:6px 30px 6px 12px;cursor:pointer;appearance:none; background-image:linear-gradient(45deg,transparent 50%,var(--dim) 50%),linear-gradient(135deg,var(--dim) 50%,transparent 50%); background-position:calc(100% - 15px) 52%,calc(100% - 11px) 52%; background-size:4px 4px,4px 4px;background-repeat:no-repeat} #model-switch:hover{border-color:var(--accent)} #model-switch:focus-visible{outline:2px solid var(--accent);outline-offset:1px} @media (max-width:720px){ #topbar{padding-left:56px} /* la place du bouton ☰, en position fixe */ #model-switch{max-width:130px} } /* --- Discussions ----------------------------------------------------- */ /* La liste EST la barre latérale : titre à gauche, heure à droite, une ligne. Le reste (réglages) descend sous un unique repli. */ #conv-search{width:100%;font:inherit;font-size:12.5px;color:var(--text); background:var(--panel);border:1px solid var(--border);border-radius:10px; padding:7px 11px;margin:0 0 6px} #conv-search::placeholder{color:var(--dim)} #conv-search:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-bg)} .side .preset{padding:8px 11px;margin:1px 0;gap:10px} .side .preset .preset-name{font-size:13px;font-weight:450;white-space:nowrap;overflow:hidden; text-overflow:ellipsis;word-break:normal;overflow-wrap:normal} .side .preset.active .preset-name{font-weight:550} /* Heure de la dernière activité : à DROITE du titre, sur la même ligne, en monospace pour que les colonnes s'alignent d'une ligne à l'autre. ⚠️ Sélecteur en TROIS classes : `.preset>span` (plus haut) pose flex:1 sur tout span enfant d'une ligne — l'heure prenait donc la MOITIÉ de la ligne et le titre était tronqué au premier mot. */ .side .preset .conv-when{flex:none;font-family:var(--mono);font-size:10.5px; color:var(--dim);letter-spacing:-.01em;overflow:visible} .side .preset:hover .conv-when{opacity:0} .conv-empty{font-size:12px;color:var(--dim);padding:8px 11px} /* Repli des réglages : tout ce qui n'est pas la conversation tient sous cette seule ligne, pour que la barre latérale de la maquette (discussions + machine) reste la vue par défaut. */ #side-settings{margin-top:8px;border-top:1px solid var(--border)} #side-settings>summary{height:38px} #side-settings>.sidegroup{margin-top:2px} /* --- Moniteur machine (pied de barre latérale) ----------------------- */ .perf-head{display:flex;align-items:center;gap:8px;margin-bottom:9px; font-family:var(--mono);font-size:10px;text-transform:uppercase;letter-spacing:.1em;color:var(--dim)} .perf-head .perf-toggle{margin-left:auto;border:0;background:transparent;color:var(--dim); padding:0 2px;margin:0;font-size:11px;cursor:pointer;line-height:1} .perf-row{margin:0 0 9px} .perf-row:last-child{margin-bottom:0} .perf-l{display:flex;align-items:baseline;gap:8px;font-size:11.5px;color:var(--dim);margin-bottom:4px} .perf-l b{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; font-weight:450;color:var(--text)} .perf-l .perf-pct{font-family:var(--mono);font-size:11px;color:var(--text);font-variant-numeric:tabular-nums} /* La jauge : 4 px, coins ronds, remplissage sauge. Elle passe à l'ambre puis au rouille quand la machine sature — c'est là que l'information devient utile. */ .perf-track{height:4px;border-radius:999px;background:var(--row-bg-on);overflow:hidden} .perf-fill{height:100%;width:0;border-radius:999px;background:var(--accent-soft); transition:width .4s ease,background .4s ease} .perf-fill.hot{background:var(--warn)} .perf-fill.max{background:var(--err)} .perf-sub{margin-top:3px;font-family:var(--mono);font-size:9.5px;color:var(--dim);opacity:.85} html[data-perf="0"] .perf-body{display:none} /* --- Boutons : sans contour, micro-interaction au survol ------------- */ button{border-color:transparent;background:var(--row-bg);border-radius:9px; transition:background .15s ease,color .15s ease,transform .08s ease} button:hover{background:var(--row-bg-on);border-color:transparent;color:var(--text)} button:active:not(:disabled){transform:translateY(1px)} .side .preset,.slist,#conv-search{transition:background .15s ease,border-color .15s ease} /* --- Fil : la réponse redevient une carte --------------------------- */ /* La maquette encadre la réponse de l'IA : sur un fond de fil coloré, du texte nu flotte sans ancrage, et la frontière entre deux réponses se perd. */ #chat{padding-top:22px} #chat .msg.assistant{background:var(--bubble);border:1px solid var(--border); border-radius:16px;padding:14px 18px;max-width:100%;box-shadow:var(--shadow-card)} #chat .msg.assistant.errmsg{background:var(--danger-bg);border-color:color-mix(in srgb,var(--err) 45%,transparent)} /* Question : aplat de sauge, coin bas-droit rentré. --on-accent garantit le contraste dans les DEUX variantes. */ #chat .msg.user{background:var(--accent);color:var(--on-accent);border-radius:18px 18px 6px 18px; padding:11px 16px;max-width:75%;box-shadow:var(--shadow-card)} #chat .msg.user .label{color:var(--on-accent);opacity:.75} #chat .msg.user .body a{color:var(--on-accent);text-decoration:underline} #chat .msg.user code{background:rgba(255,255,255,.18);color:inherit} [data-theme="dark"] #chat .msg.user code{background:rgba(15,23,42,.22)} /* Les pastilles de fichier d'une question sont posées SUR l'aplat de sauge : sans fond propre, elles s'y fondaient. */ #chat .msg.user .msg-files .chip-file{background:var(--panel);color:var(--text);border-color:transparent} /* --- Saisie : la pilule de la maquette ------------------------------ */ /* Le bouton d'envoi devient une pastille ronde à flèche : c'est la forme de la maquette, et elle laisse toute la largeur au texte. Le mot « Envoyer » reste porté par aria-label et par l'infobulle — l'action n'est pas moins nommée pour un lecteur d'écran, elle l'est moins à l'écran. */ #send,#stop{width:34px;height:34px;min-height:34px;padding:0;border-radius:999px; display:inline-flex;align-items:center;justify-content:center;flex:none} #send svg,#stop svg{display:block} #send{background:transparent;border-color:transparent;color:var(--accent)} #send:hover:not(:disabled){opacity:1;transform:none;background:var(--accent-bg);color:var(--accent)} #send:active:not(:disabled){transform:scale(.92)} #send:disabled{background:transparent;color:var(--dim)} #stop{background:transparent;border-color:transparent;color:var(--err)} #stop:hover{opacity:1;background:var(--danger-bg);color:var(--err)} /* Les outils de la saisie (joindre, fichiers, envoyer) montent DANS la pilule, à droite du texte : c'est là qu'on les cherche, et le pied de carte redevient ce qu'il doit être — une jauge de contexte, rien d'autre. */ #inputbar{align-items:flex-end;padding:8px 8px 8px 18px;gap:4px;box-shadow:var(--shadow-card)} #composer:focus-within #inputbar,#composer:focus-within #ctxmeta{border-color:var(--accent-soft)} #inputbar #attach,#inputbar #files-btn{width:34px;height:34px;min-height:34px;border-radius:999px; align-self:center} #inputbar #attach svg,#inputbar #files-btn svg{width:16px;height:16px} #ctxmeta{padding-bottom:12px} #ctxtrack{border-radius:999px} /* --- Coque à plat --------------------------------------------------- La direction précédente posait deux CARTES flottantes séparées par une gouttière ; la maquette est bord à bord : barre latérale collée au bord, simple filet de séparation, fil qui occupe tout le reste. On neutralise donc ici les règles de coque de la section « Terre » plutôt que de les réécrire plus haut — la cascade fait le tri, et l'historique reste lisible. */ @media (min-width:721px){ body{padding:0;gap:0} html[data-pwa="1"] body{padding-top:env(safe-area-inset-top)} .side{width:300px;border-right:1px solid var(--border);border-radius:0; box-shadow:none;background:var(--side);overflow:hidden} /* Dans la barre, plus de cartes : des lignes posées sur le fond. Deux niveaux de cadre imbriqués (carte de section + carte de liste) pour une liste de titres, c'était deux de trop. */ #side-scroll>details,#side-scroll>.sidegroup{background:transparent;box-shadow:none; border-radius:0;padding:0;margin:0} #side-scroll>details>summary{height:34px} .side .slist{background:transparent;border-color:transparent;border-radius:12px} .side #side-settings .slist{background:var(--row-bg);border-radius:12px} /* Le fil n'est plus une carte : il EST la page. */ .main{background:transparent;border-radius:0;box-shadow:none;overflow:visible} #composer{background:linear-gradient(to bottom,transparent 0,var(--bg) var(--fade))} #inputbar,#ctxmeta{background:var(--panel);border-color:var(--border)} #input{background:transparent} } /* Ligne de discussion : les actions (renommer, supprimer) sont posées EN SURIMPRESSION à droite plutôt qu'en flux. En flux, elles réservaient leur largeur en permanence — invisibles mais présentes — et le titre était tronqué au tiers de la place disponible. */ .side .preset{position:relative;border-radius:12px} .side .preset .conv-acts{position:absolute;right:6px;top:50%;transform:translateY(-50%); margin:0;background:var(--row-bg-on);border-radius:9px;padding:1px} .side .preset.active .conv-acts{background:var(--accent-bg)} .side .preset .preset-info{overflow:hidden} /* La sélection est un aplat, pas un cadre : un liseré autour d'une ligne de liste alourdit là où un fond suffit à dire « c'est celle-ci ». */ .side .preset.active{background:var(--accent-bg);border-color:transparent} .side .preset.active .preset-name{color:var(--text)} /* --- Téléphone : un seul bouton de barre latérale -------------------- */ /* Le ☰ flottant ouvre déjà le tiroir ; garder EN PLUS l'icône d'escamotage de l'en-tête donnait deux boutons voisins pour la même action, et grignotait la place du titre sur un écran qui n'en a pas. */ @media (max-width:720px){ /* Sélecteur en DEUX id : `#topbar .iconbtn` (plus haut) pose déjà un display, et il l'emporterait sur un simple `#sidetoggle`. */ #topbar #sidetoggle{display:none} #topbar{padding-left:52px;padding-right:10px;gap:6px} #topbar-title{font-size:13px} } /* Le ☰ suit la charte des boutons : pas de contour, un fond qui apparaît au survol. Il flotte au-dessus de l'en-tête, il doit donc en reprendre le fond pour ne pas laisser voir le fil défiler dessous. */ #menubtn{background:var(--bg);border:0;color:var(--dim);border-radius:10px; width:34px;height:34px;font-size:16px;top:calc(11px + env(safe-area-inset-top));left:9px} #menubtn:hover{background:var(--row-bg-on);color:var(--text)} #lc-chip{border-radius:10px;height:34px;top:calc(11px + env(safe-area-inset-top));left:calc(50px + env(safe-area-inset-left))} /* Jauges : la piste doit se VOIR, sinon une barre presque vide ressemble à un trait souligné sous l'intitulé. */ .perf-track{background:color-mix(in srgb,var(--text) 9%,transparent)} /* Une liste déroulante posée directement dans une ligne de modale n'a pas de garde-fou de largeur (contrairement à .pe-row-c) : une valeur très longue — un chemin de destination, par exemple — débordait sous l'intitulé. */ .pe-row>.pe-selc{max-width:62%} /* Marque et attribution du fork empilées, pastille d'état à leur droite. */ .sidebrand{flex:1;min-width:0;display:flex;flex-direction:column;gap:1px} .sidebrand .brand-fork{margin:0;font-size:10px;line-height:1.3}