diff --git a/internal/loki/ui/fonts/bricolage.woff2 b/internal/loki/ui/fonts/bricolage.woff2 new file mode 100644 index 0000000..fcc4eb1 Binary files /dev/null and b/internal/loki/ui/fonts/bricolage.woff2 differ diff --git a/internal/loki/ui/fonts/plexmono-400.woff2 b/internal/loki/ui/fonts/plexmono-400.woff2 new file mode 100644 index 0000000..0804aaf Binary files /dev/null and b/internal/loki/ui/fonts/plexmono-400.woff2 differ diff --git a/internal/loki/ui/fonts/plexmono-500.woff2 b/internal/loki/ui/fonts/plexmono-500.woff2 new file mode 100644 index 0000000..090f82f Binary files /dev/null and b/internal/loki/ui/fonts/plexmono-500.woff2 differ diff --git a/internal/loki/ui/index.html b/internal/loki/ui/index.html index 06d95e0..bb4776d 100644 --- a/internal/loki/ui/index.html +++ b/internal/loki/ui/index.html @@ -24,12 +24,45 @@ +

…
fork de AJEAN
+
diff --git a/internal/loki/ui/src/index.tmpl.html b/internal/loki/ui/src/index.tmpl.html index a2cbe29..1e8ea46 100644 --- a/internal/loki/ui/src/index.tmpl.html +++ b/internal/loki/ui/src/index.tmpl.html @@ -33,11 +33,15 @@
+ +

…
fork de AJEAN
+
diff --git a/internal/loki/ui/src/styles.css b/internal/loki/ui/src/styles.css index 3f57e70..b93154b 100644 --- a/internal/loki/ui/src/styles.css +++ b/internal/loki/ui/src/styles.css @@ -1,9 +1,42 @@ -/* ===== Palette ===================================================== - Un seul thème (« Clean ») : noir et blanc, sobre. Deux variantes — claire par - défaut, sombre via data-theme="dark" sur . Toutes les couleurs de l'UI - passent par ces variables ; rien d'autre ne code une couleur en dur. */ -:root{color-scheme:light;--font:-apple-system,BlinkMacSystemFont,'Segoe UI',Inter,Helvetica,Arial,sans-serif;--mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;--bubble:#f0f0f0;--bg:#ffffff;--panel:#fafafa;--border:#e0e0e2;--text:#0a0a0a;--dim:#6b6b70;--accent:#0a0a0a;--accent-bg:rgba(0,0,0,.05);--row-bg:rgba(0,0,0,.03);--row-bg-on:rgba(0,0,0,.042);--ok:#0a0a0a;--warn:#6b6b6b;--err:#c0392b;--mag:#6b6b6b;--code-bg:#f5f5f5;--danger-bg:#fbeae8;--warn-bg:#f2f2f2;--add:#1a7f37;--add-bg:rgba(26,127,55,.10);--del:#cf222e;--del-bg:rgba(207,34,46,.10)} -[data-theme="dark"]{color-scheme:dark;--bubble:#232326;--bg:#0b0b0c;--panel:#161618;--border:#33333a;--text:#f2f2f4;--dim:#a0a0a8;--accent:#f5f5f5;--accent-bg:rgba(255,255,255,.07);--row-bg:rgba(255,255,255,.04);--row-bg-on:rgba(255,255,255,.055);--ok:#f5f5f5;--warn:#8e8e8e;--err:#f2777a;--mag:#8e8e8e;--code-bg:#000000;--danger-bg:#2e1a19;--warn-bg:#1c1c1c;--add:#4ac26b;--add-bg:rgba(74,194,107,.13);--del:#f85149;--del-bg:rgba(248,81,73,.13)} +/* ===== Polices ===================================================== + Bricolage Grotesque (titres et texte) + IBM Plex Mono (chiffres, chemins, + code). Servies par Loki (/fonts/, embarquées dans le binaire) et NON par + Google Fonts : une instance locale ou coupée d'internet doit s'afficher + correctement, sans qu'un tiers apprenne qui consulte l'interface. + 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 . 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, 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 @@ -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.off{opacity:.6} .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} diff --git a/internal/loki/web_server.go b/internal/loki/web_server.go index 34bdc68..5121d95 100644 --- a/internal/loki/web_server.go +++ b/internal/loki/web_server.go @@ -9,6 +9,7 @@ import ( "net/http" "os" "os/exec" + "path" "regexp" "runtime" "strconv" @@ -18,7 +19,7 @@ import ( ) //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 // 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.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 := func(path string, h http.HandlerFunc) { mux.HandleFunc(path, requireWebAuth(h)) } api("/api/ping", handlePing)