Direction « Terre » : nouvelle identité visuelle

Reprise de la maquette fournie (LOKI — direction Terre) : bruns chauds, vert
sauge, argile, et des surfaces en CARTES arrondies séparées par une gouttière
au lieu d'une colonne à filets.

- Palette transposée sur les jetons EXISTANTS (--bg, --panel, --accent…) et
  non par réécriture des 1200 lignes : les 300 usages de variables suivent
  d'eux-mêmes. Rôles clarifiés — sauge = sélection et action, argile =
  chiffres et avertissements, terre cuite = arrêt et danger.
- Polices Bricolage Grotesque + IBM Plex Mono EMBARQUÉES dans le binaire et
  servies par Loki (/fonts/), pas par Google Fonts : une instance locale ou
  coupée d'internet doit s'afficher correctement, sans qu'un tiers apprenne
  qui consulte l'interface. Sous-ensemble latin, 106 Ko au total.
- Structure en cartes : barre latérale en pile de cartes, colonne de chat en
  panneau unique, carte de saisie posée un ton au-dessus. Liseré sauge sur la
  discussion active, bulle de question au coin bas-droit rentré.
- Bouton d'envoi en sauge : il reprenait --text, donc un brun indistinct du
  reste ; l'arrêt reste en terre cuite.

Bornage au bureau (min-width:721px) : sur téléphone la barre latérale est un
tiroir plein écran et le fil occupe toute la largeur — des cartes n'y
grignoteraient que des pixels utiles.

Vérifié au navigateur, thèmes clair ET sombre, plus un rendu 390 px :
polices réellement chargées (200 sur les trois woff2), aucun débordement
horizontal, tiroir mobile inchangé.
This commit is contained in:
Loki committed 2026-08-15 21:50:30 +00:00
1 parent 46c5a1f130
commit e7a3bcca1f
7 files changed
+237 -13

No files matched your search

Binary file not shown.
Binary file not shown.
Binary file not shown.
+107 -6
View File
@@ -24,12 +24,45 @@
<!-- Applique le thème AVANT le rendu (pas de flash) : lit le choix persisté. --> <!-- Applique le thème AVANT le rendu (pas de flash) : lit le choix persisté. -->
<script>try{var t=localStorage.getItem('loki-theme')||'light';document.documentElement.setAttribute('data-theme',(t==='dark'||t.endsWith('-dark'))?'dark':'light');['hide-reasoning','hide-tools','fold-tools','hide-side','hide-stats'].forEach(function(k){document.documentElement.setAttribute('data-'+k,localStorage.getItem('loki-'+k)==='1'?'1':'0')})}catch(e){document.documentElement.setAttribute('data-theme','light');}</script> <script>try{var t=localStorage.getItem('loki-theme')||'light';document.documentElement.setAttribute('data-theme',(t==='dark'||t.endsWith('-dark'))?'dark':'light');['hide-reasoning','hide-tools','fold-tools','hide-side','hide-stats'].forEach(function(k){document.documentElement.setAttribute('data-'+k,localStorage.getItem('loki-'+k)==='1'?'1':'0')})}catch(e){document.documentElement.setAttribute('data-theme','light');}</script>
<style> <style>
/* ===== Palette ===================================================== /* ===== Polices =====================================================
Un seul thème (« Clean ») : noir et blanc, sobre. Deux variantes — claire par Bricolage Grotesque (titres et texte) + IBM Plex Mono (chiffres, chemins,
défaut, sombre via data-theme="dark" sur <html>. Toutes les couleurs de l'UI code). Servies par Loki (/fonts/, embarquées dans le binaire) et NON par
passent par ces variables ; rien d'autre ne code une couleur en dur. */ Google Fonts : une instance locale ou coupée d'internet doit s'afficher
:root{color-scheme:light;--font:-apple-system,BlinkMacSystemFont,'Segoe UI',Inter,Helvetica,Arial,sans-serif;--mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;--bubble:#f0f0f0;--bg:#ffffff;--panel:#fafafa;--border:#e0e0e2;--text:#0a0a0a;--dim:#6b6b70;--accent:#0a0a0a;--accent-bg:rgba(0,0,0,.05);--row-bg:rgba(0,0,0,.03);--row-bg-on:rgba(0,0,0,.042);--ok:#0a0a0a;--warn:#6b6b6b;--err:#c0392b;--mag:#6b6b6b;--code-bg:#f5f5f5;--danger-bg:#fbeae8;--warn-bg:#f2f2f2;--add:#1a7f37;--add-bg:rgba(26,127,55,.10);--del:#cf222e;--del-bg:rgba(207,34,46,.10)} correctement, sans qu'un tiers apprenne qui consulte l'interface.
[data-theme="dark"]{color-scheme:dark;--bubble:#232326;--bg:#0b0b0c;--panel:#161618;--border:#33333a;--text:#f2f2f4;--dim:#a0a0a8;--accent:#f5f5f5;--accent-bg:rgba(255,255,255,.07);--row-bg:rgba(255,255,255,.04);--row-bg-on:rgba(255,255,255,.055);--ok:#f5f5f5;--warn:#8e8e8e;--err:#f2777a;--mag:#8e8e8e;--code-bg:#000000;--danger-bg:#2e1a19;--warn-bg:#1c1c1c;--add:#4ac26b;--add-bg:rgba(74,194,107,.13);--del:#f85149;--del-bg:rgba(248,81,73,.13)} swap : le texte s'affiche tout de suite dans la police de repli, puis bascule
— jamais de blanc au chargement. */
@font-face{font-family:'Bricolage Grotesque';src:url('/fonts/bricolage.woff2') format('woff2');font-weight:400 700;font-display:swap;font-style:normal}
@font-face{font-family:'IBM Plex Mono';src:url('/fonts/plexmono-400.woff2') format('woff2');font-weight:400;font-display:swap;font-style:normal}
@font-face{font-family:'IBM Plex Mono';src:url('/fonts/plexmono-500.woff2') format('woff2');font-weight:500;font-display:swap;font-style:normal}
/* ===== Palette « Terre » ===========================================
Bruns chauds, vert sauge et argile. Deux variantes — claire par défaut,
sombre via data-theme="dark" sur <html>. Toutes les couleurs de l'UI passent
par ces variables ; rien d'autre ne code une couleur en dur.
Rôles : --accent = sauge (sélection, marche, liens) · --warn = argile
(chiffres, mesures, avertissements) · --err = terre cuite (arrêt, danger).
--row-bg / --code-bg sont les deux fonds encastrés d'un panneau. */
:root{color-scheme:light;
--font:'Bricolage Grotesque',-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;
--mono:'IBM Plex Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
--bg:#f3efe8;--panel:#ffffff;--bubble:#efe9df;--border:rgba(44,38,34,.12);
--text:#2c2622;--dim:#7a7069;
--accent:#5f7f52;--accent-bg:rgba(95,127,82,.12);
--row-bg:#f7f4ee;--row-bg-on:#f4f1ea;
--ok:#5f7f52;--warn:#a9662f;--err:#c05a3f;--mag:#a9662f;
--code-bg:#f7f4ee;--danger-bg:#f8e9e4;--warn-bg:#f6efe4;
--add:#5f7f52;--add-bg:rgba(95,127,82,.12);--del:#c05a3f;--del-bg:rgba(192,90,63,.12);
--shadow:0 1px 2px rgba(44,38,34,.06)}
[data-theme="dark"]{color-scheme:dark;
--bg:#211d1a;--panel:#2b2622;--bubble:#38312a;--border:rgba(236,229,221,.12);
--text:#ece5dd;--dim:#a99d92;
--accent:#9ab68c;--accent-bg:rgba(154,182,140,.15);
--row-bg:#332d28;--row-bg-on:#38312a;
--ok:#9ab68c;--warn:#d2a07a;--err:#c9705c;--mag:#d2a07a;
--code-bg:#211d1a;--danger-bg:#3a2621;--warn-bg:#332b23;
--add:#9ab68c;--add-bg:rgba(154,182,140,.15);--del:#c9705c;--del-bg:rgba(201,112,92,.15);
--shadow:none}
/* Safari iOS peint un rectangle gris/noir sur TOUT élément touché (bouton, lien, /* Safari iOS peint un rectangle gris/noir sur TOUT élément touché (bouton, lien,
résumé de section, bulle repliable…). C'est un retour tactile par défaut, hors résumé de section, bulle repliable…). C'est un retour tactile par défaut, hors
de notre charte et impossible à styler : on le coupe. Nos états :active/:focus de notre charte et impossible à styler : on le coupe. Nos états :active/:focus
@@ -1233,6 +1266,70 @@ button:hover{border-color:var(--dim);color:var(--text)}
.node-target-opt.on{border-color:var(--accent);color:var(--accent)} .node-target-opt.on{border-color:var(--accent);color:var(--accent)}
.node-target-opt.off{opacity:.6} .node-target-opt.off{opacity:.6}
.node-target-opt input{margin:0} .node-target-opt input{margin:0}
/* ===== Direction « Terre » : surfaces en cartes =======================
La charte pose des CARTES arrondies séparées par une gouttière, au lieu
d'une colonne à filets. Bloc en fin de feuille pour prendre le pas sur les
règles de structure d'origine sans les réécrire.
Bureau UNIQUEMENT : sur téléphone la barre latérale est un tiroir plein
écran et le fil occupe toute la largeur — y poser des cartes ne ferait que
grignoter des pixels précieux. */
@media (min-width:721px){
/* Coque : le fond « terre » se voit dans la gouttière entre les deux cartes. */
body{padding:12px;gap:12px}
html[data-pwa="1"] body{padding-top:calc(12px + env(safe-area-inset-top))}
/* Barre latérale : pile de cartes, sans filet ni bordure de colonne. */
.side{width:312px;border-right:0;padding:0;background:transparent;
display:flex;flex-direction:column;gap:12px;overflow-y:auto;overscroll-behavior:contain}
.sidecard,.side>details{background:var(--panel);border:0;border-top:0;border-radius:20px;
box-shadow:var(--shadow);margin:0;padding:2px 16px}
.sidecard{padding:15px 18px 14px;display:flex;flex-direction:column;gap:2px}
.side>details[open]{padding-bottom:14px}
.side>details>summary{height:42px}
/* L'étiquette « Paramètres » n'est plus un filet mais un intertitre entre
deux cartes : le fond de la coque fait déjà la séparation. */
.side-sep{border-top:0;margin:10px 0 0;padding:0 6px}
/* Blocs encastrés DANS une carte : un ton plus chaud, sans bordure — deux
cadres imbriqués alourdiraient inutilement. */
.side .slist{background:var(--row-bg);border-color:transparent;border-radius:14px}
/* Colonne principale : une seule grande carte qui contient fil et saisie. */
.main{background:var(--panel);border-radius:20px;box-shadow:var(--shadow);overflow:hidden}
#chat{padding:26px max(20px,calc(50% - 360px)) 8px}
/* Le dégradé qui fait disparaître le fil sous la carte de saisie doit fondre
vers le PANNEAU, pas vers le fond de la coque. */
#composer{background:linear-gradient(to bottom,transparent 0,var(--panel) 34%)}
/* Carte de saisie : posée SUR le panneau, donc un ton au-dessus, et sans
bordure — c'est l'élévation qui la détache. */
#inputbar,#ctxmeta{background:var(--row-bg-on);border-color:transparent}
#input{background:transparent}
}
/* --- Terre : densité et accents ------------------------------------- */
@media (min-width:721px){
/* Gouttière resserrée : la charte empile beaucoup de cartes, 12 px entre
chacune repoussait « Actions » loin sous la ligne de flottaison. */
.side{gap:8px}
.side>details>summary{height:38px}
/* Discussion active : un LISERÉ sauge à gauche, pas un cadre complet — c'est
la marque de sélection de la charte, et elle se lit sans alourdir. */
.side .preset{border:0;border-left:3px solid transparent;border-radius:14px;background:transparent}
.side .preset.active{background:var(--row-bg-on);border-left-color:var(--accent)}
.side .preset:hover{background:var(--row-bg)}
/* Bulle de question : coin bas-droit rentré, comme une bulle qui pointe vers
son auteur. */
#chat .msg.user{border-radius:20px 20px 6px 20px}
}
/* Action principale en sauge : c'est la couleur d'action de la charte, et elle
se distingue enfin du texte (le bouton reprenait --text, donc un brun sombre
indistinct du reste). L'arrêt, lui, reste en terre cuite. */
#send{background:var(--accent);border-color:var(--accent);color:#fff}
#send:hover:not(:disabled){background:var(--accent);border-color:var(--accent);color:#fff;opacity:.88}
#stop{background:var(--err);border-color:var(--err);color:#fff}
#stop:hover{background:var(--err);border-color:var(--err);color:#fff;opacity:.88}
</style></head><body> </style></head><body>
<button id="menubtn" onclick="toggleSide()">☰</button> <button id="menubtn" onclick="toggleSide()">☰</button>
<!-- Installation d'un moteur en cours : le détail vit dans le panneau, qui est <!-- Installation d'un moteur en cours : le détail vit dans le panneau, qui est
@@ -1241,11 +1338,15 @@ button:hover{border-color:var(--dim);color:var(--text)}
<button id="lc-chip" onclick="lcChipOpen()" hidden></button> <button id="lc-chip" onclick="lcChipOpen()" hidden></button>
<div id="backdrop" onclick="toggleSide()"></div> <div id="backdrop" onclick="toggleSide()"></div>
<div class="side" id="side"> <div class="side" id="side">
<!-- sidecard : regroupe la marque et la mention du fork en UNE carte (direction
Terre). Sans ce conteneur, les deux blocs flottaient séparément. -->
<div class="sidecard">
<div class="sidehead"> <div class="sidehead">
<h1 id="brand" aria-label="loki" title="Loki — fork d'AJEAN (github.com/nathaninline/ajean, MIT)"><span id="brand-a"><svg viewBox="0 0 4 5" shape-rendering="crispEdges" fill="currentColor"><rect x="0" y="0" width="1" height="5"/><rect x="1" y="4" width="3" height="1"/></svg></span><span id="brand-word"><svg viewBox="0 0 11 5" shape-rendering="crispEdges" fill="currentColor"><rect x="0" y="0" width="3" height="1"/><rect x="0" y="1" width="1" height="3"/><rect x="2" y="1" width="1" height="3"/><rect x="0" y="4" width="3" height="1"/><rect x="4" y="0" width="1" height="5"/><rect x="6" y="0" width="1" height="2"/><rect x="5" y="2" width="1" height="1"/><rect x="6" y="3" width="1" height="2"/><rect x="8" y="0" width="3" height="1"/><rect x="9" y="1" width="1" height="3"/><rect x="8" y="4" width="3" height="1"/></svg></span><span id="ver"></span></h1> <h1 id="brand" aria-label="loki" title="Loki — fork d'AJEAN (github.com/nathaninline/ajean, MIT)"><span id="brand-a"><svg viewBox="0 0 4 5" shape-rendering="crispEdges" fill="currentColor"><rect x="0" y="0" width="1" height="5"/><rect x="1" y="4" width="3" height="1"/></svg></span><span id="brand-word"><svg viewBox="0 0 11 5" shape-rendering="crispEdges" fill="currentColor"><rect x="0" y="0" width="3" height="1"/><rect x="0" y="1" width="1" height="3"/><rect x="2" y="1" width="1" height="3"/><rect x="0" y="4" width="3" height="1"/><rect x="4" y="0" width="1" height="5"/><rect x="6" y="0" width="1" height="2"/><rect x="5" y="2" width="1" height="1"/><rect x="6" y="3" width="1" height="2"/><rect x="8" y="0" width="3" height="1"/><rect x="9" y="1" width="1" height="3"/><rect x="8" y="4" width="3" height="1"/></svg></span><span id="ver"></span></h1>
<span id="status-svc" class="statuspill" onclick="toggleSvcLog()" title="voir le journal du moteur">…</span> <span id="status-svc" class="statuspill" onclick="toggleSvcLog()" title="voir le journal du moteur">…</span>
</div> </div>
<div class="brand-fork">fork de <a href="https://github.com/nathaninline/ajean" target="_blank" rel="noopener noreferrer" title="AJEAN par nathaninline — licence MIT">AJEAN</a></div> <div class="brand-fork">fork de <a href="https://github.com/nathaninline/ajean" target="_blank" rel="noopener noreferrer" title="AJEAN par nathaninline — licence MIT">AJEAN</a></div>
</div><!-- /sidecard -->
<!-- Discussions : plusieurs fils, la liste la plus récente en tête. Le fil <!-- Discussions : plusieurs fils, la liste la plus récente en tête. Le fil
actif est partagé par tous les appareils (comme la conversation unique actif est partagé par tous les appareils (comme la conversation unique
d'origine) : basculer ici bascule partout. --> d'origine) : basculer ici bascule partout. -->
+4
View File
@@ -33,11 +33,15 @@
<button id="lc-chip" onclick="lcChipOpen()" hidden></button> <button id="lc-chip" onclick="lcChipOpen()" hidden></button>
<div id="backdrop" onclick="toggleSide()"></div> <div id="backdrop" onclick="toggleSide()"></div>
<div class="side" id="side"> <div class="side" id="side">
<!-- sidecard : regroupe la marque et la mention du fork en UNE carte (direction
Terre). Sans ce conteneur, les deux blocs flottaient séparément. -->
<div class="sidecard">
<div class="sidehead"> <div class="sidehead">
<h1 id="brand" aria-label="loki" title="Loki — fork d'AJEAN (github.com/nathaninline/ajean, MIT)"><span id="brand-a"><svg viewBox="0 0 4 5" shape-rendering="crispEdges" fill="currentColor"><rect x="0" y="0" width="1" height="5"/><rect x="1" y="4" width="3" height="1"/></svg></span><span id="brand-word"><svg viewBox="0 0 11 5" shape-rendering="crispEdges" fill="currentColor"><rect x="0" y="0" width="3" height="1"/><rect x="0" y="1" width="1" height="3"/><rect x="2" y="1" width="1" height="3"/><rect x="0" y="4" width="3" height="1"/><rect x="4" y="0" width="1" height="5"/><rect x="6" y="0" width="1" height="2"/><rect x="5" y="2" width="1" height="1"/><rect x="6" y="3" width="1" height="2"/><rect x="8" y="0" width="3" height="1"/><rect x="9" y="1" width="1" height="3"/><rect x="8" y="4" width="3" height="1"/></svg></span><span id="ver"></span></h1> <h1 id="brand" aria-label="loki" title="Loki — fork d'AJEAN (github.com/nathaninline/ajean, MIT)"><span id="brand-a"><svg viewBox="0 0 4 5" shape-rendering="crispEdges" fill="currentColor"><rect x="0" y="0" width="1" height="5"/><rect x="1" y="4" width="3" height="1"/></svg></span><span id="brand-word"><svg viewBox="0 0 11 5" shape-rendering="crispEdges" fill="currentColor"><rect x="0" y="0" width="3" height="1"/><rect x="0" y="1" width="1" height="3"/><rect x="2" y="1" width="1" height="3"/><rect x="0" y="4" width="3" height="1"/><rect x="4" y="0" width="1" height="5"/><rect x="6" y="0" width="1" height="2"/><rect x="5" y="2" width="1" height="1"/><rect x="6" y="3" width="1" height="2"/><rect x="8" y="0" width="3" height="1"/><rect x="9" y="1" width="1" height="3"/><rect x="8" y="4" width="3" height="1"/></svg></span><span id="ver"></span></h1>
<span id="status-svc" class="statuspill" onclick="toggleSvcLog()" title="voir le journal du moteur">…</span> <span id="status-svc" class="statuspill" onclick="toggleSvcLog()" title="voir le journal du moteur">…</span>
</div> </div>
<div class="brand-fork">fork de <a href="https://github.com/nathaninline/ajean" target="_blank" rel="noopener noreferrer" title="AJEAN par nathaninline — licence MIT">AJEAN</a></div> <div class="brand-fork">fork de <a href="https://github.com/nathaninline/ajean" target="_blank" rel="noopener noreferrer" title="AJEAN par nathaninline — licence MIT">AJEAN</a></div>
</div><!-- /sidecard -->
<!-- Discussions : plusieurs fils, la liste la plus récente en tête. Le fil <!-- Discussions : plusieurs fils, la liste la plus récente en tête. Le fil
actif est partagé par tous les appareils (comme la conversation unique actif est partagé par tous les appareils (comme la conversation unique
d'origine) : basculer ici bascule partout. --> d'origine) : basculer ici bascule partout. -->
+103 -6
View File
@@ -1,9 +1,42 @@
/* ===== Palette ===================================================== /* ===== Polices =====================================================
Un seul thème (« Clean ») : noir et blanc, sobre. Deux variantes — claire par Bricolage Grotesque (titres et texte) + IBM Plex Mono (chiffres, chemins,
défaut, sombre via data-theme="dark" sur <html>. Toutes les couleurs de l'UI code). Servies par Loki (/fonts/, embarquées dans le binaire) et NON par
passent par ces variables ; rien d'autre ne code une couleur en dur. */ Google Fonts : une instance locale ou coupée d'internet doit s'afficher
:root{color-scheme:light;--font:-apple-system,BlinkMacSystemFont,'Segoe UI',Inter,Helvetica,Arial,sans-serif;--mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;--bubble:#f0f0f0;--bg:#ffffff;--panel:#fafafa;--border:#e0e0e2;--text:#0a0a0a;--dim:#6b6b70;--accent:#0a0a0a;--accent-bg:rgba(0,0,0,.05);--row-bg:rgba(0,0,0,.03);--row-bg-on:rgba(0,0,0,.042);--ok:#0a0a0a;--warn:#6b6b6b;--err:#c0392b;--mag:#6b6b6b;--code-bg:#f5f5f5;--danger-bg:#fbeae8;--warn-bg:#f2f2f2;--add:#1a7f37;--add-bg:rgba(26,127,55,.10);--del:#cf222e;--del-bg:rgba(207,34,46,.10)} correctement, sans qu'un tiers apprenne qui consulte l'interface.
[data-theme="dark"]{color-scheme:dark;--bubble:#232326;--bg:#0b0b0c;--panel:#161618;--border:#33333a;--text:#f2f2f4;--dim:#a0a0a8;--accent:#f5f5f5;--accent-bg:rgba(255,255,255,.07);--row-bg:rgba(255,255,255,.04);--row-bg-on:rgba(255,255,255,.055);--ok:#f5f5f5;--warn:#8e8e8e;--err:#f2777a;--mag:#8e8e8e;--code-bg:#000000;--danger-bg:#2e1a19;--warn-bg:#1c1c1c;--add:#4ac26b;--add-bg:rgba(74,194,107,.13);--del:#f85149;--del-bg:rgba(248,81,73,.13)} swap : le texte s'affiche tout de suite dans la police de repli, puis bascule
— jamais de blanc au chargement. */
@font-face{font-family:'Bricolage Grotesque';src:url('/fonts/bricolage.woff2') format('woff2');font-weight:400 700;font-display:swap;font-style:normal}
@font-face{font-family:'IBM Plex Mono';src:url('/fonts/plexmono-400.woff2') format('woff2');font-weight:400;font-display:swap;font-style:normal}
@font-face{font-family:'IBM Plex Mono';src:url('/fonts/plexmono-500.woff2') format('woff2');font-weight:500;font-display:swap;font-style:normal}
/* ===== Palette « Terre » ===========================================
Bruns chauds, vert sauge et argile. Deux variantes — claire par défaut,
sombre via data-theme="dark" sur <html>. Toutes les couleurs de l'UI passent
par ces variables ; rien d'autre ne code une couleur en dur.
Rôles : --accent = sauge (sélection, marche, liens) · --warn = argile
(chiffres, mesures, avertissements) · --err = terre cuite (arrêt, danger).
--row-bg / --code-bg sont les deux fonds encastrés d'un panneau. */
:root{color-scheme:light;
--font:'Bricolage Grotesque',-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;
--mono:'IBM Plex Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
--bg:#f3efe8;--panel:#ffffff;--bubble:#efe9df;--border:rgba(44,38,34,.12);
--text:#2c2622;--dim:#7a7069;
--accent:#5f7f52;--accent-bg:rgba(95,127,82,.12);
--row-bg:#f7f4ee;--row-bg-on:#f4f1ea;
--ok:#5f7f52;--warn:#a9662f;--err:#c05a3f;--mag:#a9662f;
--code-bg:#f7f4ee;--danger-bg:#f8e9e4;--warn-bg:#f6efe4;
--add:#5f7f52;--add-bg:rgba(95,127,82,.12);--del:#c05a3f;--del-bg:rgba(192,90,63,.12);
--shadow:0 1px 2px rgba(44,38,34,.06)}
[data-theme="dark"]{color-scheme:dark;
--bg:#211d1a;--panel:#2b2622;--bubble:#38312a;--border:rgba(236,229,221,.12);
--text:#ece5dd;--dim:#a99d92;
--accent:#9ab68c;--accent-bg:rgba(154,182,140,.15);
--row-bg:#332d28;--row-bg-on:#38312a;
--ok:#9ab68c;--warn:#d2a07a;--err:#c9705c;--mag:#d2a07a;
--code-bg:#211d1a;--danger-bg:#3a2621;--warn-bg:#332b23;
--add:#9ab68c;--add-bg:rgba(154,182,140,.15);--del:#c9705c;--del-bg:rgba(201,112,92,.15);
--shadow:none}
/* Safari iOS peint un rectangle gris/noir sur TOUT élément touché (bouton, lien, /* Safari iOS peint un rectangle gris/noir sur TOUT élément touché (bouton, lien,
résumé de section, bulle repliable…). C'est un retour tactile par défaut, hors résumé de section, bulle repliable…). C'est un retour tactile par défaut, hors
de notre charte et impossible à styler : on le coupe. Nos états :active/:focus de notre charte et impossible à styler : on le coupe. Nos états :active/:focus
@@ -1207,3 +1240,67 @@ button:hover{border-color:var(--dim);color:var(--text)}
.node-target-opt.on{border-color:var(--accent);color:var(--accent)} .node-target-opt.on{border-color:var(--accent);color:var(--accent)}
.node-target-opt.off{opacity:.6} .node-target-opt.off{opacity:.6}
.node-target-opt input{margin:0} .node-target-opt input{margin:0}
/* ===== Direction « Terre » : surfaces en cartes =======================
La charte pose des CARTES arrondies séparées par une gouttière, au lieu
d'une colonne à filets. Bloc en fin de feuille pour prendre le pas sur les
règles de structure d'origine sans les réécrire.
Bureau UNIQUEMENT : sur téléphone la barre latérale est un tiroir plein
écran et le fil occupe toute la largeur — y poser des cartes ne ferait que
grignoter des pixels précieux. */
@media (min-width:721px){
/* Coque : le fond « terre » se voit dans la gouttière entre les deux cartes. */
body{padding:12px;gap:12px}
html[data-pwa="1"] body{padding-top:calc(12px + env(safe-area-inset-top))}
/* Barre latérale : pile de cartes, sans filet ni bordure de colonne. */
.side{width:312px;border-right:0;padding:0;background:transparent;
display:flex;flex-direction:column;gap:12px;overflow-y:auto;overscroll-behavior:contain}
.sidecard,.side>details{background:var(--panel);border:0;border-top:0;border-radius:20px;
box-shadow:var(--shadow);margin:0;padding:2px 16px}
.sidecard{padding:15px 18px 14px;display:flex;flex-direction:column;gap:2px}
.side>details[open]{padding-bottom:14px}
.side>details>summary{height:42px}
/* L'étiquette « Paramètres » n'est plus un filet mais un intertitre entre
deux cartes : le fond de la coque fait déjà la séparation. */
.side-sep{border-top:0;margin:10px 0 0;padding:0 6px}
/* Blocs encastrés DANS une carte : un ton plus chaud, sans bordure — deux
cadres imbriqués alourdiraient inutilement. */
.side .slist{background:var(--row-bg);border-color:transparent;border-radius:14px}
/* Colonne principale : une seule grande carte qui contient fil et saisie. */
.main{background:var(--panel);border-radius:20px;box-shadow:var(--shadow);overflow:hidden}
#chat{padding:26px max(20px,calc(50% - 360px)) 8px}
/* Le dégradé qui fait disparaître le fil sous la carte de saisie doit fondre
vers le PANNEAU, pas vers le fond de la coque. */
#composer{background:linear-gradient(to bottom,transparent 0,var(--panel) 34%)}
/* Carte de saisie : posée SUR le panneau, donc un ton au-dessus, et sans
bordure — c'est l'élévation qui la détache. */
#inputbar,#ctxmeta{background:var(--row-bg-on);border-color:transparent}
#input{background:transparent}
}
/* --- Terre : densité et accents ------------------------------------- */
@media (min-width:721px){
/* Gouttière resserrée : la charte empile beaucoup de cartes, 12 px entre
chacune repoussait « Actions » loin sous la ligne de flottaison. */
.side{gap:8px}
.side>details>summary{height:38px}
/* Discussion active : un LISERÉ sauge à gauche, pas un cadre complet — c'est
la marque de sélection de la charte, et elle se lit sans alourdir. */
.side .preset{border:0;border-left:3px solid transparent;border-radius:14px;background:transparent}
.side .preset.active{background:var(--row-bg-on);border-left-color:var(--accent)}
.side .preset:hover{background:var(--row-bg)}
/* Bulle de question : coin bas-droit rentré, comme une bulle qui pointe vers
son auteur. */
#chat .msg.user{border-radius:20px 20px 6px 20px}
}
/* Action principale en sauge : c'est la couleur d'action de la charte, et elle
se distingue enfin du texte (le bouton reprenait --text, donc un brun sombre
indistinct du reste). L'arrêt, lui, reste en terre cuite. */
#send{background:var(--accent);border-color:var(--accent);color:#fff}
#send:hover:not(:disabled){background:var(--accent);border-color:var(--accent);color:#fff;opacity:.88}
#stop{background:var(--err);border-color:var(--err);color:#fff}
#stop:hover{background:var(--err);border-color:var(--err);color:#fff;opacity:.88}
+23 -1
View File
@@ -9,6 +9,7 @@ import (
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
"path"
"regexp" "regexp"
"runtime" "runtime"
"strconv" "strconv"
@@ -18,7 +19,7 @@ import (
) )
//go:generate go run ../../tools/assemble-ui ui //go:generate go run ../../tools/assemble-ui ui
//go:embed ui/index.html ui/marked.min.js //go:embed ui/index.html ui/marked.min.js ui/fonts/*.woff2
var uiFS embed.FS var uiFS embed.FS
// cmdWeb starts the HTTP server on the given port (default 8090). // cmdWeb starts the HTTP server on the given port (default 8090).
@@ -115,6 +116,27 @@ func newWebMux() *http.ServeMux {
w.Header().Set("Cache-Control", "public, max-age=86400") w.Header().Set("Cache-Control", "public, max-age=86400")
w.Write(b) w.Write(b)
}) })
// Polices embarquées (Bricolage Grotesque + IBM Plex Mono, sous-ensemble
// latin). Servies par Loki et non par Google Fonts : une instance locale ou
// derrière un réseau fermé doit s'afficher correctement sans appeler un tiers,
// et sans lui signaler qui consulte l'interface.
mux.HandleFunc("/fonts/", func(w http.ResponseWriter, r *http.Request) {
name := path.Base(r.URL.Path)
if !strings.HasSuffix(name, ".woff2") {
http.NotFound(w, r)
return
}
b, err := uiFS.ReadFile("ui/fonts/" + name)
if err != nil {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "font/woff2")
// Immuables : leur nom change si leur contenu change (nouvelle version
// de l'image), donc un an de cache est sans risque.
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
w.Write(b)
})
// api enregistre une route /api/* protégée par la clé de pilotage (web_auth.go). // api enregistre une route /api/* protégée par la clé de pilotage (web_auth.go).
api := func(path string, h http.HandlerFunc) { mux.HandleFunc(path, requireWebAuth(h)) } api := func(path string, h http.HandlerFunc) { mux.HandleFunc(path, requireWebAuth(h)) }
api("/api/ping", handlePing) api("/api/ping", handlePing)