Intensité du raisonnement dans la barre de saisie, angles moins arrondis

Le niveau de raisonnement n'existait que dans l'éditeur de preset : le changer
demandait d'ouvrir les réglages, éditer, enregistrer. Or ça se décide au moment
d'écrire le message.

- Nouvelle route /api/reasoning-effort (GET/POST), calquée sur /api/memory :
  elle écrit REASONING_EFFORT dans la config vivante, relue à chaque requête au
  moteur. Aucun redémarrage — la valeur voyage dans le corps de la requête.
- Liste blanche côté serveur : cette clé finit dans une requête au moteur, on
  n'y laisse pas passer une chaîne arbitraire venue du navigateur.
- La liste est grisée quand le raisonnement est coupé pour ce modèle, plutôt que
  masquée : le réglage reste trouvable, et l'infobulle dit où le rallumer.
- L'éditeur de preset garde le même réglage comme DÉFAUT du modèle. Appliquer un
  preset réécrit toute la config, donc il reprend la main sur le choix fait à la
  volée — les deux sous-titres le disent, sinon la double présence intrigue.

Angles : les rayons allaient de 5 à 26px selon les composants, ce qui donnait
des cartes très rondes. Tout est ramené à 4px (cartes, boutons, modales) et 3px
(petits éléments), y compris les formes multi-valeurs comme la barre de saisie
(26px 26px 0 0 → 3px 3px 0 0). Les pastilles (999px) et les ronds (50%) sont
laissés intacts : ce sont des interrupteurs et des avatars, pas des cartes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
MichaelandClaude Opus 5 committed 2026-08-17 10:31:37 +02:00
1 parent ebe04f9d61
commit 0a3af60715
9 files changed
+386 -216

No files matched your search

+11 -7
View File
@@ -172,13 +172,17 @@ Ajoutées par ce fork :
pour en proposer d'autres, édite ce fichier et recompile. Une entrée dont le pour en proposer d'autres, édite ce fichier et recompile. Une entrée dont le
runtime manque (`npx`/`uvx` absent) le signale au lieu d'échouer plus tard, et runtime manque (`npx`/`uvx` absent) le signale au lieu d'échouer plus tard, et
celles qui réclament une clé d'API la rappellent avant l'enregistrement. celles qui réclament une clé d'API la rappellent avant l'enregistrement.
- **Intensité du raisonnement** : en plus de l'interrupteur *Raisonnement*, - **Intensité du raisonnement** : un niveau — auto / aucune / basse / moyenne /
l'éditeur de preset expose un niveau — auto / aucune / basse / moyenne / haute haute — envoyé à `llama-server` comme `reasoning_effort`. Réglable **dans la
— envoyé à `llama-server` comme `reasoning_effort`. Réglage **par preset**, barre de saisie**, parce que ça se décide en écrivant le message : le
donc de fait par modèle. `none` coupe le raisonnement ; les autres valeurs sont changement s'applique au message suivant, sans redémarrer le moteur. L'éditeur
passées au gabarit jinja du modèle, ce qui ne change le comportement que des de preset garde le même réglage comme **défaut du modèle** ; appliquer un
modèles qui les lisent (gpt-oss et apparentés) — ailleurs c'est ignoré sans preset reprend donc la main sur le choix fait à la volée. `none` coupe le
erreur, et le sous-titre du réglage le dit plutôt que de promettre un effet. raisonnement ; les autres valeurs sont passées au gabarit jinja du modèle, ce
qui ne change le comportement que des modèles qui les lisent (gpt-oss et
apparentés) — ailleurs c'est ignoré sans erreur, et l'interface le dit plutôt
que de promettre un effet. La liste est grisée quand le raisonnement est coupé
pour ce modèle.
- **Discussions multiples** : historique complet dans la barre latérale, titre - **Discussions multiples** : historique complet dans la barre latérale, titre
repris du premier message (renommable), suppression. **Chaque discussion a son repris du premier message (renommable), suppression. **Chaque discussion a son
dossier de fichiers** (`workspace/discussions/<id>/`) : les pièces jointes dossier de fichiers** (`workspace/discussions/<id>/`) : les pièces jointes
+165 -104
View File
@@ -174,11 +174,11 @@ select{color-scheme:inherit}
option,optgroup{background:var(--panel);color:var(--text)} option,optgroup{background:var(--panel);color:var(--text)}
.side{width:320px;border-right:1px solid var(--border);padding:16px;padding-bottom:calc(16px + env(safe-area-inset-bottom));overflow-y:auto;flex-shrink:0} .side{width:320px;border-right:1px solid var(--border);padding:16px;padding-bottom:calc(16px + env(safe-area-inset-bottom));overflow-y:auto;flex-shrink:0}
.main{flex:1;display:flex;flex-direction:column;min-width:0} .main{flex:1;display:flex;flex-direction:column;min-width:0}
#menubtn{display:none;position:fixed;top:calc(10px + env(safe-area-inset-top));left:calc(10px + env(safe-area-inset-left));z-index:20;background:var(--panel);border:1px solid var(--border);color:var(--text);width:40px;height:40px;border-radius:6px;font-size:18px;cursor:pointer} #menubtn{display:none;position:fixed;top:calc(10px + env(safe-area-inset-top));left:calc(10px + env(safe-area-inset-left));z-index:20;background:var(--panel);border:1px solid var(--border);color:var(--text);width:40px;height:40px;border-radius:3px;font-size:18px;cursor:pointer}
#backdrop{display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:10} #backdrop{display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:10}
/* Pastille « installation en cours » : suit exactement les règles d'affichage /* Pastille « installation en cours » : suit exactement les règles d'affichage
du bouton ☰ (elle ne sert que quand le panneau est en tiroir, donc caché). */ du bouton ☰ (elle ne sert que quand le panneau est en tiroir, donc caché). */
#lc-chip{display:none;position:fixed;top:calc(10px + env(safe-area-inset-top));left:calc(58px + env(safe-area-inset-left));z-index:20;max-width:60vw;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:var(--panel);border:1px solid var(--accent);color:var(--accent);height:40px;padding:0 12px;border-radius:6px;font-size:12px;cursor:pointer} #lc-chip{display:none;position:fixed;top:calc(10px + env(safe-area-inset-top));left:calc(58px + env(safe-area-inset-left));z-index:20;max-width:60vw;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:var(--panel);border:1px solid var(--accent);color:var(--accent);height:40px;padding:0 12px;border-radius:3px;font-size:12px;cursor:pointer}
#lc-chip.failed{border-color:var(--err);color:var(--err)} #lc-chip.failed{border-color:var(--err);color:var(--err)}
@media (max-width:720px){ @media (max-width:720px){
.side{position:fixed;inset:0 auto 0 0;width:80%;max-width:320px;z-index:15;transform:translateX(-100%);transition:transform .2s;background:var(--bg);padding-top:calc(16px + env(safe-area-inset-top));padding-left:calc(16px + env(safe-area-inset-left))} .side{position:fixed;inset:0 auto 0 0;width:80%;max-width:320px;z-index:15;transform:translateX(-100%);transition:transform .2s;background:var(--bg);padding-top:calc(16px + env(safe-area-inset-top));padding-left:calc(16px + env(safe-area-inset-left))}
@@ -201,7 +201,7 @@ option,optgroup{background:var(--panel);color:var(--text)}
/* Desktop : menu plus large et bulles (badges) un peu plus grosses. */ /* Desktop : menu plus large et bulles (badges) un peu plus grosses. */
@media (min-width:721px){ @media (min-width:721px){
.side{width:380px} .side{width:380px}
.preset-meta .qtag,.preset-meta .rtag,.preset-meta .btag{font-size:11px;padding:2px 9px;border-radius:9px} .preset-meta .qtag,.preset-meta .rtag,.preset-meta .btag{font-size:11px;padding:2px 9px;border-radius:4px}
.preset-meta .qtag svg,.preset-meta .rtag svg,.preset-meta .btag svg{width:11px;height:11px} .preset-meta .qtag svg,.preset-meta .rtag svg,.preset-meta .btag svg{width:11px;height:11px}
} }
h1{margin:0;font-size:18px;color:var(--accent)} h1{margin:0;font-size:18px;color:var(--accent)}
@@ -224,7 +224,7 @@ h1{margin:0;font-size:18px;color:var(--accent)}
sans souris. */ sans souris. */
.conv-acts{display:flex;gap:2px;opacity:0;transition:opacity .12s} .conv-acts{display:flex;gap:2px;opacity:0;transition:opacity .12s}
.preset:hover .conv-acts,.preset:focus-within .conv-acts{opacity:1} .preset:hover .conv-acts,.preset:focus-within .conv-acts{opacity:1}
.conv-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} .conv-acts .iconbtn{font-size:11px;line-height:1;padding:4px 6px;background:transparent;border:1px solid transparent;border-radius:3px;color:var(--dim);cursor:pointer}
.conv-acts .iconbtn:hover{border-color:var(--border);color:var(--text)} .conv-acts .iconbtn:hover{border-color:var(--border);color:var(--text)}
/* Séparateur de groupe dans la barre latérale : sépare les réglages d'IA des /* Séparateur de groupe dans la barre latérale : sépare les réglages d'IA des
réglages d'application, sans imbriquer un second niveau de repli (illisible réglages d'application, sans imbriquer un second niveau de repli (illisible
@@ -234,7 +234,7 @@ h1{margin:0;font-size:18px;color:var(--accent)}
/* Choix d'avatar : une grille de pastilles emoji, celle retenue encadrée. */ /* Choix d'avatar : une grille de pastilles emoji, celle retenue encadrée. */
.avpick{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px} .avpick{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px}
.avpick button{font-size:15px;line-height:1;padding:5px 6px;background:var(--panel); .avpick button{font-size:15px;line-height:1;padding:5px 6px;background:var(--panel);
border:1px solid var(--border);border-radius:8px;cursor:pointer} border:1px solid var(--border);border-radius:4px;cursor:pointer}
.avpick button:hover{border-color:var(--accent)} .avpick button:hover{border-color:var(--accent)}
.avpick button.sel{border-color:var(--accent);background:color-mix(in srgb,var(--accent) 15%,transparent)} .avpick button.sel{border-color:var(--accent);background:color-mix(in srgb,var(--accent) 15%,transparent)}
/* Avatar + nom en tête de bulle : l'emoji garde sa taille propre, le nom hérite /* Avatar + nom en tête de bulle : l'emoji garde sa taille propre, le nom hérite
@@ -243,7 +243,7 @@ h1{margin:0;font-size:18px;color:var(--accent)}
.msg .label .who{text-transform:none;letter-spacing:.04em;font-size:11px} .msg .label .who{text-transform:none;letter-spacing:.04em;font-size:11px}
/* Captures d'écran et images de l'agent : bornées à la largeur de la bulle, et /* Captures d'écran et images de l'agent : bornées à la largeur de la bulle, et
cliquables pour ouvrir en grand dans un onglet. */ cliquables pour ouvrir en grand dans un onglet. */
.msg .body img{max-width:100%;height:auto;border-radius:8px;border:1px solid var(--border);display:block;margin:8px 0} .msg .body img{max-width:100%;height:auto;border-radius:4px;border:1px solid var(--border);display:block;margin:8px 0}
/* pastille d'état service, en haut à droite de la barre latérale */ /* pastille d'état service, en haut à droite de la barre latérale */
.statuspill{display:inline-flex;align-items:center;gap:6px;font-size:11px;padding:3px 10px;border-radius:999px;border:1px solid var(--border);background:var(--panel);color:var(--dim);font-weight:600;white-space:nowrap;cursor:pointer} .statuspill{display:inline-flex;align-items:center;gap:6px;font-size:11px;padding:3px 10px;border-radius:999px;border:1px solid var(--border);background:var(--panel);color:var(--dim);font-weight:600;white-space:nowrap;cursor:pointer}
.statuspill.ok{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 45%,transparent);background:color-mix(in srgb,var(--ok) 12%,transparent)} .statuspill.ok{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 45%,transparent);background:color-mix(in srgb,var(--ok) 12%,transparent)}
@@ -266,15 +266,15 @@ h1{margin:0;font-size:18px;color:var(--accent)}
.pages-bar{display:flex;align-items:center;gap:8px;cursor:pointer;user-select:none;padding:7px 0;margin-top:4px} .pages-bar{display:flex;align-items:center;gap:8px;cursor:pointer;user-select:none;padding:7px 0;margin-top:4px}
.pages-bar .pages-title{font-size:11.5px;color:var(--dim);font-weight:600} .pages-bar .pages-title{font-size:11.5px;color:var(--dim);font-weight:600}
.pages-bar .addbtn{margin-left:auto} .pages-bar .addbtn{margin-left:auto}
.mem-search{width:100%;box-sizing:border-box;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:5px 9px;font:inherit;font-size:12px;margin:0 0 6px} .mem-search{width:100%;box-sizing:border-box;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:3px;padding:5px 9px;font:inherit;font-size:12px;margin:0 0 6px}
.mem-search:focus{outline:none;border-color:var(--accent)} .mem-search:focus{outline:none;border-color:var(--accent)}
.mem-list{max-height:240px;overflow-y:auto;overscroll-behavior:contain} .mem-list{max-height:240px;overflow-y:auto;overscroll-behavior:contain}
.mem-more{font-size:11px;color:var(--dim);text-align:center;padding:6px 0 2px} .mem-more{font-size:11px;color:var(--dim);text-align:center;padding:6px 0 2px}
.ask-overlay{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:60;display:flex;align-items:center;justify-content:center;padding:20px} .ask-overlay{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:60;display:flex;align-items:center;justify-content:center;padding:20px}
.ask-box{background:var(--bg);border:1px solid var(--border);border-radius:10px;width:100%;max-width:420px;padding:20px;box-shadow:0 12px 40px rgba(0,0,0,.18)} .ask-box{background:var(--bg);border:1px solid var(--border);border-radius:4px;width:100%;max-width:420px;padding:20px;box-shadow:0 12px 40px rgba(0,0,0,.18)}
.ask-title{font-size:15px;font-weight:600;color:var(--text);margin-bottom:8px} .ask-title{font-size:15px;font-weight:600;color:var(--text);margin-bottom:8px}
.ask-msg{font-size:13px;color:var(--dim);line-height:1.5;white-space:pre-wrap;word-break:break-word} .ask-msg{font-size:13px;color:var(--dim);line-height:1.5;white-space:pre-wrap;word-break:break-word}
.ask-input{width:100%;box-sizing:border-box;margin-top:12px;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:9px 11px;font:inherit;font-size:13px} .ask-input{width:100%;box-sizing:border-box;margin-top:12px;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:3px;padding:9px 11px;font:inherit;font-size:13px}
.ask-input:focus{outline:none;border-color:var(--accent)} .ask-input:focus{outline:none;border-color:var(--accent)}
.ask-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:18px} .ask-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:18px}
.ask-actions button{margin:0;padding:7px 16px} .ask-actions button{margin:0;padding:7px 16px}
@@ -306,17 +306,17 @@ h1{margin:0;font-size:18px;color:var(--accent)}
.switch input:focus-visible + .slider{box-shadow:0 0 0 2px var(--bg),0 0 0 4px var(--accent)} .switch input:focus-visible + .slider{box-shadow:0 0 0 2px var(--bg),0 0 0 4px var(--accent)}
h2{margin:20px 0 8px;font-size:11px;color:var(--dim);text-transform:uppercase;letter-spacing:.1em;font-weight:600} h2{margin:20px 0 8px;font-size:11px;color:var(--dim);text-transform:uppercase;letter-spacing:.1em;font-weight:600}
details>summary::-webkit-details-marker{display:none} details>summary::-webkit-details-marker{display:none}
.preset-add{display:none;font-size:14px;padding:0 8px;margin:0 8px 0 auto;line-height:1.4;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:6px;cursor:pointer} .preset-add{display:none;font-size:14px;padding:0 8px;margin:0 8px 0 auto;line-height:1.4;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:3px;cursor:pointer}
.preset-add:hover{border-color:var(--accent);color:var(--accent)} .preset-add:hover{border-color:var(--accent);color:var(--accent)}
details[open]>summary>.preset-add{display:inline-block} details[open]>summary>.preset-add{display:inline-block}
.tag{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;background:var(--panel);border:1px solid var(--border)} .tag{display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;background:var(--panel);border:1px solid var(--border)}
.tag.ok{color:var(--ok);border-color:var(--ok)} .tag.ok{color:var(--ok);border-color:var(--ok)}
.tag.err{color:var(--err);border-color:var(--err)} .tag.err{color:var(--err);border-color:var(--err)}
button{background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:6px 10px;font:inherit;cursor:pointer;margin:2px} button{background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:3px;padding:6px 10px;font:inherit;cursor:pointer;margin:2px}
button:hover{border-color:var(--accent);color:var(--accent)} button:hover{border-color:var(--accent);color:var(--accent)}
button:disabled{opacity:.5;cursor:not-allowed} button:disabled{opacity:.5;cursor:not-allowed}
.row{display:flex;gap:4px;flex-wrap:wrap} .row{display:flex;gap:4px;flex-wrap:wrap}
.preset{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:6px 10px;border:1px solid var(--border);border-radius:6px;margin:4px 0;cursor:pointer;background:var(--panel)} .preset{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:6px 10px;border:1px solid var(--border);border-radius:3px;margin:4px 0;cursor:pointer;background:var(--panel)}
.preset:hover{border-color:var(--accent)} .preset:hover{border-color:var(--accent)}
.preset.active{border-color:var(--accent);color:var(--accent);border-width:1.5px;padding:5.5px 9.5px} .preset.active{border-color:var(--accent);color:var(--accent);border-width:1.5px;padding:5.5px 9.5px}
.preset>span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .preset>span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
@@ -332,9 +332,9 @@ button:disabled{opacity:.5;cursor:not-allowed}
/* La carte (.slist) laisse volontairement déborder son contenu (menus), elle ne /* La carte (.slist) laisse volontairement déborder son contenu (menus), elle ne
rogne donc pas ses coins : c'est la LIGNE de bord qui porte l'arrondi, sinon rogne donc pas ses coins : c'est la LIGNE de bord qui porte l'arrondi, sinon
le voile dépasse l'angle de la carte. */ le voile dépasse l'angle de la carte. */
.rowlist:first-child>.preset:first-child{border-radius:8px 8px 0 0} .rowlist:first-child>.preset:first-child{border-radius:4px 4px 0 0}
.rowlist:last-child>.preset:last-child{border-radius:0 0 8px 8px} .rowlist:last-child>.preset:last-child{border-radius:0 0 4px 4px}
.rowlist:first-child:last-child>.preset:only-child{border-radius:8px} .rowlist:first-child:last-child>.preset:only-child{border-radius:4px}
/* Liseré de gauche : un trait DROIT (pas un arc) qui court sur TOUTE la hauteur /* Liseré de gauche : un trait DROIT (pas un arc) qui court sur TOUTE la hauteur
de la ligne. Donc pas de box-shadow interne (l'arrondi le courberait) mais un de la ligne. Donc pas de box-shadow interne (l'arrondi le courberait) mais un
élément à part, aux bords francs, que l'arrondi de la ligne de bord se élément à part, aux bords francs, que l'arrondi de la ligne de bord se
@@ -386,15 +386,15 @@ button:disabled{opacity:.5;cursor:not-allowed}
margin:0 -2px 0 0;padding:4px;line-height:0;background:transparent;border:none;opacity:.55} margin:0 -2px 0 0;padding:4px;line-height:0;background:transparent;border:none;opacity:.55}
.preset-edit:hover{opacity:1;color:var(--accent);background:transparent} .preset-edit:hover{opacity:1;color:var(--accent);background:transparent}
.preset-meta{display:flex;align-items:center;gap:6px;margin-top:3px;flex-wrap:wrap} .preset-meta{display:flex;align-items:center;gap:6px;margin-top:3px;flex-wrap:wrap}
.qtag{flex-shrink:0;font-size:9px;padding:1px 6px;border:1px solid var(--border);color:var(--dim);border-radius:8px;letter-spacing:.03em;text-transform:uppercase} .qtag{flex-shrink:0;font-size:9px;padding:1px 6px;border:1px solid var(--border);color:var(--dim);border-radius:4px;letter-spacing:.03em;text-transform:uppercase}
.rtag{flex-shrink:0;display:inline-flex;align-items:center;gap:3px;font-size:9px;padding:1px 6px;border:1px solid var(--border);color:var(--dim);border-radius:8px;letter-spacing:.03em} .rtag{flex-shrink:0;display:inline-flex;align-items:center;gap:3px;font-size:9px;padding:1px 6px;border:1px solid var(--border);color:var(--dim);border-radius:4px;letter-spacing:.03em}
.btag{flex-shrink:0;display:inline-flex;align-items:center;gap:3px;font-size:9px;padding:1px 6px;border:1px solid var(--border);color:var(--dim);border-radius:8px;letter-spacing:.03em} .btag{flex-shrink:0;display:inline-flex;align-items:center;gap:3px;font-size:9px;padding:1px 6px;border:1px solid var(--border);color:var(--dim);border-radius:4px;letter-spacing:.03em}
/* Petit « ? » d'aide : bulle au survol/clic au lieu d'un pavé de texte permanent. */ /* Petit « ? » d'aide : bulle au survol/clic au lieu d'un pavé de texte permanent. */
.help{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;margin-left:6px;flex-shrink:0;font-size:10px;font-weight:600;line-height:1;color:var(--dim);border:1px solid var(--border);border-radius:50%;cursor:help;user-select:none;vertical-align:middle} .help{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;margin-left:6px;flex-shrink:0;font-size:10px;font-weight:600;line-height:1;color:var(--dim);border:1px solid var(--border);border-radius:50%;cursor:help;user-select:none;vertical-align:middle}
.help:hover{color:var(--accent);border-color:var(--accent)} .help:hover{color:var(--accent);border-color:var(--accent)}
/* La bulle est ancrée à la ligne (.switchrow position:relative), pas au « ? », /* La bulle est ancrée à la ligne (.switchrow position:relative), pas au « ? »,
pour rester alignée au bord gauche du panneau et ne pas déborder. */ pour rester alignée au bord gauche du panneau et ne pas déborder. */
.help>.tip{display:none;position:absolute;z-index:40;left:0;right:0;top:calc(100% + 2px);padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;font-size:11px;font-weight:400;line-height:1.4;color:var(--text);box-shadow:0 4px 16px rgba(0,0,0,.15);white-space:normal;text-align:left;text-transform:none;letter-spacing:normal;font-family:var(--font);cursor:auto} .help>.tip{display:none;position:absolute;z-index:40;left:0;right:0;top:calc(100% + 2px);padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:3px;font-size:11px;font-weight:400;line-height:1.4;color:var(--text);box-shadow:0 4px 16px rgba(0,0,0,.15);white-space:normal;text-align:left;text-transform:none;letter-spacing:normal;font-family:var(--font);cursor:auto}
.help:hover>.tip,.help:focus>.tip,.help.open>.tip{display:block} .help:hover>.tip,.help:focus>.tip,.help.open>.tip{display:block}
.kv{display:flex;justify-content:space-between;gap:10px;padding:9px 12px;border-bottom:1px solid var(--border);font-size:13px;min-height:38px;align-items:center} .kv{display:flex;justify-content:space-between;gap:10px;padding:9px 12px;border-bottom:1px solid var(--border);font-size:13px;min-height:38px;align-items:center}
.kv span:first-child{color:var(--text);flex-shrink:0} .kv span:first-child{color:var(--text);flex-shrink:0}
@@ -409,7 +409,7 @@ button:disabled{opacity:.5;cursor:not-allowed}
#chat{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:20px;display:flex;flex-direction:column;gap:14px} #chat{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:20px;display:flex;flex-direction:column;gap:14px}
:root{--col:max(20px,calc(50% - 450px))} :root{--col:max(20px,calc(50% - 450px))}
@media (max-width:720px){#chat{padding:60px 8px 12px}} @media (max-width:720px){#chat{padding:60px 8px 12px}}
.msg{max-width:80%;padding:10px 14px;border-radius:10px;white-space:pre-wrap;word-wrap:break-word;line-height:1.45} .msg{max-width:80%;padding:10px 14px;border-radius:4px;white-space:pre-wrap;word-wrap:break-word;line-height:1.45}
.msg.assistant,.msg.reasoning{white-space:normal} .msg.assistant,.msg.reasoning{white-space:normal}
.msg .body{overflow-wrap:anywhere;word-break:break-word} .msg .body{overflow-wrap:anywhere;word-break:break-word}
.msg.user{align-self:flex-end;background:var(--bubble);color:var(--text)} .msg.user{align-self:flex-end;background:var(--bubble);color:var(--text)}
@@ -425,8 +425,8 @@ button:disabled{opacity:.5;cursor:not-allowed}
.msg .body li{margin:.15em 0} .msg .body li{margin:.15em 0}
.msg .body li>p{margin:0} .msg .body li>p{margin:0}
.msg.assistant .body code{background:var(--code-bg);padding:1px 5px;border-radius:3px;font-size:.9em;overflow-wrap:anywhere;word-break:break-word} .msg.assistant .body code{background:var(--code-bg);padding:1px 5px;border-radius:3px;font-size:.9em;overflow-wrap:anywhere;word-break:break-word}
.msg.assistant .body pre,.msg.reasoning .body pre{position:relative;background:var(--code-bg);border:1px solid var(--border);border-radius:6px;padding:10px;overflow-x:auto;max-width:100%;margin:.5em 0} .msg.assistant .body pre,.msg.reasoning .body pre{position:relative;background:var(--code-bg);border:1px solid var(--border);border-radius:3px;padding:10px;overflow-x:auto;max-width:100%;margin:.5em 0}
.copybtn{position:absolute;bottom:6px;right:6px;font-size:11px;padding:3px 8px;margin:0;background:var(--panel);border:1px solid var(--border);color:var(--dim);border-radius:5px;opacity:.5;transition:opacity .15s,color .15s} .copybtn{position:absolute;bottom:6px;right:6px;font-size:11px;padding:3px 8px;margin:0;background:var(--panel);border:1px solid var(--border);color:var(--dim);border-radius:3px;opacity:.5;transition:opacity .15s,color .15s}
.msg.assistant .body pre:hover .copybtn,.msg.reasoning .body pre:hover .copybtn{opacity:1} .msg.assistant .body pre:hover .copybtn,.msg.reasoning .body pre:hover .copybtn{opacity:1}
.copybtn:hover{border-color:var(--accent);color:var(--accent)} .copybtn:hover{border-color:var(--accent);color:var(--accent)}
.copybtn.done{color:var(--ok);border-color:var(--ok);opacity:1} .copybtn.done{color:var(--ok);border-color:var(--ok);opacity:1}
@@ -460,7 +460,7 @@ button:disabled{opacity:.5;cursor:not-allowed}
.diff-count .a{color:var(--add)} .diff-count .a{color:var(--add)}
.diff-count .d{color:var(--del)} .diff-count .d{color:var(--del)}
.msg.tool .tool-sub{font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.1em;margin:8px 0 4px} .msg.tool .tool-sub{font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.1em;margin:8px 0 4px}
.msg.tool pre{position:relative;background:var(--code-bg);border:1px solid var(--border);border-radius:6px;padding:8px 10px;overflow:auto;margin:0;max-height:280px} .msg.tool pre{position:relative;background:var(--code-bg);border:1px solid var(--border);border-radius:3px;padding:8px 10px;overflow:auto;margin:0;max-height:280px}
.msg.tool pre code{background:transparent;padding:0;font-size:.82em;line-height:1.4;white-space:pre} .msg.tool pre code{background:transparent;padding:0;font-size:.82em;line-height:1.4;white-space:pre}
.msg.tool .tool-caret{color:var(--accent);animation:toolblink 1s step-end infinite} .msg.tool .tool-caret{color:var(--accent);animation:toolblink 1s step-end infinite}
.msg.tool .tool-wait{font-size:12px;color:var(--dim);margin-top:8px;animation:toolblink 1.2s ease-in-out infinite} .msg.tool .tool-wait{font-size:12px;color:var(--dim);margin-top:8px;animation:toolblink 1.2s ease-in-out infinite}
@@ -598,7 +598,7 @@ details>summary+.subhead{margin-top:4px}
rapport à la ligne, un débordement caché les TRONQUERAIT — elles doivent rapport à la ligne, un débordement caché les TRONQUERAIT — elles doivent
pouvoir déborder de la carte. Les lignes n'ont pas de fond propre, l'arrondi pouvoir déborder de la carte. Les lignes n'ont pas de fond propre, l'arrondi
n'a donc rien à rogner. */ n'a donc rien à rogner. */
.slist{background:var(--panel);border:1px solid var(--border);border-radius:9px; .slist{background:var(--panel);border:1px solid var(--border);border-radius:4px;
overflow:visible;margin:0 0 8px} overflow:visible;margin:0 0 8px}
.slist:last-child{margin-bottom:0} .slist:last-child{margin-bottom:0}
/* Contenu libre (champ + boutons) : le rembourrage passe sur la carte. */ /* Contenu libre (champ + boutons) : le rembourrage passe sur la carte. */
@@ -647,10 +647,10 @@ details>summary+.subhead{margin-top:4px}
/* --- Champs et boutons --- */ /* --- Champs et boutons --- */
.sctl,.io-input{width:100%;background:var(--bg);color:var(--text);border:1px solid var(--border); .sctl,.io-input{width:100%;background:var(--bg);color:var(--text);border:1px solid var(--border);
border-radius:7px;padding:7px 9px;font:inherit;font-size:12.5px} border-radius:3px;padding:7px 9px;font:inherit;font-size:12.5px}
.sctl:focus,.io-input:focus{outline:none;border-color:var(--dim)} .sctl:focus,.io-input:focus{outline:none;border-color:var(--dim)}
textarea.sctl{min-height:76px;resize:vertical;line-height:1.5} textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
.side button:not(.preset-edit):not(.addbtn):not(.preset-add):not(.copybtn):not(.iconbtn){font-size:12px;padding:6px 10px;border-radius:7px} .side button:not(.preset-edit):not(.addbtn):not(.preset-add):not(.copybtn):not(.iconbtn){font-size:12px;padding:6px 10px;border-radius:3px}
.side .row{display:flex;gap:6px;align-items:center;margin:6px 0} .side .row{display:flex;gap:6px;align-items:center;margin:6px 0}
.side .row .sctl,.side .row .io-input{flex:1;min-width:0} .side .row .sctl,.side .row .io-input{flex:1;min-width:0}
/* Bouton d'action à côté d'un champ (copier, ouvrir) : un CARRÉ à la hauteur du /* Bouton d'action à côté d'un champ (copier, ouvrir) : un CARRÉ à la hauteur du
@@ -658,7 +658,7 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
posait de travers et changeait de largeur d'une police à l'autre. */ posait de travers et changeait de largeur d'une police à l'autre. */
.iconbtn{flex:none;display:inline-flex;align-items:center;justify-content:center; .iconbtn{flex:none;display:inline-flex;align-items:center;justify-content:center;
width:34px;height:34px;padding:0;margin:0;color:var(--dim);background:var(--panel); width:34px;height:34px;padding:0;margin:0;color:var(--dim);background:var(--panel);
border:1px solid var(--border);border-radius:9px;cursor:pointer} border:1px solid var(--border);border-radius:4px;cursor:pointer}
.iconbtn:hover{color:var(--text);border-color:var(--dim);background:var(--accent-bg)} .iconbtn:hover{color:var(--text);border-color:var(--dim);background:var(--accent-bg)}
.iconbtn svg{width:15px;height:15px;display:block} .iconbtn svg{width:15px;height:15px;display:block}
.side .btn-danger-o{border-color:var(--err);color:var(--err)} .side .btn-danger-o{border-color:var(--err);color:var(--err)}
@@ -701,10 +701,10 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
#sendhint{padding:0 12px 6px;font-size:10px;text-align:center;opacity:.7} #sendhint{padding:0 12px 6px;font-size:10px;text-align:center;opacity:.7}
/* Moteur pas encore prêt : l'aide clavier cède la place à l'état du chargement. */ /* Moteur pas encore prêt : l'aide clavier cède la place à l'état du chargement. */
#sendhint.waiting{opacity:1;color:var(--dim)} #sendhint.waiting{opacity:1;color:var(--dim)}
#input{flex:1;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:10px;font:inherit;resize:none;min-height:56px;max-height:200px;overflow-y:hidden} #input{flex:1;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:3px;padding:10px;font:inherit;resize:none;min-height:56px;max-height:200px;overflow-y:hidden}
#input:focus{outline:none;border-color:var(--accent)} #input:focus{outline:none;border-color:var(--accent)}
.muted{color:var(--dim);font-size:13px} .muted{color:var(--dim);font-size:13px}
#toast{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);z-index:50;background:var(--panel);border:1px solid var(--border);padding:8px 16px;border-radius:8px;opacity:0;transition:opacity .2s;pointer-events:none;box-shadow:0 8px 30px rgba(0,0,0,.18)} #toast{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);z-index:50;background:var(--panel);border:1px solid var(--border);padding:8px 16px;border-radius:4px;opacity:0;transition:opacity .2s;pointer-events:none;box-shadow:0 8px 30px rgba(0,0,0,.18)}
#toast.show{opacity:1} #toast.show{opacity:1}
@keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}} @keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}
/* iOS Safari auto-zoome au focus sur tout champ < 16px. On force 16px sur les /* iOS Safari auto-zoome au focus sur tout champ < 16px. On force 16px sur les
@@ -728,7 +728,7 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
rétrécit le contenu. Padding interne normal → largeur utile pleine. */ rétrécit le contenu. Padding interne normal → largeur utile pleine. */
.side{width:300px;max-width:300px;left:env(safe-area-inset-left);padding:calc(14px + env(safe-area-inset-top)) 14px calc(14px + env(safe-area-inset-bottom)) 14px} .side{width:300px;max-width:300px;left:env(safe-area-inset-left);padding:calc(14px + env(safe-area-inset-top)) 14px calc(14px + env(safe-area-inset-bottom)) 14px}
#chat{padding:48px 10px 6px;gap:7px} #chat{padding:48px 10px 6px;gap:7px}
.msg{padding:6px 10px;border-radius:8px;max-width:78%} .msg{padding:6px 10px;border-radius:4px;max-width:78%}
#chat .msg.assistant,#chat .msg.reasoning{max-width:88%} #chat .msg.assistant,#chat .msg.reasoning{max-width:88%}
#inputbar{padding:5px 8px;gap:6px} #inputbar{padding:5px 8px;gap:6px}
/* PAS de font-size < 16px ici : iOS Safari zoome à la sélection du champ. On /* PAS de font-size < 16px ici : iOS Safari zoome à la sélection du champ. On
@@ -755,7 +755,7 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
.lc-mode-t{font-size:13px;font-weight:600;color:var(--text);display:flex;align-items:center;gap:6px} .lc-mode-t{font-size:13px;font-weight:600;color:var(--text);display:flex;align-items:center;gap:6px}
/* « conseillé » : pilule PLEINE (texte inversé). En contour, elle héritait du /* « conseillé » : pilule PLEINE (texte inversé). En contour, elle héritait du
gris atténué du thème monochrome et devenait illisible en mode sombre. */ gris atténué du thème monochrome et devenait illisible en mode sombre. */
.lc-reco{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;background:var(--text);color:var(--bg);border:1px solid var(--text);border-radius:8px;padding:1px 6px} .lc-reco{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;background:var(--text);color:var(--bg);border:1px solid var(--text);border-radius:4px;padding:1px 6px}
.lc-mode-d{font-size:11px;color:var(--dim);margin-top:3px} .lc-mode-d{font-size:11px;color:var(--dim);margin-top:3px}
/* Hauteur réservée : la ligne d'état arrive après l'appel réseau, sans elle /* Hauteur réservée : la ligne d'état arrive après l'appel réseau, sans elle
les trois cartes grandissaient d'un cran une fois l'état connu. */ les trois cartes grandissaient d'un cran une fois l'état connu. */
@@ -784,7 +784,7 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
affichée avec une seule carte). */ affichée avec une seule carte). */
.pe-group[hidden]{display:none} .pe-group[hidden]{display:none}
.pe-gh{font-size:12px;font-weight:600;color:var(--dim);padding:0 6px} .pe-gh{font-size:12px;font-weight:600;color:var(--dim);padding:0 6px}
.pe-list{background:var(--panel);border:1px solid var(--border);border-radius:12px;overflow:hidden} .pe-list{background:var(--panel);border:1px solid var(--border);border-radius:3px;overflow:hidden}
/* Ligne : libellé à gauche, contrôle à droite, séparateur fin entre lignes. */ /* Ligne : libellé à gauche, contrôle à droite, séparateur fin entre lignes. */
.pe-row{display:flex;align-items:center;gap:14px;min-height:48px;padding:9px 15px} .pe-row{display:flex;align-items:center;gap:14px;min-height:48px;padding:9px 15px}
/* Séparateur porté par la ligne SUIVANTE (border-top), pas par la précédente /* Séparateur porté par la ligne SUIVANTE (border-top), pas par la précédente
@@ -837,9 +837,9 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
.gpu-legend i.alt{background:color-mix(in srgb,var(--accent) 42%,transparent)} .gpu-legend i.alt{background:color-mix(in srgb,var(--accent) 42%,transparent)}
.gpu-shares{display:flex;flex-wrap:wrap;gap:10px} .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{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{width:62px;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:3px;padding:5px 7px;font:inherit;text-align:right}
.gpu-share input:focus{outline:none;border-color:var(--accent)} .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{width:100%;box-sizing:border-box;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:4px;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-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{border:none;background:transparent;padding:6px 0;font-size:15px}
.pe-row.stack.plain .pe-inp:focus{box-shadow:none} .pe-row.stack.plain .pe-inp:focus{box-shadow:none}
@@ -850,13 +850,13 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
/* Le bouton reprend EXACTEMENT le cadre du champ (mêmes coins, même bordure) : /* 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 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. */ 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{flex:none;margin:0;border:1px solid var(--border);border-radius:0 4px 4px 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 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:focus-within .pe-inp,.pe-inpbtn:focus-within button{border-color:var(--accent)}
.pe-inpbtn button:disabled{opacity:.5;cursor:default} .pe-inpbtn button:disabled{opacity:.5;cursor:default}
/* Contrôle segmenté (moteur) façon iOS. */ /* 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{display:flex;gap:3px;background:color-mix(in srgb,var(--dim) 16%,transparent);border-radius:4px;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{flex:1;text-align:center;font-size:13px;font-weight:500;padding:7px 4px;border-radius:3px;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>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 input{display:none}
.pe-seg .be-note{display:none} .pe-seg .be-note{display:none}
@@ -882,7 +882,7 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
.pe-switch input:checked+.pe-sl::before{transform:translateX(16px);background:var(--bg)} .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)} .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. */ /* 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{background:var(--panel);border:1px solid var(--border);border-radius:3px;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{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::-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>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}
@@ -905,7 +905,7 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
align-items:center;gap:8px;flex-wrap:nowrap} 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-l{display:flex;align-items:center;gap:8px;min-width:0}
.pe-foot .foot-r{display:flex;gap:8px;flex:none} .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; .pe-btn{margin:0;padding:7px 15px;font-size:12.5px;border-radius:4px;font-weight:600;cursor:pointer;
border:1px solid var(--border);background:var(--panel);color:var(--text);white-space:nowrap} border:1px solid var(--border);background:var(--panel);color:var(--text);white-space:nowrap}
.pe-btn:hover{border-color:var(--dim)} .pe-btn:hover{border-color:var(--dim)}
.pe-btn.primary{border-color:var(--accent);background:var(--accent);color:var(--bg)} .pe-btn.primary{border-color:var(--accent);background:var(--accent);color:var(--bg)}
@@ -914,7 +914,7 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
autant que « enregistrer » alors que c'est l'action rare — et à trois boutons autant que « enregistrer » alors que c'est l'action rare — et à trois boutons
larges, le pied passait à deux lignes sur téléphone. */ 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; .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} margin:0;padding:0;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--err);cursor:pointer}
.pe-del svg{width:15px;height:15px;display:block} .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)} .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 /* Option d'une confirmation : une PASTILLE à cocher, pas une case système suivie
@@ -945,7 +945,7 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
@media (prefers-reduced-motion:reduce){ @media (prefers-reduced-motion:reduce){
.modal-ov,.ask-overlay,.modal-card,.ask-box{transition:none} .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-card{position:relative;background:var(--bg);border:1px solid var(--border);border-radius:3px;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{padding:14px 16px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center}
.modal-head strong{font-size:14px} .modal-head strong{font-size:14px}
/* Croix de fermeture : un CARRÉ, pas un rectangle. Avec un padding asymétrique /* Croix de fermeture : un CARRÉ, pas un rectangle. Avec un padding asymétrique
@@ -966,7 +966,7 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
preset au nouveau SOUS LES YEUX, une fois le chargement terminé. */ 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-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; .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} align-items:center;justify-content:center;background:var(--bg);border-radius:0 0 5px 5px}
.modal-ov.loading .modal-load{display:flex} .modal-ov.loading .modal-load{display:flex}
/* visibility (et non display) : le formulaire garde sa place, donc la carte a /* 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. */ déjà sa taille finale — la révéler ne provoque plus d'à-coup de hauteur. */
@@ -987,10 +987,10 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
.fld-l{font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--dim)} .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-l .opt{text-transform:none;letter-spacing:0;font-weight:400;opacity:.65}
.fld-hint{font-size:11px;color:var(--dim);margin-top:2px} .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{width:100%;box-sizing:border-box;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:3px;padding:9px 11px;font:inherit}
.ctl:focus{outline:none;border-color:var(--accent)} .ctl:focus{outline:none;border-color:var(--accent)}
.ta{min-height:220px;resize:vertical;line-height:1.5} .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{border:1px solid var(--border);border-radius:4px;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)} .card-h{font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--accent)}
.row{display:flex;gap:6px} .row{display:flex;gap:6px}
.row .ctl{flex:1;min-width:0} .row .ctl{flex:1;min-width:0}
@@ -1000,7 +1000,7 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
.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{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} .input-btn .btn-in:hover{border-left:0}
.seg{display:flex;gap:6px} .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{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:4px;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:hover{border-color:var(--accent)}
.seg>label:has(input:checked){border-color:var(--accent);background:var(--accent-bg);color:var(--accent)} .seg>label:has(input:checked){border-color:var(--accent);background:var(--accent-bg);color:var(--accent)}
.seg input{display:none} .seg input{display:none}
@@ -1012,12 +1012,12 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
.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{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 #lc-log-caret{font-size:9px;transition:transform .12s;display:inline-block}
.lc-logbar.open #lc-log-caret{transform:rotate(90deg)} .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-log{background:var(--panel);border:1px solid var(--border);border-radius:3px;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} .lc-spin{display:inline-block;animation:spin 1s linear infinite}
/* Serveurs MCP */ /* Serveurs MCP */
.mcp-list{display:flex;flex-direction:column;gap:4px} .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{display:flex;align-items:center;gap:8px;padding:6px 10px;border:1px solid var(--border);border-radius:3px;background:var(--panel);cursor:pointer}
.mcp-row:hover{border-color:var(--accent)} .mcp-row:hover{border-color:var(--accent)}
.mcp-row.off{opacity:.6} .mcp-row.off{opacity:.6}
.mcp-dot{flex:none;width:8px;height:8px;border-radius:50%} .mcp-dot{flex:none;width:8px;height:8px;border-radius:50%}
@@ -1027,7 +1027,7 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
.mcp-info{flex:1;min-width:0} .mcp-info{flex:1;min-width:0}
.mcp-name{font-size:12px;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .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-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-tag{font-size:10px;padding:1px 7px;border-radius:4px;border:1px solid var(--border);color:var(--dim);text-transform:uppercase;letter-spacing:.04em}
/* Version du moteur : même pastille, mais SANS majuscules forcées — un numéro /* Version du moteur : même pastille, mais SANS majuscules forcées — un numéro
de build et un hash de commit se recopient et se comparent à l'œil, et de build et un hash de commit se recopient et se comparent à l'œil, et
« AAAA111 » n'est pas le commit « aaaa111 ». Le monospace vient de là aussi. */ « AAAA111 » n'est pas le commit « aaaa111 ». Le monospace vient de là aussi. */
@@ -1038,26 +1038,34 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
.mcp-edit{flex:none;padding:2px 5px;font-size:13px;line-height:1;background:transparent;border:none;opacity:.5} .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)} .mcp-edit:hover{opacity:1;color:var(--accent)}
/* Liste des outils MCP dans la modale */ /* Liste des outils MCP dans la modale */
/* Sélecteur d'intensité du raisonnement, dans la barre de saisie. Discret par
défaut (même poids visuel que les icônes voisines), il ne s'affirme qu'au
survol ou quand une intensité est imposée. */
#think-effort{flex:none;align-self:center;height:22px;padding:0 4px;font-size:11px;
color:var(--dim);background:transparent;border:1px solid transparent;border-radius:4px;cursor:pointer}
#think-effort:hover:not(:disabled){color:var(--text);border-color:var(--border)}
#think-effort:disabled{opacity:.4;cursor:not-allowed}
.mcp-actions{display:flex;gap:6px;margin-top:6px} .mcp-actions{display:flex;gap:6px;margin-top:6px}
.mcp-cat-tabs{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px} .mcp-cat-tabs{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px}
.mcp-cat{font-size:11px;padding:2px 9px;border-radius:9px;border:1px solid var(--border);background:transparent;color:var(--dim);cursor:pointer} .mcp-cat{font-size:11px;padding:2px 9px;border-radius:4px;border:1px solid var(--border);background:transparent;color:var(--dim);cursor:pointer}
.mcp-cat:hover{border-color:var(--accent);color:var(--text)} .mcp-cat:hover{border-color:var(--accent);color:var(--text)}
.mcp-cat.on{border-color:var(--accent);color:var(--accent)} .mcp-cat.on{border-color:var(--accent);color:var(--accent)}
.mcp-cat-list{display:flex;flex-direction:column;gap:4px;max-height:52vh;overflow:auto} .mcp-cat-list{display:flex;flex-direction:column;gap:4px;max-height:52vh;overflow:auto}
.mcp-cat-row{display:flex;align-items:center;gap:8px;padding:7px 10px;border:1px solid var(--border);border-radius:6px;background:var(--panel)} .mcp-cat-row{display:flex;align-items:center;gap:8px;padding:7px 10px;border:1px solid var(--border);border-radius:3px;background:var(--panel)}
.mcp-cat-row:hover{border-color:var(--accent)} .mcp-cat-row:hover{border-color:var(--accent)}
.mcp-cat-desc{font-size:11px;color:var(--dim);margin-top:2px;line-height:1.35} .mcp-cat-desc{font-size:11px;color:var(--dim);margin-top:2px;line-height:1.35}
.mcp-cat-cmd{font-size:10px;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:340px} .mcp-cat-cmd{font-size:10px;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:340px}
.mcp-cat-use{flex:none} .mcp-cat-use{flex:none}
.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-tools-list{display:flex;flex-direction:column;gap:2px;max-height:240px;overflow:auto;border:1px solid var(--border);border-radius:3px;padding:6px}
.mcp-tool-row{display:flex;align-items:center;gap:10px;padding:4px 6px;border-radius:5px;cursor:pointer} .mcp-tool-row{display:flex;align-items:center;gap:10px;padding:4px 6px;border-radius:3px;cursor:pointer}
.mcp-tool-row:hover{background:var(--panel)} .mcp-tool-row:hover{background:var(--panel)}
.mcp-tool-switch{flex:none;transform:scale(.85)} .mcp-tool-switch{flex:none;transform:scale(.85)}
.mcp-tool-name{font-size:12px;color:var(--text);word-break:break-all} .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 /* Accès OpenAI / Accès distant : champs lecture seule (URL, clé) + libellés
compacts, pour une présentation homogène et sans surcharge visuelle. */ 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} .io-input{flex:1;min-width:0;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:4px;padding:8px 10px;font:inherit;font-size:13px}
/* Modale MCP (ajout/édition) : les champs des groupes stdio/http n'avaient aucun /* 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. espacement vertical (le conteneur n'est pas un flex à gap) → ils se touchaient.
@@ -1084,7 +1092,7 @@ h1{color:var(--text);font-weight:600;letter-spacing:-.01em}
/* Réponses de l'assistant : pas de cadre, pas de fond — juste du texte. */ /* 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} #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. */ /* 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{background:var(--bubble);color:var(--text);border-radius:3px;padding:11px 18px;max-width:75%;line-height:1.6}
#chat .msg.user .label{color:var(--dim)} #chat .msg.user .label{color:var(--dim)}
/* Libellés : l'amont les masquait tous ici, les mots « user » / « assistant » /* 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 n'ajoutant rien à l'alignement. Ils portent désormais un avatar et un prénom
@@ -1110,10 +1118,10 @@ html[data-hide-stats="1"] #chat .msg .statline{display:none}
@keyframes statin{from{opacity:0;transform:translateY(3px)}to{opacity:.75;transform:none}} @keyframes statin{from{opacity:0;transform:translateY(3px)}to{opacity:.75;transform:none}}
@media (prefers-reduced-motion:reduce){#chat .msg .statline-in{animation:none}} @media (prefers-reduced-motion:reduce){#chat .msg .statline-in{animation:none}}
/* Raisonnement / outils : encart gris discret plutôt qu'un cadre pointillé. */ /* 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,#chat .msg.tool{background:var(--panel);border:1px solid var(--border);border-radius:3px;color:var(--dim);padding:12px 16px}
#chat .msg.reasoning{color:var(--dim)} #chat .msg.reasoning{color:var(--dim)}
#chat .msg.tool .tool-head{color:var(--text)} #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 pre,#chat .msg.reasoning .body pre,#chat .msg.tool pre{border-radius:3px;padding:12px 14px}
#chat .msg.assistant .body a{text-decoration:underline;text-underline-offset:2px} #chat .msg.assistant .body a{text-decoration:underline;text-underline-offset:2px}
/* Composer = UNE seule carte posée PAR-DESSUS le fil : la conversation /* 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) continue de défiler DERRIÈRE elle et s'efface en fondu (dégradé vers le fond)
@@ -1141,7 +1149,7 @@ html[data-hide-stats="1"] #chat .msg .statline{display:none}
trou en PWA iOS. */ trou en PWA iOS. */
#chat{padding-bottom:calc(var(--composer-h,170px) + 12px)} #chat{padding-bottom:calc(var(--composer-h,170px) + 12px)}
#scrollbtn{bottom:calc(var(--composer-h,170px) + 10px);border-radius:999px} #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} #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:4px 4px 0 0}
#input{background:transparent;border:none;border-radius:0;padding:9px 0;min-height:36px;line-height:18px;max-height:160px} #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 /* Pas de cadre qui s'assombrit au focus : la carte garde exactement la même
bordure, seule la saisie change (curseur). */ bordure, seule la saisie change (curseur). */
@@ -1151,7 +1159,7 @@ html[data-hide-stats="1"] #chat .msg .statline{display:none}
(le navigateur l'écrase, d'où les coins bizarres) : on laisse donc la carte (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 découper la jauge dans sa propre forme — la barre est bien épaisse ET rognée
exactement sur la courbe, symétrique du haut. */ 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} #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 8px 8px;overflow:hidden}
/* ⚠️ NE PAS piloter la position du composer en JS quand le clavier s'ouvre /* ⚠️ 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 (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 coque à visualViewport.height, ni décaler le composer de la hauteur du
@@ -1237,16 +1245,16 @@ html[data-hide-stats="1"] #chat .msg .statline{display:none}
#dropveil{position:absolute;inset:0;z-index:20;display:none;align-items:center; #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} justify-content:center;padding:24px;background:rgba(0,0,0,.28);pointer-events:none}
#dropveil.show{display:flex} #dropveil.show{display:flex}
#dropveil>div{padding:22px 30px;border:2px dashed var(--border);border-radius:20px; #dropveil>div{padding:22px 30px;border:2px dashed var(--border);border-radius:3px;
background:var(--panel);color:var(--text);font-size:14px;box-shadow:0 8px 30px rgba(0,0,0,.18)} 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. */ /* Reste de l'UI : coins arrondis et contrastes doux, dans le même esprit. */
button,.mem-search{border-radius:10px} button,.mem-search{border-radius:4px}
/* Champ + bouton accolés : on ne garde l'arrondi que sur les coins extérieurs. */ /* 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 .pe-inp{border-radius:4px 0 0 4px}
.pe-inpbtn button{border-radius:0 10px 10px 0} .pe-inpbtn button{border-radius:0 4px 4px 0}
.input-btn .ctl{border-radius:10px 0 0 10px} .input-btn .ctl{border-radius:4px 0 0 4px}
.input-btn .btn-in{border-radius:0 10px 10px 0} .input-btn .btn-in{border-radius:0 4px 4px 0}
.preset{border-radius:12px} .preset{border-radius:3px}
/* Les presets restent des cartes (fond + bordure) : à plat, sans fond ni /* 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 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 signale par sa bordure — un simple voile d'accent (7 % de blanc sur fond
@@ -1267,7 +1275,7 @@ button:hover{border-color:var(--dim);color:var(--text)}
/* ===== Fenêtres et sélecteurs ===================================== /* ===== Fenêtres et sélecteurs =====================================
Coins arrondis et ombre douce plutôt qu'un cadre marqué. */ 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,.modal-box,.pe-list,.pe-adv{border-radius:3px}
.modal-card,.ask-box{box-shadow:0 8px 30px rgba(0,0,0,.12)} .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. */ /* 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)} .pe-seg>label:has(input:checked),.seg>label:has(input:checked){background:var(--text);border-color:var(--text);color:var(--bg)}
@@ -1310,7 +1318,7 @@ button:hover{border-color:var(--dim);color:var(--text)}
/* Barre latérale : pile de cartes, sans filet ni bordure de colonne. */ /* Barre latérale : pile de cartes, sans filet ni bordure de colonne. */
.side{width:312px;border-right:0;padding:0;background:transparent; .side{width:312px;border-right:0;padding:0;background:transparent;
display:flex;flex-direction:column;gap:12px;overflow-y:auto;overscroll-behavior:contain} 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; .sidecard,.side>details{background:var(--panel);border:0;border-top:0;border-radius:3px;
box-shadow:var(--shadow);margin:0;padding:2px 16px} box-shadow:var(--shadow);margin:0;padding:2px 16px}
.sidecard{padding:15px 18px 14px;display:flex;flex-direction:column;gap:2px} .sidecard{padding:15px 18px 14px;display:flex;flex-direction:column;gap:2px}
.side>details[open]{padding-bottom:14px} .side>details[open]{padding-bottom:14px}
@@ -1320,10 +1328,10 @@ button:hover{border-color:var(--dim);color:var(--text)}
.side-sep{border-top:0;margin:10px 0 0;padding:0 6px} .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 /* Blocs encastrés DANS une carte : un ton plus chaud, sans bordure — deux
cadres imbriqués alourdiraient inutilement. */ cadres imbriqués alourdiraient inutilement. */
.side .slist{background:var(--row-bg);border-color:transparent;border-radius:14px} .side .slist{background:var(--row-bg);border-color:transparent;border-radius:3px}
/* Colonne principale : une seule grande carte qui contient fil et saisie. */ /* Colonne principale : une seule grande carte qui contient fil et saisie. */
.main{background:var(--panel);border-radius:20px;box-shadow:var(--shadow);overflow:hidden} .main{background:var(--panel);border-radius:3px;box-shadow:var(--shadow);overflow:hidden}
#chat{padding-top:26px;padding-inline:max(20px,calc(50% - 360px))} #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 /* 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. */ vers le PANNEAU, pas vers le fond de la coque. */
@@ -1345,13 +1353,13 @@ button:hover{border-color:var(--dim);color:var(--text)}
3 px sur le seul bord gauche se lisait mal et déséquilibrait la ligne. La 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 bordure transparente est portée par tous les états : la sélection ne décale
donc pas le contenu d'un pixel. */ donc pas le contenu d'un pixel. */
.side .preset{border:1px solid transparent;border-radius:14px;background:transparent} .side .preset{border:1px solid transparent;border-radius:3px;background:transparent}
.side .preset.active{background:var(--row-bg-on);border-color:var(--accent)} .side .preset.active{background:var(--row-bg-on);border-color:var(--accent)}
.side .preset:hover{background:var(--row-bg)} .side .preset:hover{background:var(--row-bg)}
/* Bulle de question : coin bas-droit rentré, comme une bulle qui pointe vers /* Bulle de question : coin bas-droit rentré, comme une bulle qui pointe vers
son auteur. */ son auteur. */
#chat .msg.user{border-radius:20px 20px 6px 20px} #chat .msg.user{border-radius:4px}
} }
/* Action principale en sauge : c'est la couleur d'action de la charte, et elle /* 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 se distingue enfin du texte (le bouton reprenait --text, donc un brun sombre
@@ -1390,7 +1398,7 @@ button:hover{border-color:var(--dim);color:var(--text)}
l'application de l'autre, tiennent maintenant chacune dans UNE carte, avec un l'application de l'autre, tiennent maintenant chacune dans UNE carte, avec un
filet discret entre les lignes. */ filet discret entre les lignes. */
@media (min-width:721px){ @media (min-width:721px){
.sidegroup{background:var(--panel);border-radius:20px;box-shadow:var(--shadow); .sidegroup{background:var(--panel);border-radius:3px;box-shadow:var(--shadow);
padding:2px 16px;display:flex;flex-direction:column} padding:2px 16px;display:flex;flex-direction:column}
/* Dans un groupe, les sections ne sont plus des cartes : elles redeviennent /* Dans un groupe, les sections ne sont plus des cartes : elles redeviennent
des lignes, séparées par un filet — sauf la première. */ des lignes, séparées par un filet — sauf la première. */
@@ -1407,7 +1415,7 @@ button:hover{border-color:var(--dim);color:var(--text)}
quantifications, et la modale ne doit pas s'étirer jusqu'à sortir de quantifications, et la modale ne doit pas s'étirer jusqu'à sortir de
l'écran. */ l'écran. */
.hf-list{display:flex;flex-direction:column;max-height:220px;overflow-y:auto; .hf-list{display:flex;flex-direction:column;max-height:220px;overflow-y:auto;
border:1px solid var(--border);border-radius:12px;margin-top:6px} border:1px solid var(--border);border-radius:3px;margin-top:6px}
.hf-row{display:flex;align-items:center;gap:10px;padding:7px 12px;cursor:pointer; .hf-row{display:flex;align-items:center;gap:10px;padding:7px 12px;cursor:pointer;
border-top:1px solid var(--border);font-size:12px} border-top:1px solid var(--border);font-size:12px}
.hf-row:first-child{border-top:0} .hf-row:first-child{border-top:0}
@@ -1443,12 +1451,12 @@ html[data-files="1"] #files{display:flex}
.files-head{display:flex;align-items:center;gap:8px} .files-head{display:flex;align-items:center;gap:8px}
.files-head .stitle{flex:1} .files-head .stitle{flex:1}
#files-refresh,#files-close{margin:0;padding:2px 8px;min-height:0;font-size:12px; #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} background:transparent;border:1px solid transparent;color:var(--dim);border-radius:4px}
#files-refresh:hover,#files-close:hover{border-color:var(--border);color:var(--text)} #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 /* Fil d'Ariane : il doit pouvoir revenir à la ligne — un identifiant de
discussion dans le chemin dépasse largement 320 px. */ 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{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; .files-crumb-b{margin:0;padding:2px 6px;min-height:0;font-size:11px;border:0;border-radius:3px;
background:transparent;color:var(--dim);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} 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:hover:not(:disabled){background:var(--row-bg-on);color:var(--text)}
.files-crumb-b.on{color:var(--text);cursor:default} .files-crumb-b.on{color:var(--text);cursor:default}
@@ -1458,7 +1466,7 @@ html[data-files="1"] #files{display:flex}
/* Une ligne = pastille, nom + méta, actions. Les actions n'apparaissent qu'au /* 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 survol : au repos on veut lire des noms de fichiers, pas une grille de
boutons. Sur écran tactile (pas de survol) elles restent visibles. */ 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{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:3px;font-size:12px}
.files-row:hover{background:var(--row-bg-on)} .files-row:hover{background:var(--row-bg-on)}
.files-ic{flex:none;width:12px;text-align:center;color:var(--dim)} .files-ic{flex:none;width:12px;text-align:center;color:var(--dim)}
.files-row.isdir .files-ic{color:var(--accent)} .files-row.isdir .files-ic{color:var(--accent)}
@@ -1469,7 +1477,7 @@ html[data-files="1"] #files{display:flex}
.files-row:hover .files-acts,.files-row:focus-within .files-acts{opacity:1} .files-row:hover .files-acts,.files-row:focus-within .files-acts{opacity:1}
@media (hover:none){.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; .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} border:1px solid transparent;border-radius:3px;color:var(--dim);cursor:pointer;margin:0;min-height:0}
.files-acts .iconbtn:hover{border-color:var(--border);color:var(--text)} .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); .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} display:flex;align-items:center;gap:6px;flex-wrap:wrap}
@@ -1477,7 +1485,7 @@ html[data-files="1"] #files{display:flex}
secours pour les fichiers d'avant le rangement par discussion, pas une vue secours pour les fichiers d'avant le rangement par discussion, pas une vue
qu'on propose au même rang que la discussion ouverte. */ 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; .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)} background:transparent;border:1px solid var(--border);border-radius:3px;color:var(--dim)}
.files-scope:hover{color:var(--text);background:var(--row-bg-on)} .files-scope:hover{color:var(--text);background:var(--row-bg-on)}
/* Téléchargement en cours : downloadWorkspaceFile pose .busy sur la ligne. */ /* Téléchargement en cours : downloadWorkspaceFile pose .busy sur la ligne. */
.files-row.busy{opacity:.55;pointer-events:none} .files-row.busy{opacity:.55;pointer-events:none}
@@ -1500,7 +1508,7 @@ html[data-files="1"] #files-btn{color:var(--accent)}
} }
@media (min-width:721px){ @media (min-width:721px){
/* Dans la charte « Terre », le panneau est une carte comme les autres. */ /* Dans la charte « Terre », le panneau est une carte comme les autres. */
#files{background:var(--panel);border-left-color:transparent;border-radius:20px; #files{background:var(--panel);border-left-color:transparent;border-radius:3px;
box-shadow:var(--shadow);margin:0} box-shadow:var(--shadow);margin:0}
} }
@@ -1556,7 +1564,7 @@ html[data-files="1"] #files-btn{color:var(--accent)}
#topbar-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; #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)} font-size:13.5px;font-weight:500;color:var(--text)}
#topbar .iconbtn{width:30px;height:30px;flex:none;display:inline-flex;align-items:center; #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); justify-content:center;border:0;border-radius:4px;background:transparent;color:var(--dim);
cursor:pointer;margin:0;padding:0;transition:background .15s,color .15s,transform .1s} 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:hover{background:var(--row-bg-on);color:var(--text)}
#topbar .iconbtn:active{transform:scale(.92)} #topbar .iconbtn:active{transform:scale(.92)}
@@ -1580,7 +1588,7 @@ html[data-files="1"] #files-btn{color:var(--accent)}
/* La liste EST la barre latérale : titre à gauche, heure à droite, une ligne. /* La liste EST la barre latérale : titre à gauche, heure à droite, une ligne.
Le reste (réglages) descend sous un unique repli. */ Le reste (réglages) descend sous un unique repli. */
#conv-search{width:calc(100% - 24px);font:inherit;font-size:12.5px;color:var(--text); #conv-search{width:calc(100% - 24px);font:inherit;font-size:12.5px;color:var(--text);
background:var(--panel);border:1px solid var(--border);border-radius:9px; background:var(--panel);border:1px solid var(--border);border-radius:4px;
padding:7px 11px;margin:0 12px 8px} padding:7px 11px;margin:0 12px 8px}
#conv-search::placeholder{color:var(--dim)} #conv-search::placeholder{color:var(--dim)}
#conv-search:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-bg)} #conv-search:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-bg)}
@@ -1634,7 +1642,7 @@ html[data-files="1"] #files-btn{color:var(--accent)}
⚠️ Ne concerne pas les boutons-icônes de la saisie et de l'en-tête (#send, ⚠️ Ne concerne pas les boutons-icônes de la saisie et de l'en-tête (#send,
#stop, #attach, #menubtn…) : ils posent `border:0` PLUS BAS dans la feuille #stop, #attach, #menubtn…) : ils posent `border:0` PLUS BAS dans la feuille
et gardent leur allure nue, qui est la bonne pour un pictogramme seul. */ et gardent leur allure nue, qui est la bonne pour un pictogramme seul. */
button{background:var(--panel);border-color:var(--border);border-radius:9px; button{background:var(--panel);border-color:var(--border);border-radius:4px;
transition:background .15s ease,color .15s ease,border-color .15s ease,transform .08s ease} transition:background .15s ease,color .15s ease,border-color .15s ease,transform .08s ease}
button:hover{background:var(--row-bg);border-color:var(--dim);color:var(--text)} button:hover{background:var(--row-bg);border-color:var(--dim);color:var(--text)}
button:active:not(:disabled){transform:translateY(1px)} button:active:not(:disabled){transform:translateY(1px)}
@@ -1645,11 +1653,11 @@ button:active:not(:disabled){transform:translateY(1px)}
nu flotte sans ancrage, et la frontière entre deux réponses se perd. */ nu flotte sans ancrage, et la frontière entre deux réponses se perd. */
#chat{padding-top:22px} #chat{padding-top:22px}
#chat .msg.assistant{background:var(--bubble);border:0; #chat .msg.assistant{background:var(--bubble);border:0;
border-radius:10px;padding:15px 17px;max-width:100%;box-shadow:var(--shadow-card)} border-radius:4px;padding:15px 17px;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)} #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 /* Question : aplat de sauge, coin bas-droit rentré. --on-accent garantit le
contraste dans les DEUX variantes. */ contraste dans les DEUX variantes. */
#chat .msg.user{background:var(--user-bg);color:var(--on-user);border-radius:10px; #chat .msg.user{background:var(--user-bg);color:var(--on-user);border-radius:4px;
padding:13px 16px;max-width:62%;box-shadow:var(--shadow-card)} padding:13px 16px;max-width:62%;box-shadow:var(--shadow-card)}
#chat .msg.user .label{color:var(--on-user);opacity:.75} #chat .msg.user .label{color:var(--on-user);opacity:.75}
#chat .msg.user .body a{color:var(--on-user);text-decoration:underline} #chat .msg.user .body a{color:var(--on-user);text-decoration:underline}
@@ -1675,7 +1683,7 @@ button:active:not(:disabled){transform:translateY(1px)}
#inputbar #attach,#inputbar #files-btn,#send,#stop{ #inputbar #attach,#inputbar #files-btn,#send,#stop{
width:34px;height:34px;min-height:34px;flex:none;margin:0;padding:0; width:34px;height:34px;min-height:34px;flex:none;margin:0;padding:0;
display:inline-flex;align-items:center;justify-content:center;align-self:center; display:inline-flex;align-items:center;justify-content:center;align-self:center;
border:0;border-radius:10px;background:transparent;color:var(--dim); border:0;border-radius:4px;background:transparent;color:var(--dim);
transition:background .15s ease,color .15s ease,transform .08s ease} transition:background .15s ease,color .15s ease,transform .08s ease}
/* Envoyer / arrêter : un seul des deux à la fois, décidé par data-busy (posé par /* Envoyer / arrêter : un seul des deux à la fois, décidé par data-busy (posé par
syncSendBtn). C'est la feuille qui garde la main sur `display` — un style syncSendBtn). C'est la feuille qui garde la main sur `display` — un style
@@ -1696,8 +1704,8 @@ html[data-files="1"] #files-btn{color:var(--accent)}
à droite du texte : c'est là qu'on les cherche, et le pied de carte redevient à 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. */ 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); #inputbar{align-items:flex-end;padding:8px 8px 8px 18px;gap:4px;box-shadow:var(--shadow-card);
border-radius:14px 14px 0 0} border-radius:3px 3px 0 0}
#ctxmeta{border-radius:0 0 14px 14px} #ctxmeta{border-radius:0 0 5px 5px}
#ctxmeta{padding-bottom:12px} #ctxmeta{padding-bottom:12px}
#ctxtrack{border-radius:999px} #ctxtrack{border-radius:999px}
@@ -1718,8 +1726,8 @@ html[data-files="1"] #files-btn{color:var(--accent)}
#side-scroll>details,#side-scroll>.sidegroup{background:transparent;box-shadow:none; #side-scroll>details,#side-scroll>.sidegroup{background:transparent;box-shadow:none;
border-radius:0;padding:0;margin:0} border-radius:0;padding:0;margin:0}
#side-scroll>details>summary{height:34px} #side-scroll>details>summary{height:34px}
.side .slist{background:transparent;border-color:transparent;border-radius:12px} .side .slist{background:transparent;border-color:transparent;border-radius:3px}
.side #side-settings .slist{background:var(--row-bg);border-radius:12px} .side #side-settings .slist{background:var(--row-bg);border-radius:3px}
/* Le fil n'est plus une carte : il EST la page. */ /* Le fil n'est plus une carte : il EST la page. */
.main{background:transparent;border-radius:0;box-shadow:none;overflow:visible} .main{background:transparent;border-radius:0;box-shadow:none;overflow:visible}
#composer{background:linear-gradient(to bottom,transparent 0,var(--bg) var(--fade))} #composer{background:linear-gradient(to bottom,transparent 0,var(--bg) var(--fade))}
@@ -1732,7 +1740,7 @@ html[data-files="1"] #files-btn{color:var(--accent)}
au tiers de la place disponible. */ au tiers de la place disponible. */
.side .preset{position:relative} .side .preset{position:relative}
.side .preset .conv-acts{position:absolute;right:6px;top:50%;transform:translateY(-50%); .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} margin:0;background:var(--row-bg-on);border-radius:4px;padding:1px}
.side .preset.active .conv-acts{background:var(--row-bg)} .side .preset.active .conv-acts{background:var(--row-bg)}
.side .preset .preset-info{overflow:hidden} .side .preset .preset-info{overflow:hidden}
/* La sélection est un aplat, pas un cadre : un liseré autour d'une ligne de /* La sélection est un aplat, pas un cadre : un liseré autour d'une ligne de
@@ -1754,10 +1762,10 @@ html[data-files="1"] #files-btn{color:var(--accent)}
/* Le ☰ suit la charte des boutons : pas de contour, un fond qui apparaît au /* 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 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. */ pour ne pas laisser voir le fil défiler dessous. */
#menubtn{background:var(--bg);border:0;color:var(--dim);border-radius:10px; #menubtn{background:var(--bg);border:0;color:var(--dim);border-radius:4px;
width:34px;height:34px;font-size:16px;top:calc(11px + env(safe-area-inset-top));left:9px} 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)} #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))} #lc-chip{border-radius:4px;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 /* Jauges : la piste doit se VOIR, sinon une barre presque vide ressemble à un
trait souligné sous l'intitulé. Valeurs relevées sur la maquette. */ trait souligné sous l'intitulé. Valeurs relevées sur la maquette. */
.perf-track{background:var(--track)} .perf-track{background:var(--track)}
@@ -1774,7 +1782,7 @@ html[data-files="1"] #files-btn{color:var(--accent)}
#chat .msg.assistant .body pre,#chat .msg.reasoning .body pre,#chat .msg.tool pre{ #chat .msg.assistant .body pre,#chat .msg.reasoning .body pre,#chat .msg.tool pre{
background:var(--code-bg);border:1px solid color-mix(in srgb,var(--text) 10%,transparent)} background:var(--code-bg);border:1px solid color-mix(in srgb,var(--text) 10%,transparent)}
#chat .msg.assistant .body code{background:var(--code-bg); #chat .msg.assistant .body code{background:var(--code-bg);
border:1px solid color-mix(in srgb,var(--text) 7%,transparent);border-radius:5px} border:1px solid color-mix(in srgb,var(--text) 7%,transparent);border-radius:3px}
#chat .msg.assistant .body pre code{border:0;background:transparent} #chat .msg.assistant .body pre code{border:0;background:transparent}
/* --- Titres de la barre latérale ------------------------------------ /* --- Titres de la barre latérale ------------------------------------
La maquette n'a qu'un style de titre : casse normale, demi-gras, taille du La maquette n'a qu'un style de titre : casse normale, demi-gras, taille du
@@ -1860,7 +1868,7 @@ html[data-files="1"] #files-btn{color:var(--accent)}
#chat-actions-clear,.row>button>svg{vertical-align:-2px} #chat-actions-clear,.row>button>svg{vertical-align:-2px}
.row>button:has(svg){display:inline-flex;align-items:center;gap:7px} .row>button:has(svg){display:inline-flex;align-items:center;gap:7px}
/* Le « + » des sections : même carré que les autres commandes de la barre. */ /* Le « + » des sections : même carré que les autres commandes de la barre. */
.preset-add{width:26px;height:26px;border-radius:8px} .preset-add{width:26px;height:26px;border-radius:4px}
/* Étiquettes de groupe : même traitement que les titres de section — la /* Étiquettes de groupe : même traitement que les titres de section — la
maquette n'a qu'un style de titre, et le monospace en capitales de la charte maquette n'a qu'un style de titre, et le monospace en capitales de la charte
précédente criait au milieu des formulaires. */ précédente criait au milieu des formulaires. */
@@ -1892,7 +1900,7 @@ html[data-files="1"] #files-btn{color:var(--accent)}
/* La liste EST la carte, au MÊME gabarit que .sidegroup (les sections de /* La liste EST la carte, au MÊME gabarit que .sidegroup (les sections de
réglages) : fond de panneau, rayon de 20, ombre discrète. Les lignes sont réglages) : fond de panneau, rayon de 20, ombre discrète. Les lignes sont
rognées par overflow au lieu d'arrondir la première et la dernière à la main. */ rognées par overflow au lieu d'arrondir la première et la dernière à la main. */
#conv-list{background:var(--panel);border-radius:20px;box-shadow:var(--shadow); #conv-list{background:var(--panel);border-radius:3px;box-shadow:var(--shadow);
overflow:hidden;margin:0} overflow:hidden;margin:0}
.side .preset{border-radius:0;margin:0;padding:10px 12px} .side .preset{border-radius:0;margin:0;padding:10px 12px}
.side .preset:not(:last-child){border-bottom:1px solid var(--border)} .side .preset:not(:last-child){border-bottom:1px solid var(--border)}
@@ -1908,7 +1916,7 @@ html[data-files="1"] #files-btn{color:var(--accent)}
couple que « Paramètres » + sa liste dans les réglages. */ couple que « Paramètres » + sa liste dans les réglages. */
#side-perf{padding:12px 12px 12px;padding-bottom:calc(12px + env(safe-area-inset-bottom))} #side-perf{padding:12px 12px 12px;padding-bottom:calc(12px + env(safe-area-inset-bottom))}
.perf-head{margin:0 2px 7px;font-size:12.5px;font-weight:600;color:var(--dim)} .perf-head{margin:0 2px 7px;font-size:12.5px;font-weight:600;color:var(--dim)}
.perf-body{background:var(--panel);border-radius:20px;box-shadow:var(--shadow); .perf-body{background:var(--panel);border-radius:3px;box-shadow:var(--shadow);
padding:14px 15px 3px} padding:14px 15px 3px}
/* ⚠️ Pas de remise à zéro sur :last-child — les jauges vivent dans DEUX /* ⚠️ Pas de remise à zéro sur :last-child — les jauges vivent dans DEUX
conteneurs (les cartes graphiques, puis la mémoire vive). La dernière jauge conteneurs (les cartes graphiques, puis la mémoire vive). La dernière jauge
@@ -1949,8 +1957,8 @@ html[data-files="1"] #files-btn{color:var(--accent)}
<div id="conv-list" class="slist"></div> <div id="conv-list" class="slist"></div>
</details> </details>
<!-- Avertissement de lancement (App Translocation macOS) — masqué par défaut. --> <!-- Avertissement de lancement (App Translocation macOS) — masqué par défaut. -->
<div id="app-warn" class="muted" style="display:none;font-size:11px;line-height:1.5;margin:0 0 10px;padding:8px 10px;border-radius:8px;color:var(--warn);border:1px solid color-mix(in srgb,var(--warn) 45%,transparent);background:color-mix(in srgb,var(--warn) 12%,transparent)"></div> <div id="app-warn" class="muted" style="display:none;font-size:11px;line-height:1.5;margin:0 0 10px;padding:8px 10px;border-radius:4px;color:var(--warn);border:1px solid color-mix(in srgb,var(--warn) 45%,transparent);background:color-mix(in srgb,var(--warn) 12%,transparent)"></div>
<div id="model-err" style="display:none;font-size:11px;line-height:1.5;margin:0 0 10px;padding:8px 10px;border-radius:8px;color:var(--err);border:1px solid color-mix(in srgb,var(--err) 45%,transparent);background:color-mix(in srgb,var(--err) 12%,transparent)"></div> <div id="model-err" style="display:none;font-size:11px;line-height:1.5;margin:0 0 10px;padding:8px 10px;border-radius:4px;color:var(--err);border:1px solid color-mix(in srgb,var(--err) 45%,transparent);background:color-mix(in srgb,var(--err) 12%,transparent)"></div>
<!-- Journal du moteur : replié par défaut, ouvert au clic sur la pastille d'état <!-- Journal du moteur : replié par défaut, ouvert au clic sur la pastille d'état
OU par son propre chevron — c'est une section comme les autres, sinon on ne OU par son propre chevron — c'est une section comme les autres, sinon on ne
trouvait pas comment la refermer. trouvait pas comment la refermer.
@@ -2298,6 +2306,15 @@ html[data-files="1"] #files-btn{color:var(--accent)}
<div id="inputbar"> <div id="inputbar">
<textarea id="input" rows="1" placeholder="Message Loki…" onkeydown="onKey(event)" oninput="autoGrow(this)" onpaste="onPaste(event)"></textarea> <textarea id="input" rows="1" placeholder="Message Loki…" onkeydown="onKey(event)" oninput="autoGrow(this)" onpaste="onPaste(event)"></textarea>
<input type="file" id="attach-input" multiple style="display:none" onchange="onAttachPick(event)"> <input type="file" id="attach-input" multiple style="display:none" onchange="onAttachPick(event)">
<!-- Intensité du raisonnement : réglable ici parce que ça se décide au
moment d'écrire le message, pas dans un éditeur de preset. -->
<select id="think-effort" title="Intensité du raisonnement" aria-label="Intensité du raisonnement" onchange="setThinkEffort(this.value)">
<option value="">auto</option>
<option value="none">aucune</option>
<option value="low">basse</option>
<option value="medium">moyenne</option>
<option value="high">haute</option>
</select>
<!-- Joindre un fichier. Icône dessinée en SVG : pas de dépendance à une <!-- Joindre un fichier. Icône dessinée en SVG : pas de dépendance à une
police d'emoji, dont le rendu varie énormément d'un système à l'autre. --> police d'emoji, dont le rendu varie énormément d'un système à l'autre. -->
<button id="attach" onclick="document.getElementById('attach-input').click()" title="joindre un fichier" aria-label="joindre un fichier"> <button id="attach" onclick="document.getElementById('attach-input').click()" title="joindre un fichier" aria-label="joindre un fichier">
@@ -2345,7 +2362,7 @@ html[data-files="1"] #files-btn{color:var(--accent)}
</aside> </aside>
<div id="toast"></div> <div id="toast"></div>
<div id="bench-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:30;align-items:center;justify-content:center;padding:20px" onclick="if(event.target===this)closeBenchModal()"> <div id="bench-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:30;align-items:center;justify-content:center;padding:20px" onclick="if(event.target===this)closeBenchModal()">
<div style="background:var(--bg);border:1px solid var(--border);border-radius:8px;width:100%;max-width:480px;display:flex;flex-direction:column"> <div style="background:var(--bg);border:1px solid var(--border);border-radius:4px;width:100%;max-width:480px;display:flex;flex-direction:column">
<div style="padding:14px 16px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center"> <div style="padding:14px 16px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center">
<strong>Benchmark</strong> <strong>Benchmark</strong>
<button onclick="closeBenchModal()" style="margin:0" title="fermer" aria-label="Fermer"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6.5 6.5l11 11"/><path d="M17.5 6.5l-11 11"/></svg></button> <button onclick="closeBenchModal()" style="margin:0" title="fermer" aria-label="Fermer"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6.5 6.5l11 11"/><path d="M17.5 6.5l-11 11"/></svg></button>
@@ -2729,7 +2746,7 @@ html[data-files="1"] #files-btn{color:var(--accent)}
<span class="pe-row-c"><label class="pe-switch"><input type="checkbox" id="s-reasoning" onchange="cfgWriteKey('REASONING', this.checked?'on':'off')"><span class="pe-sl"></span></label></span> <span class="pe-row-c"><label class="pe-switch"><input type="checkbox" id="s-reasoning" onchange="cfgWriteKey('REASONING', this.checked?'on':'off')"><span class="pe-sl"></span></label></span>
</div> </div>
<div class="pe-row" id="s-reasoning-effort-row"> <div class="pe-row" id="s-reasoning-effort-row">
<span class="pe-row-l">Intensité du raisonnement<span class="pe-sub" id="s-reasoning-effort-sub">envoyée au gabarit du modèle — auto = ne rien imposer</span></span> <span class="pe-row-l">Intensité du raisonnement<span class="pe-sub" id="s-reasoning-effort-sub">défaut de ce preset — ajustable à la volée depuis la barre de saisie</span></span>
<span class="pe-row-c"> <span class="pe-row-c">
<select id="s-reasoning-effort" class="pe-val" onchange="cfgWriteKey('REASONING_EFFORT', this.value)"> <select id="s-reasoning-effort" class="pe-val" onchange="cfgWriteKey('REASONING_EFFORT', this.value)">
<option value="">auto</option> <option value="">auto</option>
@@ -3806,7 +3823,7 @@ async function loadAll(){
// MANQUANTE lève ici une ReferenceError qui est avalée en silence. Toute // MANQUANTE lève ici une ReferenceError qui est avalée en silence. Toute
// suppression de module doit donc retirer son appel de cette ligne, et le test // suppression de module doit donc retirer son appel de cette ligne, et le test
// navigateur écoute `pageerror` pour ne pas s'en apercevoir trop tard. // navigateur écoute `pageerror` pour ne pas s'en apercevoir trop tard.
await Promise.allSettled([loadStatus(),loadVram(),loadRam(),loadCfg(),loadPresets(),loadConversations(),loadIdentity(),loadAgent(),loadInternet(),loadMCP(),loadApiKey(),loadPrefs(),loadLlamacpp()]); await Promise.allSettled([loadStatus(),loadVram(),loadRam(),loadCfg(),loadPresets(),loadConversations(),loadIdentity(),loadAgent(),loadInternet(),loadMCP(),loadApiKey(),loadPrefs(),loadLlamacpp(),loadThinkEffort()]);
releaseHeights(); // tout est en place : on rend la main et on mesure pour la prochaine fois releaseHeights(); // tout est en place : on rend la main et on mesure pour la prochaine fois
} }
async function act(a){ toast(a+'…'); await jpost('/api/'+a); setTimeout(loadAll,1500); } async function act(a){ toast(a+'…'); await jpost('/api/'+a); setTimeout(loadAll,1500); }
@@ -4567,9 +4584,12 @@ function populateSettings(){
eff.value = (cfgReadKey('REASONING_EFFORT')||'').toLowerCase(); eff.value = (cfgReadKey('REASONING_EFFORT')||'').toLowerCase();
const effSub = document.getElementById('s-reasoning-effort-sub'); const effSub = document.getElementById('s-reasoning-effort-sub');
if(effSub){ if(effSub){
// Deux emplacements, deux portées : ici c'est le défaut ENREGISTRÉ avec le
// preset (donc rattaché au modèle) ; la liste de la barre de saisie le
// dévie pour la suite de la session, jusqu'au prochain preset appliqué.
effSub.textContent = eff.value effSub.textContent = eff.value
? 'envoyée au gabarit du modèle — sans effet si son gabarit l\'ignore' ? 'défaut de ce preset — sans effet si le gabarit du modèle l\'ignore'
: 'envoyée au gabarit du modèle — auto = ne rien imposer'; : 'défaut de ce preset — ajustable à la volée depuis la barre de saisie';
} }
} }
chk('s-flash', eaHasFlag('--flash-attn') && !/^off$/i.test(eaGetValued('--flash-attn'))); chk('s-flash', eaHasFlag('--flash-attn') && !/^off$/i.test(eaGetValued('--flash-attn')));
@@ -6440,6 +6460,47 @@ async function deleteMcp(){
loadMCP(); loadMCP();
toast('serveur supprimé'); toast('serveur supprimé');
} }
// ─── Intensité du raisonnement (barre de saisie) ─────────────────────────────
// Le même réglage existe dans l'éditeur de preset, mais il y est figé avec le
// preset : le changer demande d'ouvrir les réglages, éditer, enregistrer. Ici il
// s'applique au message suivant, sans redémarrer le moteur — `reasoning_effort`
// voyage dans le corps de la requête, pas dans la ligne de commande.
//
// La valeur part dans la config vivante. Appliquer un preset la réécrit : le
// réglage reste donc attaché au modèle, cette liste ne fait que le dévier à la
// volée.
const THINK_EFFORT_LABELS = {
'': 'auto',
none: 'aucune',
low: 'basse',
medium: 'moyenne',
high: 'haute',
};
async function loadThinkEffort(){
try{ applyThinkEffort(await jget('/api/reasoning-effort')); }
catch(e){ /* endpoint indisponible : la liste garde sa valeur par défaut */ }
}
function applyThinkEffort(r){
const sel = document.getElementById('think-effort');
if(!sel || !r || !r.ok) return;
sel.value = r.value || '';
// Raisonnement coupé au niveau du preset : le niveau n'a plus de prise. On
// grise plutôt que de masquer, pour que le réglage reste trouvable.
sel.disabled = !r.on;
sel.title = r.on
? 'Intensité du raisonnement — envoyée au gabarit du modèle'
: 'Raisonnement désactivé pour ce modèle (réglages → Raisonnement)';
}
async function setThinkEffort(value){
const r = await jpost('/api/reasoning-effort', {value});
if(!r || !r.ok){ toast((r && r.error) || 'réglage refusé'); return; }
applyThinkEffort(r);
toast('raisonnement : '+(THINK_EFFORT_LABELS[r.value||''] || r.value));
}
// ===== Pièces jointes du chat ============================================== // ===== Pièces jointes du chat ==============================================
// Un fichier joint n'est PAS lu par le navigateur ni injecté dans le contexte : // Un fichier joint n'est PAS lu par le navigateur ni injecté dans le contexte :
// il est déposé tel quel dans le dossier de travail de l'IA (uploads/, côté // il est déposé tel quel dans le dossier de travail de l'IA (uploads/, côté
+13 -4
View File
@@ -62,8 +62,8 @@ document.documentElement.setAttribute('data-side',localStorage.getItem('loki-sid
<div id="conv-list" class="slist"></div> <div id="conv-list" class="slist"></div>
</details> </details>
<!-- Avertissement de lancement (App Translocation macOS) — masqué par défaut. --> <!-- Avertissement de lancement (App Translocation macOS) — masqué par défaut. -->
<div id="app-warn" class="muted" style="display:none;font-size:11px;line-height:1.5;margin:0 0 10px;padding:8px 10px;border-radius:8px;color:var(--warn);border:1px solid color-mix(in srgb,var(--warn) 45%,transparent);background:color-mix(in srgb,var(--warn) 12%,transparent)"></div> <div id="app-warn" class="muted" style="display:none;font-size:11px;line-height:1.5;margin:0 0 10px;padding:8px 10px;border-radius:4px;color:var(--warn);border:1px solid color-mix(in srgb,var(--warn) 45%,transparent);background:color-mix(in srgb,var(--warn) 12%,transparent)"></div>
<div id="model-err" style="display:none;font-size:11px;line-height:1.5;margin:0 0 10px;padding:8px 10px;border-radius:8px;color:var(--err);border:1px solid color-mix(in srgb,var(--err) 45%,transparent);background:color-mix(in srgb,var(--err) 12%,transparent)"></div> <div id="model-err" style="display:none;font-size:11px;line-height:1.5;margin:0 0 10px;padding:8px 10px;border-radius:4px;color:var(--err);border:1px solid color-mix(in srgb,var(--err) 45%,transparent);background:color-mix(in srgb,var(--err) 12%,transparent)"></div>
<!-- Journal du moteur : replié par défaut, ouvert au clic sur la pastille d'état <!-- Journal du moteur : replié par défaut, ouvert au clic sur la pastille d'état
OU par son propre chevron — c'est une section comme les autres, sinon on ne OU par son propre chevron — c'est une section comme les autres, sinon on ne
trouvait pas comment la refermer. trouvait pas comment la refermer.
@@ -411,6 +411,15 @@ document.documentElement.setAttribute('data-side',localStorage.getItem('loki-sid
<div id="inputbar"> <div id="inputbar">
<textarea id="input" rows="1" placeholder="Message Loki…" onkeydown="onKey(event)" oninput="autoGrow(this)" onpaste="onPaste(event)"></textarea> <textarea id="input" rows="1" placeholder="Message Loki…" onkeydown="onKey(event)" oninput="autoGrow(this)" onpaste="onPaste(event)"></textarea>
<input type="file" id="attach-input" multiple style="display:none" onchange="onAttachPick(event)"> <input type="file" id="attach-input" multiple style="display:none" onchange="onAttachPick(event)">
<!-- Intensité du raisonnement : réglable ici parce que ça se décide au
moment d'écrire le message, pas dans un éditeur de preset. -->
<select id="think-effort" title="Intensité du raisonnement" aria-label="Intensité du raisonnement" onchange="setThinkEffort(this.value)">
<option value="">auto</option>
<option value="none">aucune</option>
<option value="low">basse</option>
<option value="medium">moyenne</option>
<option value="high">haute</option>
</select>
<!-- Joindre un fichier. Icône dessinée en SVG : pas de dépendance à une <!-- Joindre un fichier. Icône dessinée en SVG : pas de dépendance à une
police d'emoji, dont le rendu varie énormément d'un système à l'autre. --> police d'emoji, dont le rendu varie énormément d'un système à l'autre. -->
<button id="attach" onclick="document.getElementById('attach-input').click()" title="joindre un fichier" aria-label="joindre un fichier"> <button id="attach" onclick="document.getElementById('attach-input').click()" title="joindre un fichier" aria-label="joindre un fichier">
@@ -458,7 +467,7 @@ document.documentElement.setAttribute('data-side',localStorage.getItem('loki-sid
</aside> </aside>
<div id="toast"></div> <div id="toast"></div>
<div id="bench-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:30;align-items:center;justify-content:center;padding:20px" onclick="if(event.target===this)closeBenchModal()"> <div id="bench-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:30;align-items:center;justify-content:center;padding:20px" onclick="if(event.target===this)closeBenchModal()">
<div style="background:var(--bg);border:1px solid var(--border);border-radius:8px;width:100%;max-width:480px;display:flex;flex-direction:column"> <div style="background:var(--bg);border:1px solid var(--border);border-radius:4px;width:100%;max-width:480px;display:flex;flex-direction:column">
<div style="padding:14px 16px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center"> <div style="padding:14px 16px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center">
<strong>Benchmark</strong> <strong>Benchmark</strong>
<button onclick="closeBenchModal()" style="margin:0" title="fermer" aria-label="Fermer"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6.5 6.5l11 11"/><path d="M17.5 6.5l-11 11"/></svg></button> <button onclick="closeBenchModal()" style="margin:0" title="fermer" aria-label="Fermer"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6.5 6.5l11 11"/><path d="M17.5 6.5l-11 11"/></svg></button>
@@ -842,7 +851,7 @@ document.documentElement.setAttribute('data-side',localStorage.getItem('loki-sid
<span class="pe-row-c"><label class="pe-switch"><input type="checkbox" id="s-reasoning" onchange="cfgWriteKey('REASONING', this.checked?'on':'off')"><span class="pe-sl"></span></label></span> <span class="pe-row-c"><label class="pe-switch"><input type="checkbox" id="s-reasoning" onchange="cfgWriteKey('REASONING', this.checked?'on':'off')"><span class="pe-sl"></span></label></span>
</div> </div>
<div class="pe-row" id="s-reasoning-effort-row"> <div class="pe-row" id="s-reasoning-effort-row">
<span class="pe-row-l">Intensité du raisonnement<span class="pe-sub" id="s-reasoning-effort-sub">envoyée au gabarit du modèle — auto = ne rien imposer</span></span> <span class="pe-row-l">Intensité du raisonnement<span class="pe-sub" id="s-reasoning-effort-sub">défaut de ce preset — ajustable à la volée depuis la barre de saisie</span></span>
<span class="pe-row-c"> <span class="pe-row-c">
<select id="s-reasoning-effort" class="pe-val" onchange="cfgWriteKey('REASONING_EFFORT', this.value)"> <select id="s-reasoning-effort" class="pe-val" onchange="cfgWriteKey('REASONING_EFFORT', this.value)">
<option value="">auto</option> <option value="">auto</option>
+1 -1
View File
@@ -451,7 +451,7 @@ async function loadAll(){
// MANQUANTE lève ici une ReferenceError qui est avalée en silence. Toute // MANQUANTE lève ici une ReferenceError qui est avalée en silence. Toute
// suppression de module doit donc retirer son appel de cette ligne, et le test // suppression de module doit donc retirer son appel de cette ligne, et le test
// navigateur écoute `pageerror` pour ne pas s'en apercevoir trop tard. // navigateur écoute `pageerror` pour ne pas s'en apercevoir trop tard.
await Promise.allSettled([loadStatus(),loadVram(),loadRam(),loadCfg(),loadPresets(),loadConversations(),loadIdentity(),loadAgent(),loadInternet(),loadMCP(),loadApiKey(),loadPrefs(),loadLlamacpp()]); await Promise.allSettled([loadStatus(),loadVram(),loadRam(),loadCfg(),loadPresets(),loadConversations(),loadIdentity(),loadAgent(),loadInternet(),loadMCP(),loadApiKey(),loadPrefs(),loadLlamacpp(),loadThinkEffort()]);
releaseHeights(); // tout est en place : on rend la main et on mesure pour la prochaine fois releaseHeights(); // tout est en place : on rend la main et on mesure pour la prochaine fois
} }
async function act(a){ toast(a+'…'); await jpost('/api/'+a); setTimeout(loadAll,1500); } async function act(a){ toast(a+'…'); await jpost('/api/'+a); setTimeout(loadAll,1500); }
+5 -2
View File
@@ -755,9 +755,12 @@ function populateSettings(){
eff.value = (cfgReadKey('REASONING_EFFORT')||'').toLowerCase(); eff.value = (cfgReadKey('REASONING_EFFORT')||'').toLowerCase();
const effSub = document.getElementById('s-reasoning-effort-sub'); const effSub = document.getElementById('s-reasoning-effort-sub');
if(effSub){ if(effSub){
// Deux emplacements, deux portées : ici c'est le défaut ENREGISTRÉ avec le
// preset (donc rattaché au modèle) ; la liste de la barre de saisie le
// dévie pour la suite de la session, jusqu'au prochain preset appliqué.
effSub.textContent = eff.value effSub.textContent = eff.value
? 'envoyée au gabarit du modèle — sans effet si son gabarit l\'ignore' ? 'défaut de ce preset — sans effet si le gabarit du modèle l\'ignore'
: 'envoyée au gabarit du modèle — auto = ne rien imposer'; : 'défaut de ce preset — ajustable à la volée depuis la barre de saisie';
} }
} }
chk('s-flash', eaHasFlag('--flash-attn') && !/^off$/i.test(eaGetValued('--flash-attn'))); chk('s-flash', eaHasFlag('--flash-attn') && !/^off$/i.test(eaGetValued('--flash-attn')));
+41
View File
@@ -0,0 +1,41 @@
// ─── Intensité du raisonnement (barre de saisie) ─────────────────────────────
// Le même réglage existe dans l'éditeur de preset, mais il y est figé avec le
// preset : le changer demande d'ouvrir les réglages, éditer, enregistrer. Ici il
// s'applique au message suivant, sans redémarrer le moteur — `reasoning_effort`
// voyage dans le corps de la requête, pas dans la ligne de commande.
//
// La valeur part dans la config vivante. Appliquer un preset la réécrit : le
// réglage reste donc attaché au modèle, cette liste ne fait que le dévier à la
// volée.
const THINK_EFFORT_LABELS = {
'': 'auto',
none: 'aucune',
low: 'basse',
medium: 'moyenne',
high: 'haute',
};
async function loadThinkEffort(){
try{ applyThinkEffort(await jget('/api/reasoning-effort')); }
catch(e){ /* endpoint indisponible : la liste garde sa valeur par défaut */ }
}
function applyThinkEffort(r){
const sel = document.getElementById('think-effort');
if(!sel || !r || !r.ok) return;
sel.value = r.value || '';
// Raisonnement coupé au niveau du preset : le niveau n'a plus de prise. On
// grise plutôt que de masquer, pour que le réglage reste trouvable.
sel.disabled = !r.on;
sel.title = r.on
? 'Intensité du raisonnement — envoyée au gabarit du modèle'
: 'Raisonnement désactivé pour ce modèle (réglages → Raisonnement)';
}
async function setThinkEffort(value){
const r = await jpost('/api/reasoning-effort', {value});
if(!r || !r.ok){ toast((r && r.error) || 'réglage refusé'); return; }
applyThinkEffort(r);
toast('raisonnement : '+(THINK_EFFORT_LABELS[r.value||''] || r.value));
}
+105 -97
View File
@@ -143,11 +143,11 @@ select{color-scheme:inherit}
option,optgroup{background:var(--panel);color:var(--text)} option,optgroup{background:var(--panel);color:var(--text)}
.side{width:320px;border-right:1px solid var(--border);padding:16px;padding-bottom:calc(16px + env(safe-area-inset-bottom));overflow-y:auto;flex-shrink:0} .side{width:320px;border-right:1px solid var(--border);padding:16px;padding-bottom:calc(16px + env(safe-area-inset-bottom));overflow-y:auto;flex-shrink:0}
.main{flex:1;display:flex;flex-direction:column;min-width:0} .main{flex:1;display:flex;flex-direction:column;min-width:0}
#menubtn{display:none;position:fixed;top:calc(10px + env(safe-area-inset-top));left:calc(10px + env(safe-area-inset-left));z-index:20;background:var(--panel);border:1px solid var(--border);color:var(--text);width:40px;height:40px;border-radius:6px;font-size:18px;cursor:pointer} #menubtn{display:none;position:fixed;top:calc(10px + env(safe-area-inset-top));left:calc(10px + env(safe-area-inset-left));z-index:20;background:var(--panel);border:1px solid var(--border);color:var(--text);width:40px;height:40px;border-radius:3px;font-size:18px;cursor:pointer}
#backdrop{display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:10} #backdrop{display:none;position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:10}
/* Pastille « installation en cours » : suit exactement les règles d'affichage /* Pastille « installation en cours » : suit exactement les règles d'affichage
du bouton ☰ (elle ne sert que quand le panneau est en tiroir, donc caché). */ du bouton ☰ (elle ne sert que quand le panneau est en tiroir, donc caché). */
#lc-chip{display:none;position:fixed;top:calc(10px + env(safe-area-inset-top));left:calc(58px + env(safe-area-inset-left));z-index:20;max-width:60vw;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:var(--panel);border:1px solid var(--accent);color:var(--accent);height:40px;padding:0 12px;border-radius:6px;font-size:12px;cursor:pointer} #lc-chip{display:none;position:fixed;top:calc(10px + env(safe-area-inset-top));left:calc(58px + env(safe-area-inset-left));z-index:20;max-width:60vw;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;background:var(--panel);border:1px solid var(--accent);color:var(--accent);height:40px;padding:0 12px;border-radius:3px;font-size:12px;cursor:pointer}
#lc-chip.failed{border-color:var(--err);color:var(--err)} #lc-chip.failed{border-color:var(--err);color:var(--err)}
@media (max-width:720px){ @media (max-width:720px){
.side{position:fixed;inset:0 auto 0 0;width:80%;max-width:320px;z-index:15;transform:translateX(-100%);transition:transform .2s;background:var(--bg);padding-top:calc(16px + env(safe-area-inset-top));padding-left:calc(16px + env(safe-area-inset-left))} .side{position:fixed;inset:0 auto 0 0;width:80%;max-width:320px;z-index:15;transform:translateX(-100%);transition:transform .2s;background:var(--bg);padding-top:calc(16px + env(safe-area-inset-top));padding-left:calc(16px + env(safe-area-inset-left))}
@@ -170,7 +170,7 @@ option,optgroup{background:var(--panel);color:var(--text)}
/* Desktop : menu plus large et bulles (badges) un peu plus grosses. */ /* Desktop : menu plus large et bulles (badges) un peu plus grosses. */
@media (min-width:721px){ @media (min-width:721px){
.side{width:380px} .side{width:380px}
.preset-meta .qtag,.preset-meta .rtag,.preset-meta .btag{font-size:11px;padding:2px 9px;border-radius:9px} .preset-meta .qtag,.preset-meta .rtag,.preset-meta .btag{font-size:11px;padding:2px 9px;border-radius:4px}
.preset-meta .qtag svg,.preset-meta .rtag svg,.preset-meta .btag svg{width:11px;height:11px} .preset-meta .qtag svg,.preset-meta .rtag svg,.preset-meta .btag svg{width:11px;height:11px}
} }
h1{margin:0;font-size:18px;color:var(--accent)} h1{margin:0;font-size:18px;color:var(--accent)}
@@ -193,7 +193,7 @@ h1{margin:0;font-size:18px;color:var(--accent)}
sans souris. */ sans souris. */
.conv-acts{display:flex;gap:2px;opacity:0;transition:opacity .12s} .conv-acts{display:flex;gap:2px;opacity:0;transition:opacity .12s}
.preset:hover .conv-acts,.preset:focus-within .conv-acts{opacity:1} .preset:hover .conv-acts,.preset:focus-within .conv-acts{opacity:1}
.conv-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} .conv-acts .iconbtn{font-size:11px;line-height:1;padding:4px 6px;background:transparent;border:1px solid transparent;border-radius:3px;color:var(--dim);cursor:pointer}
.conv-acts .iconbtn:hover{border-color:var(--border);color:var(--text)} .conv-acts .iconbtn:hover{border-color:var(--border);color:var(--text)}
/* Séparateur de groupe dans la barre latérale : sépare les réglages d'IA des /* Séparateur de groupe dans la barre latérale : sépare les réglages d'IA des
réglages d'application, sans imbriquer un second niveau de repli (illisible réglages d'application, sans imbriquer un second niveau de repli (illisible
@@ -203,7 +203,7 @@ h1{margin:0;font-size:18px;color:var(--accent)}
/* Choix d'avatar : une grille de pastilles emoji, celle retenue encadrée. */ /* Choix d'avatar : une grille de pastilles emoji, celle retenue encadrée. */
.avpick{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px} .avpick{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px}
.avpick button{font-size:15px;line-height:1;padding:5px 6px;background:var(--panel); .avpick button{font-size:15px;line-height:1;padding:5px 6px;background:var(--panel);
border:1px solid var(--border);border-radius:8px;cursor:pointer} border:1px solid var(--border);border-radius:4px;cursor:pointer}
.avpick button:hover{border-color:var(--accent)} .avpick button:hover{border-color:var(--accent)}
.avpick button.sel{border-color:var(--accent);background:color-mix(in srgb,var(--accent) 15%,transparent)} .avpick button.sel{border-color:var(--accent);background:color-mix(in srgb,var(--accent) 15%,transparent)}
/* Avatar + nom en tête de bulle : l'emoji garde sa taille propre, le nom hérite /* Avatar + nom en tête de bulle : l'emoji garde sa taille propre, le nom hérite
@@ -212,7 +212,7 @@ h1{margin:0;font-size:18px;color:var(--accent)}
.msg .label .who{text-transform:none;letter-spacing:.04em;font-size:11px} .msg .label .who{text-transform:none;letter-spacing:.04em;font-size:11px}
/* Captures d'écran et images de l'agent : bornées à la largeur de la bulle, et /* Captures d'écran et images de l'agent : bornées à la largeur de la bulle, et
cliquables pour ouvrir en grand dans un onglet. */ cliquables pour ouvrir en grand dans un onglet. */
.msg .body img{max-width:100%;height:auto;border-radius:8px;border:1px solid var(--border);display:block;margin:8px 0} .msg .body img{max-width:100%;height:auto;border-radius:4px;border:1px solid var(--border);display:block;margin:8px 0}
/* pastille d'état service, en haut à droite de la barre latérale */ /* pastille d'état service, en haut à droite de la barre latérale */
.statuspill{display:inline-flex;align-items:center;gap:6px;font-size:11px;padding:3px 10px;border-radius:999px;border:1px solid var(--border);background:var(--panel);color:var(--dim);font-weight:600;white-space:nowrap;cursor:pointer} .statuspill{display:inline-flex;align-items:center;gap:6px;font-size:11px;padding:3px 10px;border-radius:999px;border:1px solid var(--border);background:var(--panel);color:var(--dim);font-weight:600;white-space:nowrap;cursor:pointer}
.statuspill.ok{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 45%,transparent);background:color-mix(in srgb,var(--ok) 12%,transparent)} .statuspill.ok{color:var(--ok);border-color:color-mix(in srgb,var(--ok) 45%,transparent);background:color-mix(in srgb,var(--ok) 12%,transparent)}
@@ -235,15 +235,15 @@ h1{margin:0;font-size:18px;color:var(--accent)}
.pages-bar{display:flex;align-items:center;gap:8px;cursor:pointer;user-select:none;padding:7px 0;margin-top:4px} .pages-bar{display:flex;align-items:center;gap:8px;cursor:pointer;user-select:none;padding:7px 0;margin-top:4px}
.pages-bar .pages-title{font-size:11.5px;color:var(--dim);font-weight:600} .pages-bar .pages-title{font-size:11.5px;color:var(--dim);font-weight:600}
.pages-bar .addbtn{margin-left:auto} .pages-bar .addbtn{margin-left:auto}
.mem-search{width:100%;box-sizing:border-box;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:5px 9px;font:inherit;font-size:12px;margin:0 0 6px} .mem-search{width:100%;box-sizing:border-box;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:3px;padding:5px 9px;font:inherit;font-size:12px;margin:0 0 6px}
.mem-search:focus{outline:none;border-color:var(--accent)} .mem-search:focus{outline:none;border-color:var(--accent)}
.mem-list{max-height:240px;overflow-y:auto;overscroll-behavior:contain} .mem-list{max-height:240px;overflow-y:auto;overscroll-behavior:contain}
.mem-more{font-size:11px;color:var(--dim);text-align:center;padding:6px 0 2px} .mem-more{font-size:11px;color:var(--dim);text-align:center;padding:6px 0 2px}
.ask-overlay{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:60;display:flex;align-items:center;justify-content:center;padding:20px} .ask-overlay{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:60;display:flex;align-items:center;justify-content:center;padding:20px}
.ask-box{background:var(--bg);border:1px solid var(--border);border-radius:10px;width:100%;max-width:420px;padding:20px;box-shadow:0 12px 40px rgba(0,0,0,.18)} .ask-box{background:var(--bg);border:1px solid var(--border);border-radius:4px;width:100%;max-width:420px;padding:20px;box-shadow:0 12px 40px rgba(0,0,0,.18)}
.ask-title{font-size:15px;font-weight:600;color:var(--text);margin-bottom:8px} .ask-title{font-size:15px;font-weight:600;color:var(--text);margin-bottom:8px}
.ask-msg{font-size:13px;color:var(--dim);line-height:1.5;white-space:pre-wrap;word-break:break-word} .ask-msg{font-size:13px;color:var(--dim);line-height:1.5;white-space:pre-wrap;word-break:break-word}
.ask-input{width:100%;box-sizing:border-box;margin-top:12px;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:9px 11px;font:inherit;font-size:13px} .ask-input{width:100%;box-sizing:border-box;margin-top:12px;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:3px;padding:9px 11px;font:inherit;font-size:13px}
.ask-input:focus{outline:none;border-color:var(--accent)} .ask-input:focus{outline:none;border-color:var(--accent)}
.ask-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:18px} .ask-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:18px}
.ask-actions button{margin:0;padding:7px 16px} .ask-actions button{margin:0;padding:7px 16px}
@@ -275,17 +275,17 @@ h1{margin:0;font-size:18px;color:var(--accent)}
.switch input:focus-visible + .slider{box-shadow:0 0 0 2px var(--bg),0 0 0 4px var(--accent)} .switch input:focus-visible + .slider{box-shadow:0 0 0 2px var(--bg),0 0 0 4px var(--accent)}
h2{margin:20px 0 8px;font-size:11px;color:var(--dim);text-transform:uppercase;letter-spacing:.1em;font-weight:600} h2{margin:20px 0 8px;font-size:11px;color:var(--dim);text-transform:uppercase;letter-spacing:.1em;font-weight:600}
details>summary::-webkit-details-marker{display:none} details>summary::-webkit-details-marker{display:none}
.preset-add{display:none;font-size:14px;padding:0 8px;margin:0 8px 0 auto;line-height:1.4;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:6px;cursor:pointer} .preset-add{display:none;font-size:14px;padding:0 8px;margin:0 8px 0 auto;line-height:1.4;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:3px;cursor:pointer}
.preset-add:hover{border-color:var(--accent);color:var(--accent)} .preset-add:hover{border-color:var(--accent);color:var(--accent)}
details[open]>summary>.preset-add{display:inline-block} details[open]>summary>.preset-add{display:inline-block}
.tag{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;background:var(--panel);border:1px solid var(--border)} .tag{display:inline-block;padding:2px 8px;border-radius:4px;font-size:11px;background:var(--panel);border:1px solid var(--border)}
.tag.ok{color:var(--ok);border-color:var(--ok)} .tag.ok{color:var(--ok);border-color:var(--ok)}
.tag.err{color:var(--err);border-color:var(--err)} .tag.err{color:var(--err);border-color:var(--err)}
button{background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:6px 10px;font:inherit;cursor:pointer;margin:2px} button{background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:3px;padding:6px 10px;font:inherit;cursor:pointer;margin:2px}
button:hover{border-color:var(--accent);color:var(--accent)} button:hover{border-color:var(--accent);color:var(--accent)}
button:disabled{opacity:.5;cursor:not-allowed} button:disabled{opacity:.5;cursor:not-allowed}
.row{display:flex;gap:4px;flex-wrap:wrap} .row{display:flex;gap:4px;flex-wrap:wrap}
.preset{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:6px 10px;border:1px solid var(--border);border-radius:6px;margin:4px 0;cursor:pointer;background:var(--panel)} .preset{display:flex;align-items:center;justify-content:space-between;gap:6px;padding:6px 10px;border:1px solid var(--border);border-radius:3px;margin:4px 0;cursor:pointer;background:var(--panel)}
.preset:hover{border-color:var(--accent)} .preset:hover{border-color:var(--accent)}
.preset.active{border-color:var(--accent);color:var(--accent);border-width:1.5px;padding:5.5px 9.5px} .preset.active{border-color:var(--accent);color:var(--accent);border-width:1.5px;padding:5.5px 9.5px}
.preset>span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .preset>span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
@@ -301,9 +301,9 @@ button:disabled{opacity:.5;cursor:not-allowed}
/* La carte (.slist) laisse volontairement déborder son contenu (menus), elle ne /* La carte (.slist) laisse volontairement déborder son contenu (menus), elle ne
rogne donc pas ses coins : c'est la LIGNE de bord qui porte l'arrondi, sinon rogne donc pas ses coins : c'est la LIGNE de bord qui porte l'arrondi, sinon
le voile dépasse l'angle de la carte. */ le voile dépasse l'angle de la carte. */
.rowlist:first-child>.preset:first-child{border-radius:8px 8px 0 0} .rowlist:first-child>.preset:first-child{border-radius:4px 4px 0 0}
.rowlist:last-child>.preset:last-child{border-radius:0 0 8px 8px} .rowlist:last-child>.preset:last-child{border-radius:0 0 4px 4px}
.rowlist:first-child:last-child>.preset:only-child{border-radius:8px} .rowlist:first-child:last-child>.preset:only-child{border-radius:4px}
/* Liseré de gauche : un trait DROIT (pas un arc) qui court sur TOUTE la hauteur /* Liseré de gauche : un trait DROIT (pas un arc) qui court sur TOUTE la hauteur
de la ligne. Donc pas de box-shadow interne (l'arrondi le courberait) mais un de la ligne. Donc pas de box-shadow interne (l'arrondi le courberait) mais un
élément à part, aux bords francs, que l'arrondi de la ligne de bord se élément à part, aux bords francs, que l'arrondi de la ligne de bord se
@@ -355,15 +355,15 @@ button:disabled{opacity:.5;cursor:not-allowed}
margin:0 -2px 0 0;padding:4px;line-height:0;background:transparent;border:none;opacity:.55} margin:0 -2px 0 0;padding:4px;line-height:0;background:transparent;border:none;opacity:.55}
.preset-edit:hover{opacity:1;color:var(--accent);background:transparent} .preset-edit:hover{opacity:1;color:var(--accent);background:transparent}
.preset-meta{display:flex;align-items:center;gap:6px;margin-top:3px;flex-wrap:wrap} .preset-meta{display:flex;align-items:center;gap:6px;margin-top:3px;flex-wrap:wrap}
.qtag{flex-shrink:0;font-size:9px;padding:1px 6px;border:1px solid var(--border);color:var(--dim);border-radius:8px;letter-spacing:.03em;text-transform:uppercase} .qtag{flex-shrink:0;font-size:9px;padding:1px 6px;border:1px solid var(--border);color:var(--dim);border-radius:4px;letter-spacing:.03em;text-transform:uppercase}
.rtag{flex-shrink:0;display:inline-flex;align-items:center;gap:3px;font-size:9px;padding:1px 6px;border:1px solid var(--border);color:var(--dim);border-radius:8px;letter-spacing:.03em} .rtag{flex-shrink:0;display:inline-flex;align-items:center;gap:3px;font-size:9px;padding:1px 6px;border:1px solid var(--border);color:var(--dim);border-radius:4px;letter-spacing:.03em}
.btag{flex-shrink:0;display:inline-flex;align-items:center;gap:3px;font-size:9px;padding:1px 6px;border:1px solid var(--border);color:var(--dim);border-radius:8px;letter-spacing:.03em} .btag{flex-shrink:0;display:inline-flex;align-items:center;gap:3px;font-size:9px;padding:1px 6px;border:1px solid var(--border);color:var(--dim);border-radius:4px;letter-spacing:.03em}
/* Petit « ? » d'aide : bulle au survol/clic au lieu d'un pavé de texte permanent. */ /* Petit « ? » d'aide : bulle au survol/clic au lieu d'un pavé de texte permanent. */
.help{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;margin-left:6px;flex-shrink:0;font-size:10px;font-weight:600;line-height:1;color:var(--dim);border:1px solid var(--border);border-radius:50%;cursor:help;user-select:none;vertical-align:middle} .help{display:inline-flex;align-items:center;justify-content:center;width:15px;height:15px;margin-left:6px;flex-shrink:0;font-size:10px;font-weight:600;line-height:1;color:var(--dim);border:1px solid var(--border);border-radius:50%;cursor:help;user-select:none;vertical-align:middle}
.help:hover{color:var(--accent);border-color:var(--accent)} .help:hover{color:var(--accent);border-color:var(--accent)}
/* La bulle est ancrée à la ligne (.switchrow position:relative), pas au « ? », /* La bulle est ancrée à la ligne (.switchrow position:relative), pas au « ? »,
pour rester alignée au bord gauche du panneau et ne pas déborder. */ pour rester alignée au bord gauche du panneau et ne pas déborder. */
.help>.tip{display:none;position:absolute;z-index:40;left:0;right:0;top:calc(100% + 2px);padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;font-size:11px;font-weight:400;line-height:1.4;color:var(--text);box-shadow:0 4px 16px rgba(0,0,0,.15);white-space:normal;text-align:left;text-transform:none;letter-spacing:normal;font-family:var(--font);cursor:auto} .help>.tip{display:none;position:absolute;z-index:40;left:0;right:0;top:calc(100% + 2px);padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:3px;font-size:11px;font-weight:400;line-height:1.4;color:var(--text);box-shadow:0 4px 16px rgba(0,0,0,.15);white-space:normal;text-align:left;text-transform:none;letter-spacing:normal;font-family:var(--font);cursor:auto}
.help:hover>.tip,.help:focus>.tip,.help.open>.tip{display:block} .help:hover>.tip,.help:focus>.tip,.help.open>.tip{display:block}
.kv{display:flex;justify-content:space-between;gap:10px;padding:9px 12px;border-bottom:1px solid var(--border);font-size:13px;min-height:38px;align-items:center} .kv{display:flex;justify-content:space-between;gap:10px;padding:9px 12px;border-bottom:1px solid var(--border);font-size:13px;min-height:38px;align-items:center}
.kv span:first-child{color:var(--text);flex-shrink:0} .kv span:first-child{color:var(--text);flex-shrink:0}
@@ -378,7 +378,7 @@ button:disabled{opacity:.5;cursor:not-allowed}
#chat{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:20px;display:flex;flex-direction:column;gap:14px} #chat{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;padding:20px;display:flex;flex-direction:column;gap:14px}
:root{--col:max(20px,calc(50% - 450px))} :root{--col:max(20px,calc(50% - 450px))}
@media (max-width:720px){#chat{padding:60px 8px 12px}} @media (max-width:720px){#chat{padding:60px 8px 12px}}
.msg{max-width:80%;padding:10px 14px;border-radius:10px;white-space:pre-wrap;word-wrap:break-word;line-height:1.45} .msg{max-width:80%;padding:10px 14px;border-radius:4px;white-space:pre-wrap;word-wrap:break-word;line-height:1.45}
.msg.assistant,.msg.reasoning{white-space:normal} .msg.assistant,.msg.reasoning{white-space:normal}
.msg .body{overflow-wrap:anywhere;word-break:break-word} .msg .body{overflow-wrap:anywhere;word-break:break-word}
.msg.user{align-self:flex-end;background:var(--bubble);color:var(--text)} .msg.user{align-self:flex-end;background:var(--bubble);color:var(--text)}
@@ -394,8 +394,8 @@ button:disabled{opacity:.5;cursor:not-allowed}
.msg .body li{margin:.15em 0} .msg .body li{margin:.15em 0}
.msg .body li>p{margin:0} .msg .body li>p{margin:0}
.msg.assistant .body code{background:var(--code-bg);padding:1px 5px;border-radius:3px;font-size:.9em;overflow-wrap:anywhere;word-break:break-word} .msg.assistant .body code{background:var(--code-bg);padding:1px 5px;border-radius:3px;font-size:.9em;overflow-wrap:anywhere;word-break:break-word}
.msg.assistant .body pre,.msg.reasoning .body pre{position:relative;background:var(--code-bg);border:1px solid var(--border);border-radius:6px;padding:10px;overflow-x:auto;max-width:100%;margin:.5em 0} .msg.assistant .body pre,.msg.reasoning .body pre{position:relative;background:var(--code-bg);border:1px solid var(--border);border-radius:3px;padding:10px;overflow-x:auto;max-width:100%;margin:.5em 0}
.copybtn{position:absolute;bottom:6px;right:6px;font-size:11px;padding:3px 8px;margin:0;background:var(--panel);border:1px solid var(--border);color:var(--dim);border-radius:5px;opacity:.5;transition:opacity .15s,color .15s} .copybtn{position:absolute;bottom:6px;right:6px;font-size:11px;padding:3px 8px;margin:0;background:var(--panel);border:1px solid var(--border);color:var(--dim);border-radius:3px;opacity:.5;transition:opacity .15s,color .15s}
.msg.assistant .body pre:hover .copybtn,.msg.reasoning .body pre:hover .copybtn{opacity:1} .msg.assistant .body pre:hover .copybtn,.msg.reasoning .body pre:hover .copybtn{opacity:1}
.copybtn:hover{border-color:var(--accent);color:var(--accent)} .copybtn:hover{border-color:var(--accent);color:var(--accent)}
.copybtn.done{color:var(--ok);border-color:var(--ok);opacity:1} .copybtn.done{color:var(--ok);border-color:var(--ok);opacity:1}
@@ -429,7 +429,7 @@ button:disabled{opacity:.5;cursor:not-allowed}
.diff-count .a{color:var(--add)} .diff-count .a{color:var(--add)}
.diff-count .d{color:var(--del)} .diff-count .d{color:var(--del)}
.msg.tool .tool-sub{font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.1em;margin:8px 0 4px} .msg.tool .tool-sub{font-size:10px;color:var(--dim);text-transform:uppercase;letter-spacing:.1em;margin:8px 0 4px}
.msg.tool pre{position:relative;background:var(--code-bg);border:1px solid var(--border);border-radius:6px;padding:8px 10px;overflow:auto;margin:0;max-height:280px} .msg.tool pre{position:relative;background:var(--code-bg);border:1px solid var(--border);border-radius:3px;padding:8px 10px;overflow:auto;margin:0;max-height:280px}
.msg.tool pre code{background:transparent;padding:0;font-size:.82em;line-height:1.4;white-space:pre} .msg.tool pre code{background:transparent;padding:0;font-size:.82em;line-height:1.4;white-space:pre}
.msg.tool .tool-caret{color:var(--accent);animation:toolblink 1s step-end infinite} .msg.tool .tool-caret{color:var(--accent);animation:toolblink 1s step-end infinite}
.msg.tool .tool-wait{font-size:12px;color:var(--dim);margin-top:8px;animation:toolblink 1.2s ease-in-out infinite} .msg.tool .tool-wait{font-size:12px;color:var(--dim);margin-top:8px;animation:toolblink 1.2s ease-in-out infinite}
@@ -567,7 +567,7 @@ details>summary+.subhead{margin-top:4px}
rapport à la ligne, un débordement caché les TRONQUERAIT — elles doivent rapport à la ligne, un débordement caché les TRONQUERAIT — elles doivent
pouvoir déborder de la carte. Les lignes n'ont pas de fond propre, l'arrondi pouvoir déborder de la carte. Les lignes n'ont pas de fond propre, l'arrondi
n'a donc rien à rogner. */ n'a donc rien à rogner. */
.slist{background:var(--panel);border:1px solid var(--border);border-radius:9px; .slist{background:var(--panel);border:1px solid var(--border);border-radius:4px;
overflow:visible;margin:0 0 8px} overflow:visible;margin:0 0 8px}
.slist:last-child{margin-bottom:0} .slist:last-child{margin-bottom:0}
/* Contenu libre (champ + boutons) : le rembourrage passe sur la carte. */ /* Contenu libre (champ + boutons) : le rembourrage passe sur la carte. */
@@ -616,10 +616,10 @@ details>summary+.subhead{margin-top:4px}
/* --- Champs et boutons --- */ /* --- Champs et boutons --- */
.sctl,.io-input{width:100%;background:var(--bg);color:var(--text);border:1px solid var(--border); .sctl,.io-input{width:100%;background:var(--bg);color:var(--text);border:1px solid var(--border);
border-radius:7px;padding:7px 9px;font:inherit;font-size:12.5px} border-radius:3px;padding:7px 9px;font:inherit;font-size:12.5px}
.sctl:focus,.io-input:focus{outline:none;border-color:var(--dim)} .sctl:focus,.io-input:focus{outline:none;border-color:var(--dim)}
textarea.sctl{min-height:76px;resize:vertical;line-height:1.5} textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
.side button:not(.preset-edit):not(.addbtn):not(.preset-add):not(.copybtn):not(.iconbtn){font-size:12px;padding:6px 10px;border-radius:7px} .side button:not(.preset-edit):not(.addbtn):not(.preset-add):not(.copybtn):not(.iconbtn){font-size:12px;padding:6px 10px;border-radius:3px}
.side .row{display:flex;gap:6px;align-items:center;margin:6px 0} .side .row{display:flex;gap:6px;align-items:center;margin:6px 0}
.side .row .sctl,.side .row .io-input{flex:1;min-width:0} .side .row .sctl,.side .row .io-input{flex:1;min-width:0}
/* Bouton d'action à côté d'un champ (copier, ouvrir) : un CARRÉ à la hauteur du /* Bouton d'action à côté d'un champ (copier, ouvrir) : un CARRÉ à la hauteur du
@@ -627,7 +627,7 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
posait de travers et changeait de largeur d'une police à l'autre. */ posait de travers et changeait de largeur d'une police à l'autre. */
.iconbtn{flex:none;display:inline-flex;align-items:center;justify-content:center; .iconbtn{flex:none;display:inline-flex;align-items:center;justify-content:center;
width:34px;height:34px;padding:0;margin:0;color:var(--dim);background:var(--panel); width:34px;height:34px;padding:0;margin:0;color:var(--dim);background:var(--panel);
border:1px solid var(--border);border-radius:9px;cursor:pointer} border:1px solid var(--border);border-radius:4px;cursor:pointer}
.iconbtn:hover{color:var(--text);border-color:var(--dim);background:var(--accent-bg)} .iconbtn:hover{color:var(--text);border-color:var(--dim);background:var(--accent-bg)}
.iconbtn svg{width:15px;height:15px;display:block} .iconbtn svg{width:15px;height:15px;display:block}
.side .btn-danger-o{border-color:var(--err);color:var(--err)} .side .btn-danger-o{border-color:var(--err);color:var(--err)}
@@ -670,10 +670,10 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
#sendhint{padding:0 12px 6px;font-size:10px;text-align:center;opacity:.7} #sendhint{padding:0 12px 6px;font-size:10px;text-align:center;opacity:.7}
/* Moteur pas encore prêt : l'aide clavier cède la place à l'état du chargement. */ /* Moteur pas encore prêt : l'aide clavier cède la place à l'état du chargement. */
#sendhint.waiting{opacity:1;color:var(--dim)} #sendhint.waiting{opacity:1;color:var(--dim)}
#input{flex:1;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:10px;font:inherit;resize:none;min-height:56px;max-height:200px;overflow-y:hidden} #input{flex:1;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:3px;padding:10px;font:inherit;resize:none;min-height:56px;max-height:200px;overflow-y:hidden}
#input:focus{outline:none;border-color:var(--accent)} #input:focus{outline:none;border-color:var(--accent)}
.muted{color:var(--dim);font-size:13px} .muted{color:var(--dim);font-size:13px}
#toast{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);z-index:50;background:var(--panel);border:1px solid var(--border);padding:8px 16px;border-radius:8px;opacity:0;transition:opacity .2s;pointer-events:none;box-shadow:0 8px 30px rgba(0,0,0,.18)} #toast{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);z-index:50;background:var(--panel);border:1px solid var(--border);padding:8px 16px;border-radius:4px;opacity:0;transition:opacity .2s;pointer-events:none;box-shadow:0 8px 30px rgba(0,0,0,.18)}
#toast.show{opacity:1} #toast.show{opacity:1}
@keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}} @keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}
/* iOS Safari auto-zoome au focus sur tout champ < 16px. On force 16px sur les /* iOS Safari auto-zoome au focus sur tout champ < 16px. On force 16px sur les
@@ -697,7 +697,7 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
rétrécit le contenu. Padding interne normal → largeur utile pleine. */ rétrécit le contenu. Padding interne normal → largeur utile pleine. */
.side{width:300px;max-width:300px;left:env(safe-area-inset-left);padding:calc(14px + env(safe-area-inset-top)) 14px calc(14px + env(safe-area-inset-bottom)) 14px} .side{width:300px;max-width:300px;left:env(safe-area-inset-left);padding:calc(14px + env(safe-area-inset-top)) 14px calc(14px + env(safe-area-inset-bottom)) 14px}
#chat{padding:48px 10px 6px;gap:7px} #chat{padding:48px 10px 6px;gap:7px}
.msg{padding:6px 10px;border-radius:8px;max-width:78%} .msg{padding:6px 10px;border-radius:4px;max-width:78%}
#chat .msg.assistant,#chat .msg.reasoning{max-width:88%} #chat .msg.assistant,#chat .msg.reasoning{max-width:88%}
#inputbar{padding:5px 8px;gap:6px} #inputbar{padding:5px 8px;gap:6px}
/* PAS de font-size < 16px ici : iOS Safari zoome à la sélection du champ. On /* PAS de font-size < 16px ici : iOS Safari zoome à la sélection du champ. On
@@ -724,7 +724,7 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
.lc-mode-t{font-size:13px;font-weight:600;color:var(--text);display:flex;align-items:center;gap:6px} .lc-mode-t{font-size:13px;font-weight:600;color:var(--text);display:flex;align-items:center;gap:6px}
/* « conseillé » : pilule PLEINE (texte inversé). En contour, elle héritait du /* « conseillé » : pilule PLEINE (texte inversé). En contour, elle héritait du
gris atténué du thème monochrome et devenait illisible en mode sombre. */ gris atténué du thème monochrome et devenait illisible en mode sombre. */
.lc-reco{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;background:var(--text);color:var(--bg);border:1px solid var(--text);border-radius:8px;padding:1px 6px} .lc-reco{font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;background:var(--text);color:var(--bg);border:1px solid var(--text);border-radius:4px;padding:1px 6px}
.lc-mode-d{font-size:11px;color:var(--dim);margin-top:3px} .lc-mode-d{font-size:11px;color:var(--dim);margin-top:3px}
/* Hauteur réservée : la ligne d'état arrive après l'appel réseau, sans elle /* Hauteur réservée : la ligne d'état arrive après l'appel réseau, sans elle
les trois cartes grandissaient d'un cran une fois l'état connu. */ les trois cartes grandissaient d'un cran une fois l'état connu. */
@@ -753,7 +753,7 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
affichée avec une seule carte). */ affichée avec une seule carte). */
.pe-group[hidden]{display:none} .pe-group[hidden]{display:none}
.pe-gh{font-size:12px;font-weight:600;color:var(--dim);padding:0 6px} .pe-gh{font-size:12px;font-weight:600;color:var(--dim);padding:0 6px}
.pe-list{background:var(--panel);border:1px solid var(--border);border-radius:12px;overflow:hidden} .pe-list{background:var(--panel);border:1px solid var(--border);border-radius:3px;overflow:hidden}
/* Ligne : libellé à gauche, contrôle à droite, séparateur fin entre lignes. */ /* Ligne : libellé à gauche, contrôle à droite, séparateur fin entre lignes. */
.pe-row{display:flex;align-items:center;gap:14px;min-height:48px;padding:9px 15px} .pe-row{display:flex;align-items:center;gap:14px;min-height:48px;padding:9px 15px}
/* Séparateur porté par la ligne SUIVANTE (border-top), pas par la précédente /* Séparateur porté par la ligne SUIVANTE (border-top), pas par la précédente
@@ -806,9 +806,9 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
.gpu-legend i.alt{background:color-mix(in srgb,var(--accent) 42%,transparent)} .gpu-legend i.alt{background:color-mix(in srgb,var(--accent) 42%,transparent)}
.gpu-shares{display:flex;flex-wrap:wrap;gap:10px} .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{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{width:62px;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:3px;padding:5px 7px;font:inherit;text-align:right}
.gpu-share input:focus{outline:none;border-color:var(--accent)} .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{width:100%;box-sizing:border-box;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:4px;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-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{border:none;background:transparent;padding:6px 0;font-size:15px}
.pe-row.stack.plain .pe-inp:focus{box-shadow:none} .pe-row.stack.plain .pe-inp:focus{box-shadow:none}
@@ -819,13 +819,13 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
/* Le bouton reprend EXACTEMENT le cadre du champ (mêmes coins, même bordure) : /* 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 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. */ 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{flex:none;margin:0;border:1px solid var(--border);border-radius:0 4px 4px 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 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:focus-within .pe-inp,.pe-inpbtn:focus-within button{border-color:var(--accent)}
.pe-inpbtn button:disabled{opacity:.5;cursor:default} .pe-inpbtn button:disabled{opacity:.5;cursor:default}
/* Contrôle segmenté (moteur) façon iOS. */ /* 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{display:flex;gap:3px;background:color-mix(in srgb,var(--dim) 16%,transparent);border-radius:4px;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{flex:1;text-align:center;font-size:13px;font-weight:500;padding:7px 4px;border-radius:3px;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>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 input{display:none}
.pe-seg .be-note{display:none} .pe-seg .be-note{display:none}
@@ -851,7 +851,7 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
.pe-switch input:checked+.pe-sl::before{transform:translateX(16px);background:var(--bg)} .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)} .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. */ /* 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{background:var(--panel);border:1px solid var(--border);border-radius:3px;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{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::-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>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}
@@ -874,7 +874,7 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
align-items:center;gap:8px;flex-wrap:nowrap} 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-l{display:flex;align-items:center;gap:8px;min-width:0}
.pe-foot .foot-r{display:flex;gap:8px;flex:none} .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; .pe-btn{margin:0;padding:7px 15px;font-size:12.5px;border-radius:4px;font-weight:600;cursor:pointer;
border:1px solid var(--border);background:var(--panel);color:var(--text);white-space:nowrap} border:1px solid var(--border);background:var(--panel);color:var(--text);white-space:nowrap}
.pe-btn:hover{border-color:var(--dim)} .pe-btn:hover{border-color:var(--dim)}
.pe-btn.primary{border-color:var(--accent);background:var(--accent);color:var(--bg)} .pe-btn.primary{border-color:var(--accent);background:var(--accent);color:var(--bg)}
@@ -883,7 +883,7 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
autant que « enregistrer » alors que c'est l'action rare — et à trois boutons autant que « enregistrer » alors que c'est l'action rare — et à trois boutons
larges, le pied passait à deux lignes sur téléphone. */ 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; .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} margin:0;padding:0;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--err);cursor:pointer}
.pe-del svg{width:15px;height:15px;display:block} .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)} .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 /* Option d'une confirmation : une PASTILLE à cocher, pas une case système suivie
@@ -914,7 +914,7 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
@media (prefers-reduced-motion:reduce){ @media (prefers-reduced-motion:reduce){
.modal-ov,.ask-overlay,.modal-card,.ask-box{transition:none} .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-card{position:relative;background:var(--bg);border:1px solid var(--border);border-radius:3px;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{padding:14px 16px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center}
.modal-head strong{font-size:14px} .modal-head strong{font-size:14px}
/* Croix de fermeture : un CARRÉ, pas un rectangle. Avec un padding asymétrique /* Croix de fermeture : un CARRÉ, pas un rectangle. Avec un padding asymétrique
@@ -935,7 +935,7 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
preset au nouveau SOUS LES YEUX, une fois le chargement terminé. */ 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-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; .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} align-items:center;justify-content:center;background:var(--bg);border-radius:0 0 5px 5px}
.modal-ov.loading .modal-load{display:flex} .modal-ov.loading .modal-load{display:flex}
/* visibility (et non display) : le formulaire garde sa place, donc la carte a /* 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. */ déjà sa taille finale — la révéler ne provoque plus d'à-coup de hauteur. */
@@ -956,10 +956,10 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
.fld-l{font-size:11px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;color:var(--dim)} .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-l .opt{text-transform:none;letter-spacing:0;font-weight:400;opacity:.65}
.fld-hint{font-size:11px;color:var(--dim);margin-top:2px} .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{width:100%;box-sizing:border-box;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:3px;padding:9px 11px;font:inherit}
.ctl:focus{outline:none;border-color:var(--accent)} .ctl:focus{outline:none;border-color:var(--accent)}
.ta{min-height:220px;resize:vertical;line-height:1.5} .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{border:1px solid var(--border);border-radius:4px;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)} .card-h{font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;color:var(--accent)}
.row{display:flex;gap:6px} .row{display:flex;gap:6px}
.row .ctl{flex:1;min-width:0} .row .ctl{flex:1;min-width:0}
@@ -969,7 +969,7 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
.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{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} .input-btn .btn-in:hover{border-left:0}
.seg{display:flex;gap:6px} .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{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:4px;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:hover{border-color:var(--accent)}
.seg>label:has(input:checked){border-color:var(--accent);background:var(--accent-bg);color:var(--accent)} .seg>label:has(input:checked){border-color:var(--accent);background:var(--accent-bg);color:var(--accent)}
.seg input{display:none} .seg input{display:none}
@@ -981,12 +981,12 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
.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{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 #lc-log-caret{font-size:9px;transition:transform .12s;display:inline-block}
.lc-logbar.open #lc-log-caret{transform:rotate(90deg)} .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-log{background:var(--panel);border:1px solid var(--border);border-radius:3px;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} .lc-spin{display:inline-block;animation:spin 1s linear infinite}
/* Serveurs MCP */ /* Serveurs MCP */
.mcp-list{display:flex;flex-direction:column;gap:4px} .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{display:flex;align-items:center;gap:8px;padding:6px 10px;border:1px solid var(--border);border-radius:3px;background:var(--panel);cursor:pointer}
.mcp-row:hover{border-color:var(--accent)} .mcp-row:hover{border-color:var(--accent)}
.mcp-row.off{opacity:.6} .mcp-row.off{opacity:.6}
.mcp-dot{flex:none;width:8px;height:8px;border-radius:50%} .mcp-dot{flex:none;width:8px;height:8px;border-radius:50%}
@@ -996,7 +996,7 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
.mcp-info{flex:1;min-width:0} .mcp-info{flex:1;min-width:0}
.mcp-name{font-size:12px;color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .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-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-tag{font-size:10px;padding:1px 7px;border-radius:4px;border:1px solid var(--border);color:var(--dim);text-transform:uppercase;letter-spacing:.04em}
/* Version du moteur : même pastille, mais SANS majuscules forcées — un numéro /* Version du moteur : même pastille, mais SANS majuscules forcées — un numéro
de build et un hash de commit se recopient et se comparent à l'œil, et de build et un hash de commit se recopient et se comparent à l'œil, et
« AAAA111 » n'est pas le commit « aaaa111 ». Le monospace vient de là aussi. */ « AAAA111 » n'est pas le commit « aaaa111 ». Le monospace vient de là aussi. */
@@ -1007,26 +1007,34 @@ textarea.sctl{min-height:76px;resize:vertical;line-height:1.5}
.mcp-edit{flex:none;padding:2px 5px;font-size:13px;line-height:1;background:transparent;border:none;opacity:.5} .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)} .mcp-edit:hover{opacity:1;color:var(--accent)}
/* Liste des outils MCP dans la modale */ /* Liste des outils MCP dans la modale */
/* Sélecteur d'intensité du raisonnement, dans la barre de saisie. Discret par
défaut (même poids visuel que les icônes voisines), il ne s'affirme qu'au
survol ou quand une intensité est imposée. */
#think-effort{flex:none;align-self:center;height:22px;padding:0 4px;font-size:11px;
color:var(--dim);background:transparent;border:1px solid transparent;border-radius:4px;cursor:pointer}
#think-effort:hover:not(:disabled){color:var(--text);border-color:var(--border)}
#think-effort:disabled{opacity:.4;cursor:not-allowed}
.mcp-actions{display:flex;gap:6px;margin-top:6px} .mcp-actions{display:flex;gap:6px;margin-top:6px}
.mcp-cat-tabs{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px} .mcp-cat-tabs{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:8px}
.mcp-cat{font-size:11px;padding:2px 9px;border-radius:9px;border:1px solid var(--border);background:transparent;color:var(--dim);cursor:pointer} .mcp-cat{font-size:11px;padding:2px 9px;border-radius:4px;border:1px solid var(--border);background:transparent;color:var(--dim);cursor:pointer}
.mcp-cat:hover{border-color:var(--accent);color:var(--text)} .mcp-cat:hover{border-color:var(--accent);color:var(--text)}
.mcp-cat.on{border-color:var(--accent);color:var(--accent)} .mcp-cat.on{border-color:var(--accent);color:var(--accent)}
.mcp-cat-list{display:flex;flex-direction:column;gap:4px;max-height:52vh;overflow:auto} .mcp-cat-list{display:flex;flex-direction:column;gap:4px;max-height:52vh;overflow:auto}
.mcp-cat-row{display:flex;align-items:center;gap:8px;padding:7px 10px;border:1px solid var(--border);border-radius:6px;background:var(--panel)} .mcp-cat-row{display:flex;align-items:center;gap:8px;padding:7px 10px;border:1px solid var(--border);border-radius:3px;background:var(--panel)}
.mcp-cat-row:hover{border-color:var(--accent)} .mcp-cat-row:hover{border-color:var(--accent)}
.mcp-cat-desc{font-size:11px;color:var(--dim);margin-top:2px;line-height:1.35} .mcp-cat-desc{font-size:11px;color:var(--dim);margin-top:2px;line-height:1.35}
.mcp-cat-cmd{font-size:10px;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:340px} .mcp-cat-cmd{font-size:10px;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:340px}
.mcp-cat-use{flex:none} .mcp-cat-use{flex:none}
.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-tools-list{display:flex;flex-direction:column;gap:2px;max-height:240px;overflow:auto;border:1px solid var(--border);border-radius:3px;padding:6px}
.mcp-tool-row{display:flex;align-items:center;gap:10px;padding:4px 6px;border-radius:5px;cursor:pointer} .mcp-tool-row{display:flex;align-items:center;gap:10px;padding:4px 6px;border-radius:3px;cursor:pointer}
.mcp-tool-row:hover{background:var(--panel)} .mcp-tool-row:hover{background:var(--panel)}
.mcp-tool-switch{flex:none;transform:scale(.85)} .mcp-tool-switch{flex:none;transform:scale(.85)}
.mcp-tool-name{font-size:12px;color:var(--text);word-break:break-all} .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 /* Accès OpenAI / Accès distant : champs lecture seule (URL, clé) + libellés
compacts, pour une présentation homogène et sans surcharge visuelle. */ 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} .io-input{flex:1;min-width:0;background:var(--panel);color:var(--text);border:1px solid var(--border);border-radius:4px;padding:8px 10px;font:inherit;font-size:13px}
/* Modale MCP (ajout/édition) : les champs des groupes stdio/http n'avaient aucun /* 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. espacement vertical (le conteneur n'est pas un flex à gap) → ils se touchaient.
@@ -1053,7 +1061,7 @@ h1{color:var(--text);font-weight:600;letter-spacing:-.01em}
/* Réponses de l'assistant : pas de cadre, pas de fond — juste du texte. */ /* 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} #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. */ /* 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{background:var(--bubble);color:var(--text);border-radius:3px;padding:11px 18px;max-width:75%;line-height:1.6}
#chat .msg.user .label{color:var(--dim)} #chat .msg.user .label{color:var(--dim)}
/* Libellés : l'amont les masquait tous ici, les mots « user » / « assistant » /* 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 n'ajoutant rien à l'alignement. Ils portent désormais un avatar et un prénom
@@ -1079,10 +1087,10 @@ html[data-hide-stats="1"] #chat .msg .statline{display:none}
@keyframes statin{from{opacity:0;transform:translateY(3px)}to{opacity:.75;transform:none}} @keyframes statin{from{opacity:0;transform:translateY(3px)}to{opacity:.75;transform:none}}
@media (prefers-reduced-motion:reduce){#chat .msg .statline-in{animation:none}} @media (prefers-reduced-motion:reduce){#chat .msg .statline-in{animation:none}}
/* Raisonnement / outils : encart gris discret plutôt qu'un cadre pointillé. */ /* 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,#chat .msg.tool{background:var(--panel);border:1px solid var(--border);border-radius:3px;color:var(--dim);padding:12px 16px}
#chat .msg.reasoning{color:var(--dim)} #chat .msg.reasoning{color:var(--dim)}
#chat .msg.tool .tool-head{color:var(--text)} #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 pre,#chat .msg.reasoning .body pre,#chat .msg.tool pre{border-radius:3px;padding:12px 14px}
#chat .msg.assistant .body a{text-decoration:underline;text-underline-offset:2px} #chat .msg.assistant .body a{text-decoration:underline;text-underline-offset:2px}
/* Composer = UNE seule carte posée PAR-DESSUS le fil : la conversation /* 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) continue de défiler DERRIÈRE elle et s'efface en fondu (dégradé vers le fond)
@@ -1110,7 +1118,7 @@ html[data-hide-stats="1"] #chat .msg .statline{display:none}
trou en PWA iOS. */ trou en PWA iOS. */
#chat{padding-bottom:calc(var(--composer-h,170px) + 12px)} #chat{padding-bottom:calc(var(--composer-h,170px) + 12px)}
#scrollbtn{bottom:calc(var(--composer-h,170px) + 10px);border-radius:999px} #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} #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:4px 4px 0 0}
#input{background:transparent;border:none;border-radius:0;padding:9px 0;min-height:36px;line-height:18px;max-height:160px} #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 /* Pas de cadre qui s'assombrit au focus : la carte garde exactement la même
bordure, seule la saisie change (curseur). */ bordure, seule la saisie change (curseur). */
@@ -1120,7 +1128,7 @@ html[data-hide-stats="1"] #chat .msg .statline{display:none}
(le navigateur l'écrase, d'où les coins bizarres) : on laisse donc la carte (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 découper la jauge dans sa propre forme — la barre est bien épaisse ET rognée
exactement sur la courbe, symétrique du haut. */ 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} #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 8px 8px;overflow:hidden}
/* ⚠️ NE PAS piloter la position du composer en JS quand le clavier s'ouvre /* ⚠️ 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 (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 coque à visualViewport.height, ni décaler le composer de la hauteur du
@@ -1206,16 +1214,16 @@ html[data-hide-stats="1"] #chat .msg .statline{display:none}
#dropveil{position:absolute;inset:0;z-index:20;display:none;align-items:center; #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} justify-content:center;padding:24px;background:rgba(0,0,0,.28);pointer-events:none}
#dropveil.show{display:flex} #dropveil.show{display:flex}
#dropveil>div{padding:22px 30px;border:2px dashed var(--border);border-radius:20px; #dropveil>div{padding:22px 30px;border:2px dashed var(--border);border-radius:3px;
background:var(--panel);color:var(--text);font-size:14px;box-shadow:0 8px 30px rgba(0,0,0,.18)} 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. */ /* Reste de l'UI : coins arrondis et contrastes doux, dans le même esprit. */
button,.mem-search{border-radius:10px} button,.mem-search{border-radius:4px}
/* Champ + bouton accolés : on ne garde l'arrondi que sur les coins extérieurs. */ /* 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 .pe-inp{border-radius:4px 0 0 4px}
.pe-inpbtn button{border-radius:0 10px 10px 0} .pe-inpbtn button{border-radius:0 4px 4px 0}
.input-btn .ctl{border-radius:10px 0 0 10px} .input-btn .ctl{border-radius:4px 0 0 4px}
.input-btn .btn-in{border-radius:0 10px 10px 0} .input-btn .btn-in{border-radius:0 4px 4px 0}
.preset{border-radius:12px} .preset{border-radius:3px}
/* Les presets restent des cartes (fond + bordure) : à plat, sans fond ni /* 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 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 signale par sa bordure — un simple voile d'accent (7 % de blanc sur fond
@@ -1236,7 +1244,7 @@ button:hover{border-color:var(--dim);color:var(--text)}
/* ===== Fenêtres et sélecteurs ===================================== /* ===== Fenêtres et sélecteurs =====================================
Coins arrondis et ombre douce plutôt qu'un cadre marqué. */ 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,.modal-box,.pe-list,.pe-adv{border-radius:3px}
.modal-card,.ask-box{box-shadow:0 8px 30px rgba(0,0,0,.12)} .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. */ /* 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)} .pe-seg>label:has(input:checked),.seg>label:has(input:checked){background:var(--text);border-color:var(--text);color:var(--bg)}
@@ -1279,7 +1287,7 @@ button:hover{border-color:var(--dim);color:var(--text)}
/* Barre latérale : pile de cartes, sans filet ni bordure de colonne. */ /* Barre latérale : pile de cartes, sans filet ni bordure de colonne. */
.side{width:312px;border-right:0;padding:0;background:transparent; .side{width:312px;border-right:0;padding:0;background:transparent;
display:flex;flex-direction:column;gap:12px;overflow-y:auto;overscroll-behavior:contain} 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; .sidecard,.side>details{background:var(--panel);border:0;border-top:0;border-radius:3px;
box-shadow:var(--shadow);margin:0;padding:2px 16px} box-shadow:var(--shadow);margin:0;padding:2px 16px}
.sidecard{padding:15px 18px 14px;display:flex;flex-direction:column;gap:2px} .sidecard{padding:15px 18px 14px;display:flex;flex-direction:column;gap:2px}
.side>details[open]{padding-bottom:14px} .side>details[open]{padding-bottom:14px}
@@ -1289,10 +1297,10 @@ button:hover{border-color:var(--dim);color:var(--text)}
.side-sep{border-top:0;margin:10px 0 0;padding:0 6px} .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 /* Blocs encastrés DANS une carte : un ton plus chaud, sans bordure — deux
cadres imbriqués alourdiraient inutilement. */ cadres imbriqués alourdiraient inutilement. */
.side .slist{background:var(--row-bg);border-color:transparent;border-radius:14px} .side .slist{background:var(--row-bg);border-color:transparent;border-radius:3px}
/* Colonne principale : une seule grande carte qui contient fil et saisie. */ /* Colonne principale : une seule grande carte qui contient fil et saisie. */
.main{background:var(--panel);border-radius:20px;box-shadow:var(--shadow);overflow:hidden} .main{background:var(--panel);border-radius:3px;box-shadow:var(--shadow);overflow:hidden}
#chat{padding-top:26px;padding-inline:max(20px,calc(50% - 360px))} #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 /* 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. */ vers le PANNEAU, pas vers le fond de la coque. */
@@ -1314,13 +1322,13 @@ button:hover{border-color:var(--dim);color:var(--text)}
3 px sur le seul bord gauche se lisait mal et déséquilibrait la ligne. La 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 bordure transparente est portée par tous les états : la sélection ne décale
donc pas le contenu d'un pixel. */ donc pas le contenu d'un pixel. */
.side .preset{border:1px solid transparent;border-radius:14px;background:transparent} .side .preset{border:1px solid transparent;border-radius:3px;background:transparent}
.side .preset.active{background:var(--row-bg-on);border-color:var(--accent)} .side .preset.active{background:var(--row-bg-on);border-color:var(--accent)}
.side .preset:hover{background:var(--row-bg)} .side .preset:hover{background:var(--row-bg)}
/* Bulle de question : coin bas-droit rentré, comme une bulle qui pointe vers /* Bulle de question : coin bas-droit rentré, comme une bulle qui pointe vers
son auteur. */ son auteur. */
#chat .msg.user{border-radius:20px 20px 6px 20px} #chat .msg.user{border-radius:4px}
} }
/* Action principale en sauge : c'est la couleur d'action de la charte, et elle /* 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 se distingue enfin du texte (le bouton reprenait --text, donc un brun sombre
@@ -1359,7 +1367,7 @@ button:hover{border-color:var(--dim);color:var(--text)}
l'application de l'autre, tiennent maintenant chacune dans UNE carte, avec un l'application de l'autre, tiennent maintenant chacune dans UNE carte, avec un
filet discret entre les lignes. */ filet discret entre les lignes. */
@media (min-width:721px){ @media (min-width:721px){
.sidegroup{background:var(--panel);border-radius:20px;box-shadow:var(--shadow); .sidegroup{background:var(--panel);border-radius:3px;box-shadow:var(--shadow);
padding:2px 16px;display:flex;flex-direction:column} padding:2px 16px;display:flex;flex-direction:column}
/* Dans un groupe, les sections ne sont plus des cartes : elles redeviennent /* Dans un groupe, les sections ne sont plus des cartes : elles redeviennent
des lignes, séparées par un filet — sauf la première. */ des lignes, séparées par un filet — sauf la première. */
@@ -1376,7 +1384,7 @@ button:hover{border-color:var(--dim);color:var(--text)}
quantifications, et la modale ne doit pas s'étirer jusqu'à sortir de quantifications, et la modale ne doit pas s'étirer jusqu'à sortir de
l'écran. */ l'écran. */
.hf-list{display:flex;flex-direction:column;max-height:220px;overflow-y:auto; .hf-list{display:flex;flex-direction:column;max-height:220px;overflow-y:auto;
border:1px solid var(--border);border-radius:12px;margin-top:6px} border:1px solid var(--border);border-radius:3px;margin-top:6px}
.hf-row{display:flex;align-items:center;gap:10px;padding:7px 12px;cursor:pointer; .hf-row{display:flex;align-items:center;gap:10px;padding:7px 12px;cursor:pointer;
border-top:1px solid var(--border);font-size:12px} border-top:1px solid var(--border);font-size:12px}
.hf-row:first-child{border-top:0} .hf-row:first-child{border-top:0}
@@ -1412,12 +1420,12 @@ html[data-files="1"] #files{display:flex}
.files-head{display:flex;align-items:center;gap:8px} .files-head{display:flex;align-items:center;gap:8px}
.files-head .stitle{flex:1} .files-head .stitle{flex:1}
#files-refresh,#files-close{margin:0;padding:2px 8px;min-height:0;font-size:12px; #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} background:transparent;border:1px solid transparent;color:var(--dim);border-radius:4px}
#files-refresh:hover,#files-close:hover{border-color:var(--border);color:var(--text)} #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 /* Fil d'Ariane : il doit pouvoir revenir à la ligne — un identifiant de
discussion dans le chemin dépasse largement 320 px. */ 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{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; .files-crumb-b{margin:0;padding:2px 6px;min-height:0;font-size:11px;border:0;border-radius:3px;
background:transparent;color:var(--dim);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} 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:hover:not(:disabled){background:var(--row-bg-on);color:var(--text)}
.files-crumb-b.on{color:var(--text);cursor:default} .files-crumb-b.on{color:var(--text);cursor:default}
@@ -1427,7 +1435,7 @@ html[data-files="1"] #files{display:flex}
/* Une ligne = pastille, nom + méta, actions. Les actions n'apparaissent qu'au /* 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 survol : au repos on veut lire des noms de fichiers, pas une grille de
boutons. Sur écran tactile (pas de survol) elles restent visibles. */ 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{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:3px;font-size:12px}
.files-row:hover{background:var(--row-bg-on)} .files-row:hover{background:var(--row-bg-on)}
.files-ic{flex:none;width:12px;text-align:center;color:var(--dim)} .files-ic{flex:none;width:12px;text-align:center;color:var(--dim)}
.files-row.isdir .files-ic{color:var(--accent)} .files-row.isdir .files-ic{color:var(--accent)}
@@ -1438,7 +1446,7 @@ html[data-files="1"] #files{display:flex}
.files-row:hover .files-acts,.files-row:focus-within .files-acts{opacity:1} .files-row:hover .files-acts,.files-row:focus-within .files-acts{opacity:1}
@media (hover:none){.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; .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} border:1px solid transparent;border-radius:3px;color:var(--dim);cursor:pointer;margin:0;min-height:0}
.files-acts .iconbtn:hover{border-color:var(--border);color:var(--text)} .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); .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} display:flex;align-items:center;gap:6px;flex-wrap:wrap}
@@ -1446,7 +1454,7 @@ html[data-files="1"] #files{display:flex}
secours pour les fichiers d'avant le rangement par discussion, pas une vue secours pour les fichiers d'avant le rangement par discussion, pas une vue
qu'on propose au même rang que la discussion ouverte. */ 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; .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)} background:transparent;border:1px solid var(--border);border-radius:3px;color:var(--dim)}
.files-scope:hover{color:var(--text);background:var(--row-bg-on)} .files-scope:hover{color:var(--text);background:var(--row-bg-on)}
/* Téléchargement en cours : downloadWorkspaceFile pose .busy sur la ligne. */ /* Téléchargement en cours : downloadWorkspaceFile pose .busy sur la ligne. */
.files-row.busy{opacity:.55;pointer-events:none} .files-row.busy{opacity:.55;pointer-events:none}
@@ -1469,7 +1477,7 @@ html[data-files="1"] #files-btn{color:var(--accent)}
} }
@media (min-width:721px){ @media (min-width:721px){
/* Dans la charte « Terre », le panneau est une carte comme les autres. */ /* Dans la charte « Terre », le panneau est une carte comme les autres. */
#files{background:var(--panel);border-left-color:transparent;border-radius:20px; #files{background:var(--panel);border-left-color:transparent;border-radius:3px;
box-shadow:var(--shadow);margin:0} box-shadow:var(--shadow);margin:0}
} }
@@ -1525,7 +1533,7 @@ html[data-files="1"] #files-btn{color:var(--accent)}
#topbar-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; #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)} font-size:13.5px;font-weight:500;color:var(--text)}
#topbar .iconbtn{width:30px;height:30px;flex:none;display:inline-flex;align-items:center; #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); justify-content:center;border:0;border-radius:4px;background:transparent;color:var(--dim);
cursor:pointer;margin:0;padding:0;transition:background .15s,color .15s,transform .1s} 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:hover{background:var(--row-bg-on);color:var(--text)}
#topbar .iconbtn:active{transform:scale(.92)} #topbar .iconbtn:active{transform:scale(.92)}
@@ -1549,7 +1557,7 @@ html[data-files="1"] #files-btn{color:var(--accent)}
/* La liste EST la barre latérale : titre à gauche, heure à droite, une ligne. /* La liste EST la barre latérale : titre à gauche, heure à droite, une ligne.
Le reste (réglages) descend sous un unique repli. */ Le reste (réglages) descend sous un unique repli. */
#conv-search{width:calc(100% - 24px);font:inherit;font-size:12.5px;color:var(--text); #conv-search{width:calc(100% - 24px);font:inherit;font-size:12.5px;color:var(--text);
background:var(--panel);border:1px solid var(--border);border-radius:9px; background:var(--panel);border:1px solid var(--border);border-radius:4px;
padding:7px 11px;margin:0 12px 8px} padding:7px 11px;margin:0 12px 8px}
#conv-search::placeholder{color:var(--dim)} #conv-search::placeholder{color:var(--dim)}
#conv-search:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-bg)} #conv-search:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-bg)}
@@ -1603,7 +1611,7 @@ html[data-files="1"] #files-btn{color:var(--accent)}
⚠️ Ne concerne pas les boutons-icônes de la saisie et de l'en-tête (#send, ⚠️ Ne concerne pas les boutons-icônes de la saisie et de l'en-tête (#send,
#stop, #attach, #menubtn…) : ils posent `border:0` PLUS BAS dans la feuille #stop, #attach, #menubtn…) : ils posent `border:0` PLUS BAS dans la feuille
et gardent leur allure nue, qui est la bonne pour un pictogramme seul. */ et gardent leur allure nue, qui est la bonne pour un pictogramme seul. */
button{background:var(--panel);border-color:var(--border);border-radius:9px; button{background:var(--panel);border-color:var(--border);border-radius:4px;
transition:background .15s ease,color .15s ease,border-color .15s ease,transform .08s ease} transition:background .15s ease,color .15s ease,border-color .15s ease,transform .08s ease}
button:hover{background:var(--row-bg);border-color:var(--dim);color:var(--text)} button:hover{background:var(--row-bg);border-color:var(--dim);color:var(--text)}
button:active:not(:disabled){transform:translateY(1px)} button:active:not(:disabled){transform:translateY(1px)}
@@ -1614,11 +1622,11 @@ button:active:not(:disabled){transform:translateY(1px)}
nu flotte sans ancrage, et la frontière entre deux réponses se perd. */ nu flotte sans ancrage, et la frontière entre deux réponses se perd. */
#chat{padding-top:22px} #chat{padding-top:22px}
#chat .msg.assistant{background:var(--bubble);border:0; #chat .msg.assistant{background:var(--bubble);border:0;
border-radius:10px;padding:15px 17px;max-width:100%;box-shadow:var(--shadow-card)} border-radius:4px;padding:15px 17px;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)} #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 /* Question : aplat de sauge, coin bas-droit rentré. --on-accent garantit le
contraste dans les DEUX variantes. */ contraste dans les DEUX variantes. */
#chat .msg.user{background:var(--user-bg);color:var(--on-user);border-radius:10px; #chat .msg.user{background:var(--user-bg);color:var(--on-user);border-radius:4px;
padding:13px 16px;max-width:62%;box-shadow:var(--shadow-card)} padding:13px 16px;max-width:62%;box-shadow:var(--shadow-card)}
#chat .msg.user .label{color:var(--on-user);opacity:.75} #chat .msg.user .label{color:var(--on-user);opacity:.75}
#chat .msg.user .body a{color:var(--on-user);text-decoration:underline} #chat .msg.user .body a{color:var(--on-user);text-decoration:underline}
@@ -1644,7 +1652,7 @@ button:active:not(:disabled){transform:translateY(1px)}
#inputbar #attach,#inputbar #files-btn,#send,#stop{ #inputbar #attach,#inputbar #files-btn,#send,#stop{
width:34px;height:34px;min-height:34px;flex:none;margin:0;padding:0; width:34px;height:34px;min-height:34px;flex:none;margin:0;padding:0;
display:inline-flex;align-items:center;justify-content:center;align-self:center; display:inline-flex;align-items:center;justify-content:center;align-self:center;
border:0;border-radius:10px;background:transparent;color:var(--dim); border:0;border-radius:4px;background:transparent;color:var(--dim);
transition:background .15s ease,color .15s ease,transform .08s ease} transition:background .15s ease,color .15s ease,transform .08s ease}
/* Envoyer / arrêter : un seul des deux à la fois, décidé par data-busy (posé par /* Envoyer / arrêter : un seul des deux à la fois, décidé par data-busy (posé par
syncSendBtn). C'est la feuille qui garde la main sur `display` — un style syncSendBtn). C'est la feuille qui garde la main sur `display` — un style
@@ -1665,8 +1673,8 @@ html[data-files="1"] #files-btn{color:var(--accent)}
à droite du texte : c'est là qu'on les cherche, et le pied de carte redevient à 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. */ 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); #inputbar{align-items:flex-end;padding:8px 8px 8px 18px;gap:4px;box-shadow:var(--shadow-card);
border-radius:14px 14px 0 0} border-radius:3px 3px 0 0}
#ctxmeta{border-radius:0 0 14px 14px} #ctxmeta{border-radius:0 0 5px 5px}
#ctxmeta{padding-bottom:12px} #ctxmeta{padding-bottom:12px}
#ctxtrack{border-radius:999px} #ctxtrack{border-radius:999px}
@@ -1687,8 +1695,8 @@ html[data-files="1"] #files-btn{color:var(--accent)}
#side-scroll>details,#side-scroll>.sidegroup{background:transparent;box-shadow:none; #side-scroll>details,#side-scroll>.sidegroup{background:transparent;box-shadow:none;
border-radius:0;padding:0;margin:0} border-radius:0;padding:0;margin:0}
#side-scroll>details>summary{height:34px} #side-scroll>details>summary{height:34px}
.side .slist{background:transparent;border-color:transparent;border-radius:12px} .side .slist{background:transparent;border-color:transparent;border-radius:3px}
.side #side-settings .slist{background:var(--row-bg);border-radius:12px} .side #side-settings .slist{background:var(--row-bg);border-radius:3px}
/* Le fil n'est plus une carte : il EST la page. */ /* Le fil n'est plus une carte : il EST la page. */
.main{background:transparent;border-radius:0;box-shadow:none;overflow:visible} .main{background:transparent;border-radius:0;box-shadow:none;overflow:visible}
#composer{background:linear-gradient(to bottom,transparent 0,var(--bg) var(--fade))} #composer{background:linear-gradient(to bottom,transparent 0,var(--bg) var(--fade))}
@@ -1701,7 +1709,7 @@ html[data-files="1"] #files-btn{color:var(--accent)}
au tiers de la place disponible. */ au tiers de la place disponible. */
.side .preset{position:relative} .side .preset{position:relative}
.side .preset .conv-acts{position:absolute;right:6px;top:50%;transform:translateY(-50%); .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} margin:0;background:var(--row-bg-on);border-radius:4px;padding:1px}
.side .preset.active .conv-acts{background:var(--row-bg)} .side .preset.active .conv-acts{background:var(--row-bg)}
.side .preset .preset-info{overflow:hidden} .side .preset .preset-info{overflow:hidden}
/* La sélection est un aplat, pas un cadre : un liseré autour d'une ligne de /* La sélection est un aplat, pas un cadre : un liseré autour d'une ligne de
@@ -1723,10 +1731,10 @@ html[data-files="1"] #files-btn{color:var(--accent)}
/* Le ☰ suit la charte des boutons : pas de contour, un fond qui apparaît au /* 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 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. */ pour ne pas laisser voir le fil défiler dessous. */
#menubtn{background:var(--bg);border:0;color:var(--dim);border-radius:10px; #menubtn{background:var(--bg);border:0;color:var(--dim);border-radius:4px;
width:34px;height:34px;font-size:16px;top:calc(11px + env(safe-area-inset-top));left:9px} 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)} #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))} #lc-chip{border-radius:4px;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 /* Jauges : la piste doit se VOIR, sinon une barre presque vide ressemble à un
trait souligné sous l'intitulé. Valeurs relevées sur la maquette. */ trait souligné sous l'intitulé. Valeurs relevées sur la maquette. */
.perf-track{background:var(--track)} .perf-track{background:var(--track)}
@@ -1743,7 +1751,7 @@ html[data-files="1"] #files-btn{color:var(--accent)}
#chat .msg.assistant .body pre,#chat .msg.reasoning .body pre,#chat .msg.tool pre{ #chat .msg.assistant .body pre,#chat .msg.reasoning .body pre,#chat .msg.tool pre{
background:var(--code-bg);border:1px solid color-mix(in srgb,var(--text) 10%,transparent)} background:var(--code-bg);border:1px solid color-mix(in srgb,var(--text) 10%,transparent)}
#chat .msg.assistant .body code{background:var(--code-bg); #chat .msg.assistant .body code{background:var(--code-bg);
border:1px solid color-mix(in srgb,var(--text) 7%,transparent);border-radius:5px} border:1px solid color-mix(in srgb,var(--text) 7%,transparent);border-radius:3px}
#chat .msg.assistant .body pre code{border:0;background:transparent} #chat .msg.assistant .body pre code{border:0;background:transparent}
/* --- Titres de la barre latérale ------------------------------------ /* --- Titres de la barre latérale ------------------------------------
La maquette n'a qu'un style de titre : casse normale, demi-gras, taille du La maquette n'a qu'un style de titre : casse normale, demi-gras, taille du
@@ -1829,7 +1837,7 @@ html[data-files="1"] #files-btn{color:var(--accent)}
#chat-actions-clear,.row>button>svg{vertical-align:-2px} #chat-actions-clear,.row>button>svg{vertical-align:-2px}
.row>button:has(svg){display:inline-flex;align-items:center;gap:7px} .row>button:has(svg){display:inline-flex;align-items:center;gap:7px}
/* Le « + » des sections : même carré que les autres commandes de la barre. */ /* Le « + » des sections : même carré que les autres commandes de la barre. */
.preset-add{width:26px;height:26px;border-radius:8px} .preset-add{width:26px;height:26px;border-radius:4px}
/* Étiquettes de groupe : même traitement que les titres de section — la /* Étiquettes de groupe : même traitement que les titres de section — la
maquette n'a qu'un style de titre, et le monospace en capitales de la charte maquette n'a qu'un style de titre, et le monospace en capitales de la charte
précédente criait au milieu des formulaires. */ précédente criait au milieu des formulaires. */
@@ -1861,7 +1869,7 @@ html[data-files="1"] #files-btn{color:var(--accent)}
/* La liste EST la carte, au MÊME gabarit que .sidegroup (les sections de /* La liste EST la carte, au MÊME gabarit que .sidegroup (les sections de
réglages) : fond de panneau, rayon de 20, ombre discrète. Les lignes sont réglages) : fond de panneau, rayon de 20, ombre discrète. Les lignes sont
rognées par overflow au lieu d'arrondir la première et la dernière à la main. */ rognées par overflow au lieu d'arrondir la première et la dernière à la main. */
#conv-list{background:var(--panel);border-radius:20px;box-shadow:var(--shadow); #conv-list{background:var(--panel);border-radius:3px;box-shadow:var(--shadow);
overflow:hidden;margin:0} overflow:hidden;margin:0}
.side .preset{border-radius:0;margin:0;padding:10px 12px} .side .preset{border-radius:0;margin:0;padding:10px 12px}
.side .preset:not(:last-child){border-bottom:1px solid var(--border)} .side .preset:not(:last-child){border-bottom:1px solid var(--border)}
@@ -1877,7 +1885,7 @@ html[data-files="1"] #files-btn{color:var(--accent)}
couple que « Paramètres » + sa liste dans les réglages. */ couple que « Paramètres » + sa liste dans les réglages. */
#side-perf{padding:12px 12px 12px;padding-bottom:calc(12px + env(safe-area-inset-bottom))} #side-perf{padding:12px 12px 12px;padding-bottom:calc(12px + env(safe-area-inset-bottom))}
.perf-head{margin:0 2px 7px;font-size:12.5px;font-weight:600;color:var(--dim)} .perf-head{margin:0 2px 7px;font-size:12.5px;font-weight:600;color:var(--dim)}
.perf-body{background:var(--panel);border-radius:20px;box-shadow:var(--shadow); .perf-body{background:var(--panel);border-radius:3px;box-shadow:var(--shadow);
padding:14px 15px 3px} padding:14px 15px 3px}
/* ⚠️ Pas de remise à zéro sur :last-child — les jauges vivent dans DEUX /* ⚠️ Pas de remise à zéro sur :last-child — les jauges vivent dans DEUX
conteneurs (les cartes graphiques, puis la mémoire vive). La dernière jauge conteneurs (les cartes graphiques, puis la mémoire vive). La dernière jauge
+43
View File
@@ -703,6 +703,49 @@ func handleMCP(w http.ResponseWriter, r *http.Request) {
sendJSON(w, 200, map[string]any{"ok": true, "servers": list}) sendJSON(w, 200, map[string]any{"ok": true, "servers": list})
} }
// handleReasoningEffort lit (GET) ou règle (POST) l'intensité du raisonnement.
//
// Même rôle que le sélecteur de l'éditeur de preset, mais applicable depuis la
// barre de saisie sans rouvrir les réglages : la valeur part dans la config
// vivante, elle est relue à chaque requête au moteur (llm_client.go). Aucun
// redémarrage du moteur — `reasoning_effort` voyage dans le corps de la requête.
//
// Attention : appliquer un preset réécrit TOUTE la config, donc la valeur du
// preset reprend la main à ce moment-là. C'est voulu — le réglage reste attaché
// au modèle, la barre de saisie ne fait que le changer à la volée.
func handleReasoningEffort(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
var req struct {
Value string `json:"value"`
}
_ = json.NewDecoder(r.Body).Decode(&req)
v := strings.ToLower(strings.TrimSpace(req.Value))
// Liste blanche : cette clé finit dans une requête au moteur, on n'y
// laisse pas passer une chaîne arbitraire venue du navigateur.
ok := v == ""
for _, allowed := range ReasoningEfforts {
if v == allowed {
ok = true
}
}
if !ok {
sendJSON(w, 400, map[string]any{"ok": false, "error": "valeur inconnue : " + v})
return
}
if err := SetConfigKey("REASONING_EFFORT", v); err != nil {
sendJSON(w, 500, map[string]any{"ok": false, "error": err.Error()})
return
}
}
cfg := ReadConfig()
sendJSON(w, 200, map[string]any{
"ok": true,
"value": reasoningEffortValue(cfg["REASONING_EFFORT"]),
"on": reasoningActive(cfg["REASONING"]),
"choices": ReasoningEfforts,
})
}
// handleMCPCatalog renvoie le catalogue embarqué de serveurs MCP connus. // handleMCPCatalog renvoie le catalogue embarqué de serveurs MCP connus.
// Lecture seule : rien n'est installé ici, l'UI ne fait que préremplir la modale // Lecture seule : rien n'est installé ici, l'UI ne fait que préremplir la modale
// d'ajout avec la commande de l'entrée choisie. // d'ajout avec la commande de l'entrée choisie.
+2 -1
View File
@@ -225,7 +225,8 @@ func newWebMux() *http.ServeMux {
api("/api/mcp/tool", handleMCPTool) api("/api/mcp/tool", handleMCPTool)
api("/api/mcp/test", handleMCPTest) api("/api/mcp/test", handleMCPTest)
api("/api/memory", handleMemoryMode) api("/api/memory", handleMemoryMode)
api("/api/network", handleNetwork) // écoute LAN du moteur + pare-feu (Windows) api("/api/reasoning-effort", handleReasoningEffort) // intensité réglable depuis la barre de saisie
api("/api/network", handleNetwork) // écoute LAN du moteur + pare-feu (Windows)
api("/api/prefs", handleWebPrefs) api("/api/prefs", handleWebPrefs)
api("/api/sysprompt", handleSysPrompt) api("/api/sysprompt", handleSysPrompt)
// Alias rétro-compat : l'ancien portail ajean.link (dépôt loki-relay) pilote // Alias rétro-compat : l'ancien portail ajean.link (dépôt loki-relay) pilote