Thème Nocturne clair (Claude Design) à la place du néo-brutaliste

Reprend les jetons de « Loki App.dc.html » : fond gris froid #f7f8fa,
surfaces blanches, accent bleu ardoise #2f4a7a (au lieu de l'orange),
filets 1 px #e3e6eb (au lieu des bordures noires 3 px) et ombres douces
diffuses (au lieu des ombres dures décalées).

- tailwind.config.ts : les NOMS de jetons sont conservés, seules leurs
  valeurs basculent — toute l'interface suit sans réécrire les
  composants. Les jetons historiquement sombres (card-deep, on-dark…)
  deviennent la surface sélectionnée claire et son encre.
- Police unique Inter ; l'ex-« font-pixel » devient le kicker de la
  maquette (petites capitales espacées de 0.14em), donc les libellés
  existants suivent sans modification.
- Conversion mécanique des filets 3 px / 2 px vers 1 px, séparateurs
  épais affinés, logo et avatar refaits selon la maquette (carré teinté
  à filet accent), couleurs « papier » de l'aperçu passées en jetons.
- Texte blanc corrigé partout où il reposait sur une surface devenue
  claire ; conservé sur les fonds accent/warn où il reste lisible.

Vérifié au rendu : body #f7f8fa, encre #1c2536, surface #ffffff, Inter ;
plus aucune trace d'orange, de noir #18181b ni d'ombre dure.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SVay7z3y7q2gEe54ByAE6N
This commit is contained in:
Claude committed 2026-08-03 09:06:46 +00:00
1 parent 174a3d4a35
commit 7615bcfbbb
16 files changed
+254 -215

No files matched your search

+1 -1
View File
@@ -7,7 +7,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Press+Start+2P&display=swap"
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
rel="stylesheet"
/>
<title>Loki - Agent local</title>
+4 -4
View File
@@ -16,10 +16,10 @@ const items: { id: View; label: string; icon: React.ReactNode }[] = [
];
const btn = (on: boolean) =>
`flex h-10 w-10 items-center justify-center border-[3px] ${
`flex h-10 w-10 items-center justify-center border ${
on
? "border-white bg-accent text-white shadow-accent-soft"
: "border-chrome-3 text-on-dark-2 hover:text-white"
: "border-chrome-3 text-on-dark-2 hover:text-ink"
}`;
/** Barre d'activité verticale (60px) sombre à gauche. */
@@ -31,7 +31,7 @@ export function ActivityBar({
onChange: (v: View) => void;
}) {
return (
<div className="flex w-[60px] flex-none flex-col items-center gap-2 border-r-[3px] border-line bg-bar py-3">
<div className="flex w-[60px] flex-none flex-col items-center gap-2 border-r border-line bg-bar py-3">
{items.map((it) => (
<button
key={it.id}
@@ -55,7 +55,7 @@ export function ActivityBar({
<SettingsIcon />
</button>
<div className="font-pixel flex h-[34px] w-[34px] items-center justify-center border-[3px] border-white bg-white text-[11px] text-line">
<div className="font-pixel flex h-[34px] w-[34px] items-center justify-center border border-line bg-line-soft text-[11px] text-ink-3">
M
</div>
</div>
+9 -6
View File
@@ -104,20 +104,23 @@ export const CheckIcon = ({ size = 14, className }: Props) => (
</svg>
);
/** Logo Loki : carré orange à bordure blanche + carré interne (néo-brutaliste). */
/** Logo Loki : carré teinté à filet accent + petit carré accent (maquette). */
export const LokiMark = ({ size = 30 }: { size?: number }) => (
<div
className="flex items-center justify-center bg-accent"
className="grid place-items-center border border-accent bg-accent-ghost"
style={{
width: size,
height: size,
border: "3px solid #fff",
boxShadow: "3px 3px 0 #ff5436",
borderRadius: 7,
borderRadius: Math.round(size * 0.27),
}}
>
<div
style={{ width: size * 0.3, height: size * 0.3, background: "#fff" }}
className="bg-accent"
style={{
width: size * 0.3,
height: size * 0.3,
borderRadius: 2,
}}
/>
</div>
);
+4 -4
View File
@@ -45,15 +45,15 @@ function CodeBlock({ lang, code }: { lang: string; code: string }) {
};
return (
<div className="my-2 overflow-hidden border-[3px] border-line bg-card-deep shadow-hard-sm" style={{ borderRadius: 7 }}>
<div className="flex items-center justify-between border-b-2 border-chrome-2 px-3 py-1.5">
<div className="my-2 overflow-hidden border border-line bg-card-deep shadow-hard-sm" style={{ borderRadius: 7 }}>
<div className="flex items-center justify-between border-b border-chrome-2 px-3 py-1.5">
<span className="font-mono text-[10.5px] uppercase tracking-wide text-on-dark-2">
{lang || "code"}
</span>
<button
type="button"
onClick={copy}
className="flex items-center gap-1 rounded-md px-1.5 py-0.5 text-[11px] text-on-dark-2 transition-colors hover:text-white"
className="flex items-center gap-1 rounded-md px-1.5 py-0.5 text-[11px] text-on-dark-2 transition-colors hover:text-ink"
>
{copied ? <CheckIcon size={13} /> : <CopyIcon size={13} />}
{failed ? "Erreur" : copied ? "Copié" : "Copier"}
@@ -122,7 +122,7 @@ const components: Components = {
),
em: ({ children }) => <em className="italic">{children}</em>,
blockquote: ({ children }) => (
<blockquote className="my-2 border-l-2 border-line-strong pl-3 text-muted">
<blockquote className="my-2 border-l border-line-strong pl-3 text-muted">
{children}
</blockquote>
),
+5 -5
View File
@@ -40,14 +40,14 @@ export function ModelSelector({
onClick={() => setOpen((v) => !v)}
className={
variant === "composer"
? "flex h-8 min-w-0 max-w-[220px] items-center gap-1.5 border-2 border-line bg-accent px-2.5 text-[13px] text-white"
: "flex h-[34px] min-w-0 max-w-[260px] items-center gap-2 border-[3px] border-white bg-accent px-3 text-white shadow-accent-soft"
? "flex h-8 min-w-0 max-w-[220px] items-center gap-1.5 border border-line bg-accent px-2.5 text-[13px] text-white"
: "flex h-[34px] min-w-0 max-w-[260px] items-center gap-2 border border-white bg-accent px-3 text-white shadow-accent-soft"
}
title={selectedModel || undefined}
style={{ borderRadius: 7 }}
>
<span
className={`h-2.5 w-2.5 border-2 border-line ${
className={`h-2.5 w-2.5 border border-line ${
warmingModel === selectedModel
? "bg-accent animate-pulse"
: selectedLoaded
@@ -74,7 +74,7 @@ export function ModelSelector({
{open && (
<div
className={`absolute z-20 w-64 border-[3px] border-line bg-card p-1 shadow-hard ${
className={`absolute z-20 w-64 border border-line bg-card p-1 shadow-hard ${
variant === "composer"
? "bottom-[calc(100%+6px)] left-0"
: "right-0 top-11"
@@ -97,7 +97,7 @@ export function ModelSelector({
}`}
>
<span
className={`h-2 w-2 border-2 border-line ${
className={`h-2 w-2 border border-line ${
m.name === selectedModel ? "bg-accent" : "bg-card"
}`}
/>
+5 -5
View File
@@ -52,7 +52,7 @@ export function ProjectChip() {
<div ref={rootRef} className="relative">
<button
onClick={() => setOpen((o) => !o)}
className="flex h-8 items-center gap-1.5 border-2 border-line bg-base px-2.5 text-[12px] text-ink-2"
className="flex h-8 items-center gap-1.5 border border-line bg-base px-2.5 text-[12px] text-ink-2"
title="Projet de travail de cette session"
>
📁 <span className="max-w-[140px] truncate">{active ?? "workspace"}</span>
@@ -60,7 +60,7 @@ export function ProjectChip() {
{open && (
<div
className="absolute bottom-[calc(100%+6px)] left-0 z-30 w-[240px] border-[3px] border-line bg-card shadow-hard"
className="absolute bottom-[calc(100%+6px)] left-0 z-30 w-[240px] border border-line bg-card shadow-hard"
style={{ borderRadius: 7 }}
>
<button
@@ -75,7 +75,7 @@ export function ProjectChip() {
<button
key={p.name}
onClick={() => choose(p.name)}
className={`block w-full border-t-2 border-line-soft px-3 py-2 text-left text-[13px] text-ink-2 hover:bg-base ${
className={`block w-full border-t border-line-soft px-3 py-2 text-left text-[13px] text-ink-2 hover:bg-base ${
active === p.name ? "bg-base font-bold" : ""
}`}
>
@@ -85,7 +85,7 @@ export function ProjectChip() {
</span>
</button>
))}
<div className="border-t-2 border-line-soft p-2">
<div className="border-t border-line-soft p-2">
{creating ? (
<input
autoFocus
@@ -99,7 +99,7 @@ export function ProjectChip() {
}
}}
placeholder="nom-du-projet"
className="w-full border-2 border-line bg-base px-2 py-1 text-[12px] text-ink outline-none"
className="w-full border border-line bg-base px-2 py-1 text-[12px] text-ink outline-none"
/>
) : (
<button
+8 -8
View File
@@ -55,7 +55,7 @@ export function SessionMenu() {
<div ref={rootRef} className="relative">
<button
onClick={() => setOpen((o) => !o)}
className="flex cursor-pointer items-center gap-2 text-[13px] text-on-dark hover:text-white"
className="flex cursor-pointer items-center gap-2 text-[13px] text-on-dark hover:text-ink"
title="Sessions"
>
<span className="max-w-[220px] truncate">
@@ -66,7 +66,7 @@ export function SessionMenu() {
{open && (
<div
className="absolute left-0 top-[calc(100%+9px)] z-30 w-[300px] border-[3px] border-line bg-chrome-2 shadow-hard"
className="absolute left-0 top-[calc(100%+9px)] z-30 w-[300px] border border-line bg-chrome-2 shadow-hard"
style={{ borderRadius: 7 }}
>
<button
@@ -74,7 +74,7 @@ export function SessionMenu() {
setOpen(false);
await newSession();
}}
className="flex w-full cursor-pointer items-center gap-2 border-b-[3px] border-chrome-3 px-3 py-2.5 text-[13px] font-bold text-accent hover:bg-chrome-3"
className="flex w-full cursor-pointer items-center gap-2 border-b border-chrome-3 px-3 py-2.5 text-[13px] font-bold text-accent hover:bg-chrome-3"
>
<PlusIcon />
Nouvelle session
@@ -97,7 +97,7 @@ export function SessionMenu() {
setOpen(false);
void openSession(s.id);
}}
className={`group cursor-pointer border-b-2 border-chrome-3 px-3 py-2 last:border-b-0 ${
className={`group cursor-pointer border-b border-chrome-3 px-3 py-2 last:border-b-0 ${
active ? "bg-chrome-3" : "hover:bg-chrome-3"
}`}
>
@@ -116,12 +116,12 @@ export function SessionMenu() {
}
}}
onBlur={() => setEditingId(null)}
className="min-w-0 flex-1 border-2 border-line bg-card-deep px-1.5 py-0.5 text-[13px] text-white outline-none"
className="min-w-0 flex-1 border border-line bg-card-deep px-1.5 py-0.5 text-[13px] text-ink outline-none"
/>
) : (
<span
className={`min-w-0 flex-1 truncate text-[13px] ${
active ? "text-white" : "text-on-dark"
active ? "text-ink" : "text-ink-3"
}`}
>
{s.title}
@@ -129,7 +129,7 @@ export function SessionMenu() {
)}
{working && (
<span
className="flex-none border-2 border-line bg-accent px-1 py-0.5 text-[9px] leading-none text-white"
className="flex-none border border-line bg-accent px-1 py-0.5 text-[9px] leading-none text-white"
title="Session en cours de travail"
>
EN COURS
@@ -141,7 +141,7 @@ export function SessionMenu() {
setEditingId(s.id);
setDraft(s.title);
}}
className="hidden flex-none text-[11px] text-on-dark-3 hover:text-white group-hover:block"
className="hidden flex-none text-[11px] text-on-dark-3 hover:text-ink group-hover:block"
title="Renommer la session"
>
✎
+5 -5
View File
@@ -23,11 +23,11 @@ export function ToolCard({ call }: { call: ToolCall }) {
const write = call.name === "write_file" || call.name === "edit_file";
return (
<div className="mb-[11px] overflow-hidden border-[3px] border-line bg-card shadow-hard-sm" style={{ borderRadius: 7 }}>
<div className="mb-[11px] overflow-hidden border border-line bg-card shadow-hard-sm" style={{ borderRadius: 7 }}>
<div className="flex items-center gap-2.5 px-3 py-2.5">
<span
className={`flex h-[26px] w-[26px] items-center justify-center border-2 border-line ${
write ? "bg-accent text-white" : "bg-card-deep text-white"
className={`flex h-[26px] w-[26px] items-center justify-center border border-line ${
write ? "bg-accent text-white" : "bg-card-deep text-ink"
}`}
>
<ToolGlyph name={call.name} />
@@ -37,7 +37,7 @@ export function ToolCard({ call }: { call: ToolCall }) {
<div className="flex-1" />
{running ? (
<span className="flex items-center gap-1.5 text-[13px] text-muted-2">
<span className="h-2.5 w-2.5 animate-spin rounded-full border-2 border-muted-3 border-t-accent" />
<span className="h-2.5 w-2.5 animate-spin rounded-full border border-muted-3 border-t-accent" />
en cours
</span>
) : pending ? (
@@ -53,7 +53,7 @@ export function ToolCard({ call }: { call: ToolCall }) {
)}
</div>
{call.summary && !running && (
<div className="border-t-2 border-line-soft px-3 py-2 text-[13px] text-muted-2">
<div className="border-t border-line-soft px-3 py-2 text-[13px] text-muted-2">
→ {call.summary}
</div>
)}
+5 -5
View File
@@ -16,10 +16,10 @@ export function TopBar() {
}, []);
return (
<div className="flex h-[54px] flex-none items-center gap-3.5 border-b-[3px] border-line bg-bar px-4">
<div className="flex h-[54px] flex-none items-center gap-3.5 border-b border-line bg-bar px-4">
<div className="flex items-center gap-2.5">
<LokiMark />
<div className="font-pixel text-[14px] text-white">LOKI</div>
<div className="font-pixel text-[14px] text-ink">LOKI</div>
<div
className="text-[13px] leading-none text-on-dark-2"
title={version ? `build ${version}` : undefined}
@@ -39,7 +39,7 @@ export function TopBar() {
{/* Stats système temps réel : CPU, RAM, GPU/VRAM */}
{stats && (
<div className="flex h-8 items-center gap-2.5 border-[3px] border-chrome-3 bg-chrome-2 px-[11px] text-[13px] text-on-dark">
<div className="flex h-8 items-center gap-2.5 border border-chrome-3 bg-chrome-2 px-[11px] text-[13px] text-on-dark">
<span>CPU {stats.cpu_pct.toFixed(0)}%</span>
<span className="text-on-dark-3">·</span>
<span>RAM {stats.ram_pct.toFixed(0)}%</span>
@@ -58,11 +58,11 @@ export function TopBar() {
{/* Statut Ollama */}
<div
className="flex h-8 items-center gap-2 border-[3px] border-chrome-3 bg-chrome-2 px-[11px]"
className="flex h-8 items-center gap-2 border border-chrome-3 bg-chrome-2 px-[11px]"
title={connected ? status?.host : status?.error}
>
<span
className={`h-[11px] w-[11px] border-2 border-line ${
className={`h-[11px] w-[11px] border border-line ${
connected ? "bg-ok" : "bg-warn"
}`}
/>
+33 -13
View File
@@ -10,27 +10,47 @@ body,
}
body {
background: #ececef;
color: #18181b;
font-family: "DM Mono", ui-monospace, monospace;
letter-spacing: -0.01em;
background: #f7f8fa;
color: #1c2536;
font-family: "Inter", ui-sans-serif, system-ui, sans-serif;
-webkit-font-smoothing: antialiased;
}
/* Police pixel pour les libellés (utilitaire .font-pixel via Tailwind aussi) */
.font-pixel {
font-family: "Press Start 2P", ui-monospace, monospace;
/* Titres : léger resserrement, comme dans la maquette Nocturne. */
h1,
h2,
h3 {
letter-spacing: -0.025em;
}
/* Barres de défilement néo-brutalistes */
/* Ex-police pixel : « kicker » de la maquette (petites capitales espacées). */
.font-pixel {
font-family: "Inter", ui-sans-serif, system-ui, sans-serif;
font-weight: 500;
letter-spacing: 0.14em;
text-transform: uppercase;
}
/* Barres de défilement discrètes (règles .scr de la maquette) */
.scr::-webkit-scrollbar {
width: 12px;
height: 12px;
width: 10px;
height: 10px;
}
.scr::-webkit-scrollbar-thumb {
background: #18181b;
border: 2px solid #ececef;
background: #c3c9d2;
border-radius: 6px;
border: 3px solid transparent;
background-clip: content-box;
}
.scr::-webkit-scrollbar-track {
background: #e4e4e7;
background: transparent;
}
/* Focus visible accentué, sélection teintée — repris de la maquette. */
:focus-visible {
outline: 2px solid #2f4a7a;
outline-offset: 2px;
}
::selection {
background: rgba(47, 74, 122, 0.32);
}
+12 -12
View File
@@ -14,7 +14,7 @@ export function HistoryView({ onOpen }: { onOpen: () => void }) {
<Page title="Historique" subtitle="Toutes les conversations enregistrées localement.">
<div className="grid grid-cols-1 gap-3 lg:grid-cols-2 xl:grid-cols-3">
{sessions.map((session) => (
<div key={session.id} className="border-[3px] border-line bg-card p-4 shadow-hard">
<div key={session.id} className="border border-line bg-card p-4 shadow-hard">
<div className="truncate text-[15px] font-semibold text-ink">{session.title}</div>
<div className="mt-1 text-[12px] text-muted-2">
{session.message_count ?? 0} message(s) · {new Date(session.updated_at * 1000).toLocaleString("fr-FR")}
@@ -25,13 +25,13 @@ export function HistoryView({ onOpen }: { onOpen: () => void }) {
await openSession(session.id);
onOpen();
}}
className="border-2 border-line bg-accent px-3 py-1.5 text-[12px] font-bold text-white"
className="border border-line bg-accent px-3 py-1.5 text-[12px] font-bold text-white"
>
Ouvrir
</button>
<button
onClick={() => removeSession(session.id)}
className="border-2 border-line bg-base px-3 py-1.5 text-[12px] text-warn"
className="border border-line bg-base px-3 py-1.5 text-[12px] text-warn"
>
Supprimer
</button>
@@ -53,7 +53,7 @@ export function FilesView() {
return (
<Page title="Fichiers" subtitle="Workspace créé et modifié par l’agent.">
<div className="border-[3px] border-line bg-panel p-3 shadow-hard">
<div className="border border-line bg-panel p-3 shadow-hard">
{fileTree.length ? <WorkspaceTree nodes={fileTree} depth={0} /> : <Empty text="Le workspace est vide." />}
</div>
</Page>
@@ -74,9 +74,9 @@ function WorkspaceTree({ nodes, depth }: { nodes: FileNode[]; depth: number }) {
{nodes.map((node) => (
<div key={node.path}>
<div
className={`mb-1 flex items-center gap-2 border-2 px-3 py-2 ${
className={`mb-1 flex items-center gap-2 border px-3 py-2 ${
node.path === previewPath
? "border-accent bg-card-deep text-white"
? "border-accent bg-card-deep text-ink"
: "border-line bg-card text-ink-2"
}`}
style={{ marginLeft: depth * 18 }}
@@ -94,7 +94,7 @@ function WorkspaceTree({ nodes, depth }: { nodes: FileNode[]; depth: number }) {
{node.type === "file" && (
<button
onClick={() => downloadFile(node.path, currentProject())}
className="flex items-center gap-1 border-2 border-line bg-base px-2 py-1 text-[11px] text-accent"
className="flex items-center gap-1 border border-line bg-base px-2 py-1 text-[11px] text-accent"
title={`Télécharger ${node.name}`}
>
<DownloadIcon size={12} /> Télécharger
@@ -102,7 +102,7 @@ function WorkspaceTree({ nodes, depth }: { nodes: FileNode[]; depth: number }) {
)}
<button
onClick={() => confirmDelete(node)}
className="flex items-center gap-1 border-2 border-line bg-base px-2 py-1 text-[11px] text-warn"
className="flex items-center gap-1 border border-line bg-base px-2 py-1 text-[11px] text-warn"
title={`Supprimer ${node.name}`}
>
<TrashIcon size={12} /> Supprimer
@@ -128,10 +128,10 @@ export function ToolsView({ onSettings }: { onSettings: () => void }) {
{availableTools.map((name) => {
const enabled = config?.tools[name] ?? false;
return (
<div key={name} className="border-[3px] border-line bg-card p-4 shadow-hard">
<div key={name} className="border border-line bg-card p-4 shadow-hard">
<div className="flex items-center justify-between gap-3">
<span className="font-mono text-[14px] text-ink">{name}</span>
<span className={`border-2 border-line px-2 py-1 text-[10px] ${enabled ? "bg-ok text-white" : "bg-base text-muted-2"}`}>
<span className={`border border-line px-2 py-1 text-[10px] ${enabled ? "bg-ok text-white" : "bg-base text-muted-2"}`}>
{enabled ? "ACTIF" : "INACTIF"}
</span>
</div>
@@ -141,7 +141,7 @@ export function ToolsView({ onSettings }: { onSettings: () => void }) {
</div>
<button
onClick={onSettings}
className="mt-5 border-[3px] border-line bg-accent px-4 py-2 text-[13px] font-bold text-white shadow-hard"
className="mt-5 border border-line bg-accent px-4 py-2 text-[13px] font-bold text-white shadow-hard"
>
Configurer les outils
</button>
@@ -162,7 +162,7 @@ function Page({ title, subtitle, children }: { title: string; subtitle: string;
}
function Empty({ text }: { text: string }) {
return <div className="border-2 border-line bg-card p-8 text-center text-muted-2">{text}</div>;
return <div className="border border-line bg-card p-8 text-center text-muted-2">{text}</div>;
}
function formatSize(bytes: number): string {
+25 -25
View File
@@ -63,10 +63,10 @@ export function ChatPanel() {
return (
<div className="flex min-w-0 flex-1 flex-col bg-base">
{/* Barre de contexte */}
<div className="flex h-[46px] flex-none items-center gap-2 border-b-[3px] border-line bg-panel px-4">
<div className="flex h-[46px] flex-none items-center gap-2 border-b border-line bg-panel px-4">
<Chip>⚑ Invite système</Chip>
<Chip>
<span className="h-2 w-2 border-2 border-line bg-ok" />
<span className="h-2 w-2 border border-line bg-ok" />
{activeTools} outil{activeTools > 1 ? "s" : ""}
</Chip>
<Chip>
@@ -130,9 +130,9 @@ export function ChatPanel() {
</div>
{/* Composer */}
<div className="flex-none border-t-[3px] border-line bg-panel px-7 pb-[18px] pt-3.5">
<div className="flex-none border-t border-line bg-panel px-7 pb-[18px] pt-3.5">
<div className="mx-auto max-w-[680px]">
<div className="border-[3px] border-line bg-card p-3 shadow-hard" style={{ borderRadius: 8 }}>
<div className="border border-line bg-card p-3 shadow-hard" style={{ borderRadius: 8 }}>
<textarea
rows={1}
value={draft}
@@ -158,8 +158,8 @@ export function ChatPanel() {
<button
onClick={streaming ? stopStreaming : submit}
disabled={!streaming && !draft.trim()}
className={`flex h-[38px] items-center gap-1.5 border-[3px] border-line px-4 text-[14px] text-white shadow-hard-accent disabled:opacity-40 ${
streaming ? "bg-warn" : "bg-card-deep"
className={`flex h-[38px] items-center gap-1.5 border border-line px-4 text-[14px] text-white shadow-hard-accent disabled:opacity-40 ${
streaming ? "bg-warn" : "bg-accent"
}`}
style={{ borderRadius: 7 }}
>
@@ -201,7 +201,7 @@ function ModeSelector() {
<div className="relative" ref={ref}>
<button
onClick={() => setOpen((v) => !v)}
className={`flex h-8 items-center gap-1.5 border-2 border-line px-2.5 text-[13px] ${
className={`flex h-8 items-center gap-1.5 border border-line px-2.5 text-[13px] ${
mode === "plan"
? "bg-info text-white"
: mode === "yolo"
@@ -215,7 +215,7 @@ function ModeSelector() {
<ChevronDown size={11} />
</button>
{open && (
<div className="absolute bottom-10 left-0 z-20 w-64 border-[3px] border-line bg-card p-1 shadow-hard">
<div className="absolute bottom-10 left-0 z-20 w-64 border border-line bg-card p-1 shadow-hard">
{MODES.map((m) => (
<button
key={m.id}
@@ -257,10 +257,10 @@ function PlanCard({
return (
<div
className="mb-[11px] overflow-hidden border-[3px] border-line bg-card shadow-hard-sm"
className="mb-[11px] overflow-hidden border border-line bg-card shadow-hard-sm"
style={{ borderRadius: 7 }}
>
<div className="flex items-center gap-2 border-b-2 border-line-soft px-3 py-2">
<div className="flex items-center gap-2 border-b border-line-soft px-3 py-2">
<span className="font-pixel text-[9px] text-accent">PLAN</span>
<span className="text-[12px] text-muted-2">
{doneCount > 0
@@ -280,7 +280,7 @@ function PlanCard({
}`}
>
<span
className={`flex h-[18px] w-[18px] flex-none items-center justify-center border-2 text-[11px] ${
className={`flex h-[18px] w-[18px] flex-none items-center justify-center border text-[11px] ${
isDone
? "border-ok bg-ok text-white"
: isActive
@@ -324,7 +324,7 @@ function Bubble({
if (msg.role === "user") {
return (
<div className="flex gap-3">
<div className="font-pixel flex h-[34px] w-[34px] flex-none items-center justify-center border-[3px] border-line bg-card-deep text-[11px] text-white">
<div className="font-pixel flex h-[34px] w-[34px] flex-none items-center justify-center border border-line bg-card-deep text-[11px] text-ink">
M
</div>
<div className="flex-1">
@@ -332,7 +332,7 @@ function Bubble({
<span className="text-[14px] text-ink">VOUS</span>
<span className="text-[13px] text-muted-3">{time}</span>
</div>
<div className="border-[3px] border-line bg-card px-[14px] py-3 text-[14px] leading-snug text-ink shadow-hard-sm whitespace-pre-wrap" style={{ borderRadius: 7 }}>
<div className="border border-line bg-card px-[14px] py-3 text-[14px] leading-snug text-ink shadow-hard-sm whitespace-pre-wrap" style={{ borderRadius: 7 }}>
{msg.content}
</div>
</div>
@@ -342,8 +342,8 @@ function Bubble({
return (
<div className="flex gap-3">
<div className="flex h-[34px] w-[34px] flex-none items-center justify-center border-[3px] border-line bg-accent">
<div style={{ width: 10, height: 10, background: "#fff" }} />
<div className="grid h-[34px] w-[34px] flex-none place-items-center rounded-[9px] border border-accent bg-accent-ghost">
<div className="bg-accent" style={{ width: 10, height: 10, borderRadius: 2 }} />
</div>
<div className="min-w-0 flex-1">
<div className="mb-2 flex items-baseline gap-2">
@@ -364,7 +364,7 @@ function Bubble({
<ToolCard key={i} call={t} />
))}
{notice && (
<div className="mb-2 border-[3px] border-line bg-card px-3 py-2 text-[13px] text-warn">
<div className="mb-2 border border-line bg-card px-3 py-2 text-[13px] text-warn">
{notice}
</div>
)}
@@ -416,7 +416,7 @@ function ReasoningPanel({ text, live }: { text: string; live: boolean }) {
if (!text) return null;
return (
<div className="mb-[11px] overflow-hidden border-[3px] border-line bg-card shadow-hard-sm" style={{ borderRadius: 7 }}>
<div className="mb-[11px] overflow-hidden border border-line bg-card shadow-hard-sm" style={{ borderRadius: 7 }}>
<button
onClick={() => setOpen((o) => !o)}
className="flex w-full items-center gap-2 px-3 py-2 text-left"
@@ -428,7 +428,7 @@ function ReasoningPanel({ text, live }: { text: string; live: boolean }) {
<span className="text-[13px] font-medium text-ink">Raisonnement</span>
{live && (
<span className="flex items-center gap-1 text-[12px] text-accent">
<span className="h-2 w-2 animate-pulse border-2 border-line bg-accent" />
<span className="h-2 w-2 animate-pulse border border-line bg-accent" />
en cours…
</span>
)}
@@ -439,7 +439,7 @@ function ReasoningPanel({ text, live }: { text: string; live: boolean }) {
{open && (
<div
ref={bodyRef}
className="scr max-h-[200px] min-h-[60px] resize-y overflow-auto whitespace-pre-wrap border-t-2 border-line-soft bg-card-deep px-3 py-2 text-[12.5px] leading-relaxed text-on-dark-2"
className="scr max-h-[200px] min-h-[60px] resize-y overflow-auto whitespace-pre-wrap border-t border-line-soft bg-card-deep px-3 py-2 text-[12.5px] leading-relaxed text-on-dark-2"
>
{text}
</div>
@@ -458,26 +458,26 @@ function ShellConfirm({
onReject: () => void;
}) {
return (
<div className="ml-[46px] overflow-hidden border-[3px] border-line bg-card shadow-hard" style={{ borderRadius: 7 }}>
<div className="flex items-center gap-2 border-b-2 border-line px-3 py-2.5">
<div className="ml-[46px] overflow-hidden border border-line bg-card shadow-hard" style={{ borderRadius: 7 }}>
<div className="flex items-center gap-2 border-b border-line px-3 py-2.5">
<span className="text-[14px] text-accent">run_shell</span>
<span className="text-[13px] text-muted-2">· commande sensible à valider</span>
</div>
<div className="px-3 py-3">
<pre className="m-0 mb-3 overflow-auto whitespace-pre-wrap border-2 border-line bg-card-deep px-3 py-2.5 text-[12.5px] text-on-dark">
<pre className="m-0 mb-3 overflow-auto whitespace-pre-wrap border border-line bg-card-deep px-3 py-2.5 text-[12.5px] text-on-dark">
$ {command}
</pre>
<div className="flex items-center gap-2">
<button
onClick={onApprove}
className="flex h-[32px] items-center gap-1.5 border-[3px] border-line bg-card-deep px-3.5 text-[13px] text-white shadow-hard-accent"
className="flex h-[32px] items-center gap-1.5 border border-line bg-accent px-3.5 text-[13px] text-white shadow-hard-accent"
style={{ borderRadius: 7 }}
>
Approuver &amp; exécuter
</button>
<button
onClick={onReject}
className="flex h-[32px] items-center border-[3px] border-line bg-card px-3.5 text-[13px] text-ink-2"
className="flex h-[32px] items-center border border-line bg-card px-3.5 text-[13px] text-ink-2"
style={{ borderRadius: 7 }}
>
Refuser
@@ -490,7 +490,7 @@ function ShellConfirm({
function Chip({ children }: { children: React.ReactNode }) {
return (
<div className="flex h-7 items-center gap-1.5 border-2 border-line bg-card px-2.5 text-[13px] text-ink-2">
<div className="flex h-7 items-center gap-1.5 border border-line bg-card px-2.5 text-[13px] text-ink-2">
{children}
</div>
);
+7 -7
View File
@@ -24,7 +24,7 @@ export function LeftPanel() {
}, [refreshSessions, refreshFiles]);
return (
<div className="flex w-[280px] flex-none flex-col border-r-[3px] border-line bg-panel">
<div className="flex w-[280px] flex-none flex-col border-r border-line bg-panel">
{/* Historique */}
<div className="flex items-center justify-between px-4 pb-2.5 pt-4">
<span className="font-pixel text-[10px] text-label">HISTORIQUE</span>
@@ -50,7 +50,7 @@ export function LeftPanel() {
<div
key={s.id}
onClick={() => openSession(s.id)}
className={`group cursor-pointer border-[3px] border-line px-3 py-2.5 ${
className={`group cursor-pointer border border-line px-3 py-2.5 ${
active ? "bg-card-deep shadow-hard-accent" : "bg-card"
}`}
style={{ borderRadius: 7 }}
@@ -58,14 +58,14 @@ export function LeftPanel() {
<div className="flex items-center gap-1">
<div
className={`flex-1 truncate text-[14px] leading-tight ${
active ? "text-white" : "text-ink-2"
active ? "text-ink" : "text-ink-2"
}`}
>
{s.title}
</div>
{working && (
<span
className="flex-none border-2 border-line bg-accent px-1.5 py-0.5 text-[10px] leading-none text-white"
className="flex-none border border-line bg-accent px-1.5 py-0.5 text-[10px] leading-none text-white"
title="Session en cours de travail"
>
EN COURS
@@ -93,7 +93,7 @@ export function LeftPanel() {
})}
</div>
<div className="mx-0 my-4 h-[3px] bg-line" />
<div className="mx-0 my-4 h-px bg-line" />
{/* Fichiers */}
<div className="flex items-center justify-between px-4 pb-2">
@@ -136,7 +136,7 @@ function FileTree({ nodes, depth }: { nodes: FileNode[]; depth: number }) {
className={`group mb-[3px] flex items-center gap-2 px-2 py-[5px] ${
n.type === "file"
? active
? "cursor-pointer border-2 border-line bg-card-deep text-white"
? "cursor-pointer border border-line bg-card-deep text-ink"
: "cursor-pointer text-ink-2 hover:bg-base"
: "text-muted"
}`}
@@ -146,7 +146,7 @@ function FileTree({ nodes, depth }: { nodes: FileNode[]; depth: number }) {
<span className="text-muted-2">▾</span>
) : (
<span
className={`h-2 w-2 border-2 ${
className={`h-2 w-2 border ${
active ? "border-white bg-accent" : "border-line bg-muted-2"
}`}
/>
+20 -20
View File
@@ -63,7 +63,7 @@ export function PreviewPanel() {
// Replié : barre fine, aucun contenu monté (pas d'iframe vivante).
if (collapsed) {
return (
<div className="flex w-9 flex-none flex-col items-center border-l-[3px] border-line bg-panel pt-3">
<div className="flex w-9 flex-none flex-col items-center border-l border-line bg-panel pt-3">
<button
onClick={toggleCollapsed}
title="Déplier l'aperçu"
@@ -80,7 +80,7 @@ export function PreviewPanel() {
return (
<div
className="relative flex flex-none flex-col border-l-[3px] border-line bg-panel"
className="relative flex flex-none flex-col border-l border-line bg-panel"
style={{ width }}
>
<div
@@ -97,7 +97,7 @@ export function PreviewPanel() {
title="Glisser pour redimensionner"
/>
{/* Onglets */}
<div className="flex h-[46px] flex-none items-center gap-1.5 border-b-[3px] border-line bg-base px-3">
<div className="flex h-[46px] flex-none items-center gap-1.5 border-b border-line bg-base px-3">
<Tab active={tab === "preview"} onClick={() => setTab("preview")}>
Aperçu
</Tab>
@@ -106,7 +106,7 @@ export function PreviewPanel() {
</Tab>
<Tab active={tab === "logs"} onClick={() => setTab("logs")}>
Logs
<span className="font-pixel ml-1.5 border-2 border-line bg-accent px-1 py-0.5 text-[8px] text-white">
<span className="font-pixel ml-1.5 border border-line bg-accent px-1 py-0.5 text-[8px] text-white">
{logs.length}
</span>
</Tab>
@@ -125,7 +125,7 @@ export function PreviewPanel() {
{/* URL bar */}
<div className="flex flex-none items-center gap-2 px-3 py-2.5">
<div className="flex h-8 flex-1 items-center gap-2 border-[3px] border-line bg-card px-[11px]">
<div className="flex h-8 flex-1 items-center gap-2 border border-line bg-card px-[11px]">
<span className="text-[13px]">🔒</span>
<span className="truncate text-[13px] text-muted-2">
{previewPath ? `workspace/${previewPath}` : "workspace/"}
@@ -135,7 +135,7 @@ export function PreviewPanel() {
<>
<button
onClick={() => downloadFile(previewPath, useStore.getState().currentProject())}
className="flex h-8 items-center gap-1.5 border-[3px] border-line bg-card px-2.5 text-[12px] text-accent"
className="flex h-8 items-center gap-1.5 border border-line bg-card px-2.5 text-[12px] text-accent"
title={`Télécharger ${previewPath}`}
>
<DownloadIcon size={13} />
@@ -147,7 +147,7 @@ export function PreviewPanel() {
void removeFile(previewPath);
}
}}
className="flex h-8 items-center gap-1.5 border-[3px] border-line bg-card px-2.5 text-[12px] text-warn"
className="flex h-8 items-center gap-1.5 border border-line bg-card px-2.5 text-[12px] text-warn"
title={`Supprimer ${previewPath}`}
>
<TrashIcon size={13} />
@@ -162,7 +162,7 @@ export function PreviewPanel() {
<GitPanel />
) : /* Onglet Logs (fond sombre) */
tab === "logs" ? (
<div className="scr mx-3 mb-3 flex-1 overflow-auto border-[3px] border-line bg-card-deep p-3 text-[12.5px]">
<div className="scr mx-3 mb-3 flex-1 overflow-auto border border-line bg-card-deep p-3 text-[12.5px]">
{logs.length === 0 ? (
<div className="py-10 text-center text-on-dark-3">
Aucune activité d'outil pour cette session.
@@ -171,7 +171,7 @@ export function PreviewPanel() {
logs.map((l, i) => (
<div
key={i}
className="flex items-center gap-2 border-b-2 border-chrome-2 py-[7px] last:border-0"
className="flex items-center gap-2 border-b border-chrome-2 py-[7px] last:border-0"
>
<span
className={
@@ -204,14 +204,14 @@ export function PreviewPanel() {
</div>
) : (
/* Viewport Aperçu / Code (fond clair) */
<div className="scr mx-3 mb-3 flex-1 overflow-auto border-[3px] border-line bg-[#faf6ef] shadow-hard">
<div className="scr mx-3 mb-3 flex-1 overflow-auto border border-line bg-panel shadow-hard">
{!previewPath ? (
<Empty>
L'aperçu s'affichera ici dès que l'agent générera un fichier (clique
aussi un fichier à gauche).
</Empty>
) : tab === "code" ? (
<pre className="m-0 whitespace-pre-wrap p-4 text-[12px] leading-relaxed text-[#2a2018]">
<pre className="m-0 whitespace-pre-wrap p-4 text-[12px] leading-relaxed text-ink">
{previewContent}
</pre>
) : isHtml ? (
@@ -225,7 +225,7 @@ export function PreviewPanel() {
sandbox="allow-scripts"
/>
) : (
<pre className="m-0 whitespace-pre-wrap p-4 text-[12px] leading-relaxed text-[#2a2018]">
<pre className="m-0 whitespace-pre-wrap p-4 text-[12px] leading-relaxed text-ink">
{previewContent}
</pre>
)}
@@ -276,14 +276,14 @@ function GitPanel() {
};
return (
<div className="scr mx-3 mb-3 flex flex-1 flex-col overflow-hidden border-[3px] border-line bg-card">
<div className="scr mx-3 mb-3 flex flex-1 flex-col overflow-hidden border border-line bg-card">
{error && (
<div className="border-b-2 border-line bg-warn/10 px-3 py-2 text-[12px] text-warn">
<div className="border-b border-line bg-warn/10 px-3 py-2 text-[12px] text-warn">
{error}
</div>
)}
{/* Liste des commits */}
<div className="scr max-h-[45%] overflow-auto border-b-2 border-line">
<div className="scr max-h-[45%] overflow-auto border-b border-line">
{commits.length === 0 ? (
<div className="p-4 text-center text-[12px] text-muted-2">
Aucun commit. L'agent commitera chaque modification de code ici.
@@ -310,7 +310,7 @@ function GitPanel() {
<button
onClick={() => doRevert(c.hash)}
disabled={busy === c.hash}
className="flex-none border-2 border-line bg-card px-2 py-1 text-[11px] text-warn disabled:opacity-40"
className="flex-none border border-line bg-card px-2 py-1 text-[11px] text-warn disabled:opacity-40"
title="Annuler ce commit (revert)"
>
{busy === c.hash ? "…" : "↶ Annuler"}
@@ -357,8 +357,8 @@ function DiffView({ diff }: { diff: string }) {
function Empty({ children }: { children: React.ReactNode }) {
return (
<div className="flex h-full flex-col items-center justify-center gap-2 p-8 text-center">
<div className="font-pixel text-[10px] text-[#a98b63]">AUCUN APERÇU</div>
<div className="max-w-[240px] text-[13px] text-[#8a7a66]">{children}</div>
<div className="font-pixel text-[10px] text-label">AUCUN APERÇU</div>
<div className="max-w-[240px] text-[13px] text-muted-2">{children}</div>
</div>
);
}
@@ -375,8 +375,8 @@ function Tab({
return (
<button
onClick={onClick}
className={`flex h-[30px] items-center border-[3px] border-line px-[13px] text-[13px] ${
active ? "bg-card-deep text-white" : "bg-card text-muted"
className={`flex h-[30px] items-center border border-line px-[13px] text-[13px] ${
active ? "bg-card-deep text-ink" : "bg-card text-muted"
}`}
>
{children}
+48 -48
View File
@@ -130,13 +130,13 @@ export function SettingsView() {
<div className="flex items-center gap-3">
<PresetBar model={selectedModel} onApplied={refreshConfig} />
<div className="flex items-center gap-1.5 text-[13px] text-ok">
<span className="h-2 w-2 border-2 border-line bg-ok" />
<span className="h-2 w-2 border border-line bg-ok" />
{status?.connected ? "Ollama connecté" : "Ollama déconnecté"}
</div>
<button
onClick={save}
disabled={!dirty}
className="flex h-[34px] items-center gap-1.5 border-[3px] border-line bg-accent px-4 text-[14px] text-white shadow-accent-soft disabled:opacity-40"
className="flex h-[34px] items-center gap-1.5 border border-line bg-accent px-4 text-[14px] text-white shadow-accent-soft disabled:opacity-40"
style={{ borderRadius: 7 }}
>
{saved ? "✓ ENREGISTRÉ" : dirty ? "ENREGISTRER" : "À JOUR"}
@@ -159,7 +159,7 @@ export function SettingsView() {
</div>
<button
onClick={refreshModels}
className="flex h-7 items-center gap-1.5 border-2 border-line bg-card-soft px-2.5 text-[13px] text-muted-2"
className="flex h-7 items-center gap-1.5 border border-line bg-card-soft px-2.5 text-[13px] text-muted-2"
>
<RefreshIcon size={12} />
Actualiser
@@ -168,7 +168,7 @@ export function SettingsView() {
<div className="flex flex-col gap-2">
{models.length === 0 && (
<div className="border-2 border-line bg-base p-4 text-center text-[13px] text-muted-2">
<div className="border border-line bg-base p-4 text-center text-[13px] text-muted-2">
Aucun modèle installé. Télécharge-en un ci-dessous.
</div>
)}
@@ -179,7 +179,7 @@ export function SettingsView() {
return (
<div
key={m.name}
className={`flex items-center gap-3 border-[3px] border-line p-3 text-left ${
className={`flex items-center gap-3 border border-line p-3 text-left ${
on ? "bg-card-deep" : "bg-card-soft"
}`}
>
@@ -188,7 +188,7 @@ export function SettingsView() {
className="flex min-w-0 flex-1 items-center gap-3 text-left"
>
<span
className={`flex h-[18px] w-[18px] flex-none items-center justify-center border-[3px] ${
className={`flex h-[18px] w-[18px] flex-none items-center justify-center border ${
on ? "border-accent" : "border-muted-3"
}`}
>
@@ -196,11 +196,11 @@ export function SettingsView() {
</span>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className={`truncate text-[14px] ${on ? "text-white" : "text-ink"}`}>
<span className={`truncate text-[14px] ${on ? "text-ink" : "text-ink"}`}>
{m.name}
</span>
{m.name === status?.default_model && (
<span className="font-pixel flex-none border-2 border-white bg-accent px-1.5 py-0.5 text-[7px] text-white">
<span className="font-pixel flex-none border border-white bg-accent px-1.5 py-0.5 text-[7px] text-white">
DÉFAUT
</span>
)}
@@ -237,7 +237,7 @@ export function SettingsView() {
<button
onClick={() => doDeleteModel(m.name)}
disabled={deletingModel === m.name}
className="flex-none border-2 border-line bg-base px-2 py-1 text-[12px] text-warn disabled:opacity-50"
className="flex-none border border-line bg-base px-2 py-1 text-[12px] text-warn disabled:opacity-50"
title={`Supprimer ${m.name}`}
>
{deletingModel === m.name ? "…" : "Supprimer"}
@@ -246,18 +246,18 @@ export function SettingsView() {
);
})}
{deleteError && (
<div className="border-2 border-warn bg-base px-3 py-2 text-[12px] text-warn">
<div className="border border-warn bg-base px-3 py-2 text-[12px] text-warn">
{deleteError}
</div>
)}
{warmError && (
<div className="border-2 border-warn bg-base px-3 py-2 text-[12px] text-warn">
<div className="border border-warn bg-base px-3 py-2 text-[12px] text-warn">
Préchargement : {warmError}
</div>
)}
</div>
<div className="my-4 h-[3px] bg-line" />
<div className="my-4 h-px bg-line" />
<div className="font-pixel mb-2.5 text-[10px] text-label">
TÉLÉCHARGER
@@ -267,11 +267,11 @@ export function SettingsView() {
value={pullName}
onChange={(e) => setPullName(e.target.value)}
placeholder="gemma2:9b"
className="h-9 flex-1 border-[3px] border-line bg-card-soft px-3 text-[13px] text-ink outline-none placeholder:text-muted-3"
className="h-9 flex-1 border border-line bg-card-soft px-3 text-[13px] text-ink outline-none placeholder:text-muted-3"
/>
<button
onClick={doPull}
className="flex h-9 items-center gap-1.5 border-[3px] border-line bg-card-deep px-3.5 text-[13px] text-white"
className="flex h-9 items-center gap-1.5 border border-line bg-accent px-3.5 text-[13px] text-white"
>
<DownloadIcon />
Récupérer
@@ -279,14 +279,14 @@ export function SettingsView() {
</div>
{pullStatus && (
<div className="border-[3px] border-line bg-card-soft p-3">
<div className="border border-line bg-card-soft p-3">
<div className="mb-2 flex items-center justify-between">
<span className="text-[13px] text-ink-2">{pullName}</span>
<span className="text-[13px] text-accent">
{pullStatus} {pullPct ? `· ${pullPct}%` : ""}
</span>
</div>
<div className="flex h-3.5 gap-px border-2 border-line bg-card p-px">
<div className="flex h-3.5 gap-px border border-line bg-card p-px">
<span
className="bg-accent transition-all"
style={{ width: `${pullPct}%` }}
@@ -374,13 +374,13 @@ export function SettingsView() {
onChange={(v) => set("num_batch", Math.round(v))}
last
/>
<div className="mt-3 border-2 border-line bg-card-soft px-3 py-2 text-[12px] leading-relaxed text-muted-2">
<div className="mt-3 border border-line bg-card-soft px-3 py-2 text-[12px] leading-relaxed text-muted-2">
La précision KV (<code>f16</code>/<code>q8_0</code>) est un
réglage global du serveur Ollama et nécessite son redémarrage.
</div>
</Advanced>
<div className="mt-4 flex items-center gap-3 border-2 border-line bg-base px-3 py-2.5">
<div className="mt-4 flex items-center gap-3 border border-line bg-base px-3 py-2.5">
<div className="flex-1">
<div className="text-[14px] text-ink">Mode réflexion</div>
<div className="text-[12px] text-muted-2">
@@ -405,7 +405,7 @@ export function SettingsView() {
<div
key={name}
className={`flex items-center gap-3 py-2 ${
i > 0 ? "border-t-2 border-line-soft" : ""
i > 0 ? "border-t border-line-soft" : ""
}`}
>
<span
@@ -424,7 +424,7 @@ export function SettingsView() {
);
})}
{draft.tools.run_shell && (
<div className="mt-2 flex items-center gap-3 border-2 border-line bg-base px-3 py-2.5">
<div className="mt-2 flex items-center gap-3 border border-line bg-base px-3 py-2.5">
<span className="flex-1 text-[13px] text-ink-2">
Demander une validation avant chaque commande shell
</span>
@@ -473,7 +473,7 @@ export function SettingsView() {
onClick={() => set(key, !draft[key])}
/>
))}
<div className="flex items-center gap-3 border-t-2 border-line-soft py-2">
<div className="flex items-center gap-3 border-t border-line-soft py-2">
<span className="flex-1">
<span className="block text-[14px] text-ink">Mémoire</span>
<span className="block text-[12px] text-muted-2">
@@ -489,7 +489,7 @@ export function SettingsView() {
e.target.value as AgentConfig["memory_mode"]
)
}
className="h-8 border-[3px] border-line bg-card px-2 text-[13px] text-ink"
className="h-8 border border-line bg-card px-2 text-[13px] text-ink"
>
<option value="off">Désactivée</option>
<option value="ondemand">Sur demande</option>
@@ -497,7 +497,7 @@ export function SettingsView() {
</select>
</div>
</Advanced>
<div className="mt-2 flex items-center gap-3 border-t-2 border-line-soft pt-3">
<div className="mt-2 flex items-center gap-3 border-t border-line-soft pt-3">
<span className="flex-1">
<span className="block text-[14px] text-ink">
Maintien en VRAM (préchargement)
@@ -509,7 +509,7 @@ export function SettingsView() {
<select
value={draft.keep_alive}
onChange={(e) => set("keep_alive", e.target.value)}
className="h-8 border-[3px] border-line bg-card px-2 text-[13px] text-ink"
className="h-8 border border-line bg-card px-2 text-[13px] text-ink"
>
<option value="0">Décharger aussitôt</option>
<option value="5m">5 min</option>
@@ -542,7 +542,7 @@ export function SettingsView() {
value={draft.system_prompt}
onChange={(e) => set("system_prompt", e.target.value)}
rows={6}
className="scr w-full resize-y border-[3px] border-line bg-card-deep p-4 text-[13px] leading-[1.6] text-on-dark outline-none"
className="scr w-full resize-y border border-line bg-card-deep p-4 text-[13px] leading-[1.6] text-on-dark outline-none"
/>
</Card>
</>
@@ -595,10 +595,10 @@ function McpCard() {
</div>
<div className="flex flex-col gap-3">
{servers.map((s) => (
<div key={s.id} className="border-[3px] border-line bg-base p-3">
<div key={s.id} className="border border-line bg-base p-3">
<div className="flex items-center gap-3">
<span
className={`h-[11px] w-[11px] flex-none border-2 border-line ${
className={`h-[11px] w-[11px] flex-none border border-line ${
s.state === "connected"
? "bg-ok"
: s.state === "error"
@@ -623,7 +623,7 @@ function McpCard() {
<button
onClick={() => doTest(s)}
disabled={testing === s.id}
className="border-2 border-line bg-card px-2 py-1 text-[11px] text-ink-2"
className="border border-line bg-card px-2 py-1 text-[11px] text-ink-2"
>
{testing === s.id ? "test…" : "Tester"}
</button>
@@ -636,7 +636,7 @@ function McpCard() {
defaultValue={s.params.command ?? ""}
onBlur={(e) => setParam(s, "command", e.target.value)}
placeholder="commande stdio (ex. npx -y mon-mcp) ou URL"
className="border-2 border-line bg-card px-2 py-1 text-[12px] text-ink"
className="border border-line bg-card px-2 py-1 text-[12px] text-ink"
/>
)}
{s.env_params.map((k) => (
@@ -645,7 +645,7 @@ function McpCard() {
defaultValue={s.params[k] ?? ""}
onBlur={(e) => setParam(s, k, e.target.value)}
placeholder={k}
className="border-2 border-line bg-card px-2 py-1 text-[12px] text-ink"
className="border border-line bg-card px-2 py-1 text-[12px] text-ink"
/>
))}
</div>
@@ -685,7 +685,7 @@ function HardwareCard() {
<div className="font-pixel text-[11px] text-ink">MATÉRIEL</div>
<button
onClick={refresh}
className="flex h-7 items-center gap-1.5 border-2 border-line bg-card-soft px-2.5 text-[13px] text-muted-2"
className="flex h-7 items-center gap-1.5 border border-line bg-card-soft px-2.5 text-[13px] text-muted-2"
>
<RefreshIcon size={12} />
Rafraîchir
@@ -698,7 +698,7 @@ function HardwareCard() {
</div>
{hw?.loki_gpus.length ? (
hw.loki_gpus.map((g) => (
<div key={g.index} className="border-2 border-line bg-base px-3 py-2 text-[13px]">
<div key={g.index} className="border border-line bg-base px-3 py-2 text-[13px]">
<div className="flex items-center justify-between">
<span className="text-ink">{g.name}</span>
<span className="text-muted-2">
@@ -709,7 +709,7 @@ function HardwareCard() {
</div>
))
) : hw?.gpu_override ? (
<div className="border-2 border-line bg-base px-3 py-2 text-[13px] text-ink">
<div className="border border-line bg-base px-3 py-2 text-[13px] text-ink">
{hw.gpu_override.name} ·{" "}
{(hw.gpu_override.vram_total_mb / 1024).toFixed(0)} Go{" "}
<span className="text-muted-2">(valeur déclarée)</span>
@@ -719,7 +719,7 @@ function HardwareCard() {
</div>
</div>
) : (
<div className="border-2 border-line bg-base px-3 py-2 text-[12px] text-muted-2">
<div className="border border-line bg-base px-3 py-2 text-[12px] text-muted-2">
Aucun GPU visible depuis le conteneur — normal si Ollama tourne sur une
autre machine. Déclare <code>GPU_VRAM_MB</code> pour l'auto-réglage.
</div>
@@ -729,10 +729,10 @@ function HardwareCard() {
<div className="mb-1 mt-3 text-[12px] font-bold uppercase tracking-wide text-label">
Ollama {hw?.ollama_is_local ? "(local)" : "(distant)"}
</div>
<div className="border-2 border-line bg-base px-3 py-2 text-[13px]">
<div className="border border-line bg-base px-3 py-2 text-[13px]">
<div className="mb-1 flex items-center gap-2">
<span
className={`h-2 w-2 border-2 border-line ${
className={`h-2 w-2 border border-line ${
hw?.ollama.connected ? "bg-ok" : "bg-warn"
}`}
/>
@@ -820,7 +820,7 @@ function BenchCard() {
<button
onClick={launch}
disabled={running || !selectedModel}
className="flex h-8 items-center gap-1.5 border-[3px] border-line bg-accent px-3 text-[13px] text-white shadow-accent-soft disabled:opacity-40"
className="flex h-8 items-center gap-1.5 border border-line bg-accent px-3 text-[13px] text-white shadow-accent-soft disabled:opacity-40"
style={{ borderRadius: 7 }}
>
{running ? "ÉVALUATION…" : "TESTER CE MODÈLE"}
@@ -833,7 +833,7 @@ function BenchCard() {
</div>
{running && (
<div className="border-2 border-line bg-base px-3 py-2">
<div className="border border-line bg-base px-3 py-2">
{progress.map((p) => (
<div key={p.task} className="flex items-center gap-2 py-[2px] text-[13px]">
<span className="flex-1 text-ink-2">{p.task}</span>
@@ -850,13 +850,13 @@ function BenchCard() {
)}
{error && (
<div className="border-2 border-warn bg-base px-3 py-2 text-[12px] text-warn">
<div className="border border-warn bg-base px-3 py-2 text-[12px] text-warn">
Échec du test : {error}
</div>
)}
{!running && current && (
<div className="border-2 border-line bg-base px-3 py-2">
<div className="border border-line bg-base px-3 py-2">
<div className="mb-1 flex items-center justify-between">
<span className="text-[13px] text-ink">Score</span>
<span
@@ -898,7 +898,7 @@ function Card({
}) {
return (
<div
className={`border-[3px] border-line bg-card p-4 shadow-hard-lg ${className}`}
className={`border border-line bg-card p-4 shadow-hard-lg ${className}`}
style={{ borderRadius: 9 }}
>
{children}
@@ -911,7 +911,7 @@ function Toggle({ on, onClick }: { on: boolean; onClick: () => void }) {
<button
onClick={onClick}
className={`relative h-5 w-[38px] flex-none ${
on ? "bg-card-deep" : "border-2 border-line bg-base"
on ? "bg-card-deep" : "border border-line bg-base"
}`}
>
<span
@@ -968,7 +968,7 @@ function PresetBar({
value=""
disabled={busy || presets.length === 0}
onChange={(e) => apply(e.target.value)}
className="h-[34px] border-[3px] border-line bg-card px-2 text-[13px] text-ink disabled:opacity-40"
className="h-[34px] border border-line bg-card px-2 text-[13px] text-ink disabled:opacity-40"
title="Appliquer un preset"
>
<option value="">
@@ -983,7 +983,7 @@ function PresetBar({
<button
onClick={create}
disabled={busy}
className="h-[34px] border-[3px] border-line bg-card px-2.5 text-[13px] text-muted-2 disabled:opacity-40"
className="h-[34px] border border-line bg-card px-2.5 text-[13px] text-muted-2 disabled:opacity-40"
title="Enregistrer les réglages actuels comme preset"
>
+ Preset
@@ -1005,7 +1005,7 @@ function Advanced({
label?: string;
}) {
return (
<details className="group mt-3 border-t-2 border-line-soft pt-3">
<details className="group mt-3 border-t border-line-soft pt-3">
<summary className="flex cursor-pointer select-none items-center gap-1.5 text-[13px] text-muted-2 marker:content-['']">
<span className="transition-transform group-open:rotate-90">▸</span>
{label}
@@ -1032,7 +1032,7 @@ function ToggleRow({
return (
<div
className={`flex items-center gap-3 py-2 ${
first ? "" : "border-t-2 border-line-soft"
first ? "" : "border-t border-line-soft"
}`}
>
<span className="flex-1">
@@ -1070,13 +1070,13 @@ function Slider({
<span className="text-[13px] text-ink-2">{label}</span>
<span className="text-[13px] text-accent">{fmt(value)}</span>
</div>
<div className="relative h-[14px] border-2 border-line bg-card">
<div className="relative h-[14px] border border-line bg-card">
<div
className="absolute left-0 top-0 bottom-0 bg-line"
style={{ width: `${pct}%` }}
/>
<div
className="absolute top-1/2 h-5 w-4 -translate-x-1/2 -translate-y-1/2 border-[3px] border-line bg-accent"
className="absolute top-1/2 h-5 w-4 -translate-x-1/2 -translate-y-1/2 border border-line bg-accent"
style={{ left: `${pct}%` }}
/>
<input
+63 -47
View File
@@ -1,9 +1,17 @@
import type { Config } from "tailwindcss";
/**
* Système de design « Loki » — thème néo-brutaliste clair.
* Bordures noires épaisses, ombres dures décalées, accent orange,
* police pixel (Press Start 2P) pour les libellés, DM Mono pour le corps.
* Système de design « Loki » — thème Nocturne clair (Claude Design).
*
* Reprend les jetons de `Loki App.dc.html` : fond gris froid, surfaces
* blanches, filets 1 px, accent bleu ardoise et ombres douces diffuses.
* Remplace le thème néo-brutaliste (bordures noires 3 px, ombres dures
* décalées, accent orange, police pixel).
*
* Les NOMS de jetons sont inchangés : seule leur valeur bascule, ce qui fait
* suivre toute l'interface sans réécrire les composants. Les jetons
* historiquement « sombres » (card-deep, on-dark…) deviennent la surface
* sélectionnée claire et son encre, conformément à la maquette.
*/
export default {
content: ["./index.html", "./src/**/*.{ts,tsx}"],
@@ -11,60 +19,68 @@ export default {
extend: {
colors: {
// Fonds
base: "#ececef", // contenu principal
bar: "#18181b", // chrome sombre (barres)
panel: "#fafafa", // panneaux latéraux
sunken: "#18181b", // encarts sombres (code, invite système)
base: "#f7f8fa", // --color-bg
bar: "#ffffff", // barres : surface + filet (plus de chrome sombre)
panel: "#ffffff", // --color-surface
sunken: "#f1f3f6", // encarts (code, invite système) : neutral-900
// Surfaces / cartes
card: "#ffffff",
"card-soft": "#fafafa",
"card-deep": "#18181b",
// Bordures
line: "#18181b", // bordure noire signature
"line-soft": "#e4e4e7", // séparateur clair
"line-strong": "#18181b",
// Accent orange
accent: "#ff5436",
"accent-1": "#ff6b50",
"accent-2": "#e23e22",
// Statuts
ok: "#22c55e",
info: "#3b82f6",
warn: "#dc2626",
// Textes (sombres sur fond clair)
ink: "#18181b",
"ink-2": "#27272a",
"ink-3": "#3f3f46",
muted: "#52525b",
"muted-2": "#71717a",
"muted-3": "#a1a1aa",
"muted-4": "#a1a1aa",
label: "#52525b",
// Teintes claires utiles sur le chrome sombre
"chrome-2": "#27272a",
"chrome-3": "#3f3f46",
"on-dark": "#e4e4e7",
"on-dark-2": "#a1a1aa",
"on-dark-3": "#71717a",
"card-soft": "#f7f8fa",
"card-deep": "#eef2f9", // sélection : accent-900
// Filets
line: "#e3e6eb", // --color-divider
"line-soft": "#e6e9ee", // neutral-800
"line-strong": "#c3c9d2", // neutral-700
// Accent bleu ardoise
accent: "#2f4a7a", // --color-accent
"accent-1": "#4a6797", // accent-600
"accent-2": "#26406b", // accent-300
"accent-ghost": "#eef2f9", // accent-900 (fonds teintés)
"accent-line": "#b9c6dd", // accent-700 (bordure de sélection)
// Statuts, accordés à la palette
ok: "#2f7a5a",
info: "#2f4a7a",
warn: "#b3403a",
// Encres
ink: "#1c2536", // --color-text / neutral-100
"ink-2": "#29334a", // neutral-200
"ink-3": "#3d4a5e", // neutral-300
muted: "#55627a", // neutral-400
"muted-2": "#6b7686", // neutral-500
"muted-3": "#8b95a4", // neutral-600
"muted-4": "#8b95a4",
label: "#8b95a4", // couleur des « kickers »
// Anciennes teintes « sur fond sombre » -> encres sur surface claire
"chrome-2": "#e3e6eb",
"chrome-3": "#c3c9d2",
"on-dark": "#1c2536",
"on-dark-2": "#3d4a5e",
"on-dark-3": "#6b7686",
},
fontFamily: {
sans: ['"DM Mono"', "ui-monospace", "monospace"],
mono: ['"DM Mono"', "ui-monospace", "monospace"],
pixel: ['"Press Start 2P"', "ui-monospace", "monospace"],
sans: ['"Inter"', "ui-sans-serif", "system-ui", "sans-serif"],
mono: ["ui-monospace", '"SF Mono"', "Menlo", "monospace"],
// Ex-police pixel : devient le « kicker » de la maquette — petites
// capitales très espacées. Les libellés existants suivent sans edit.
pixel: [
'"Inter"',
{ letterSpacing: "0.14em", fontWeight: "500" },
],
},
borderRadius: {
card: "9px",
card: "10px", // --radius-md
},
boxShadow: {
"hard-sm": "3px 3px 0 #18181b",
hard: "4px 4px 0 #18181b",
"hard-lg": "7px 7px 0 #18181b",
"hard-accent": "3px 3px 0 #ff5436",
"accent-soft": "3px 3px 0 rgba(255,84,54,.4)",
frame: "4px 4px 0 #18181b",
// Ombres douces diffuses (--shadow-sm/md/lg) au lieu des ombres dures.
"hard-sm": "0 1px 2px rgba(28, 37, 54, 0.06)",
hard: "0 2px 8px rgba(28, 37, 54, 0.08)",
"hard-lg": "0 8px 28px rgba(28, 37, 54, 0.10)",
"hard-accent": "0 2px 8px rgba(47, 74, 122, 0.18)",
"accent-soft": "0 1px 2px rgba(47, 74, 122, 0.14)",
frame: "0 2px 8px rgba(28, 37, 54, 0.08)",
},
backgroundImage: {
"accent-grad": "linear-gradient(140deg, #ff6b50, #e23e22)",
"accent-grad": "linear-gradient(140deg, #4a6797, #2f4a7a)",
},
},
},