/* ===== Palette ===================================================== Un seul thème (« Clean ») : noir et blanc, sobre. Deux variantes — claire par défaut, sombre via data-theme="dark" sur . Toutes les couleurs de l'UI passent par ces variables ; rien d'autre ne code une couleur en dur. */ :root{color-scheme:light;--font:-apple-system,BlinkMacSystemFont,'Segoe UI',Inter,Helvetica,Arial,sans-serif;--mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;--bubble:#f0f0f0;--bg:#ffffff;--panel:#fafafa;--border:#e0e0e2;--text:#0a0a0a;--dim:#6b6b70;--accent:#0a0a0a;--accent-bg:rgba(0,0,0,.05);--row-bg:rgba(0,0,0,.03);--row-bg-on:rgba(0,0,0,.042);--ok:#0a0a0a;--warn:#6b6b6b;--err:#c0392b;--mag:#6b6b6b;--code-bg:#f5f5f5;--danger-bg:#fbeae8;--warn-bg:#f2f2f2;--add:#1a7f37;--add-bg:rgba(26,127,55,.10);--del:#cf222e;--del-bg:rgba(207,34,46,.10)} [data-theme="dark"]{color-scheme:dark;--bubble:#232326;--bg:#0b0b0c;--panel:#161618;--border:#33333a;--text:#f2f2f4;--dim:#a0a0a8;--accent:#f5f5f5;--accent-bg:rgba(255,255,255,.07);--row-bg:rgba(255,255,255,.04);--row-bg-on:rgba(255,255,255,.055);--ok:#f5f5f5;--warn:#8e8e8e;--err:#f2777a;--mag:#8e8e8e;--code-bg:#000000;--danger-bg:#2e1a19;--warn-bg:#1c1c1c;--add:#4ac26b;--add-bg:rgba(74,194,107,.13);--del:#f85149;--del-bg:rgba(248,81,73,.13)} /* 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/AJEAN. 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. */ #chat{padding:28px max(16px,calc(50% - 380px)) 8px;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)} /* Étiquettes VOUS / AJEAN : inutiles ici (l'alignement suffit), sauf pour les bulles repliables (raisonnement / outils) où le label est le bouton de repli. */ #chat .msg .label{display:none} #chat .msg.collapsible .label{display:block} /* 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. */ #composer{position:absolute;left:0;right:0;bottom:0;z-index:4;border-top:none;display:flex;flex-direction:column;padding:34px 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) 34%)} #chat{padding-bottom:calc(170px + env(safe-area-inset-bottom))} #scrollbtn{bottom:calc(180px + env(safe-area-inset-bottom));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} #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} /* 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)}