feat(site,docs): aligne le site vitrine et la doc sur l'app « Emerald »
Site (packages/site) : adopte le design system Emerald (polices Inter + JetBrains Mono variables, tokens sémantiques, thème clair/sombre + bascule) et met les mockups à jour sur l'app réelle. - style.css : tokens Emerald (@theme sombre + override html[data-theme=light]) ; lib/theme.ts + ThemeToggle.vue + anti-FOUC index.html ; sweep des couleurs brutes vers les tokens dans toutes les sections. - Mockups fidèles à l'IDE multi-projet : HeroMockup (rail ActivityBar au lieu de la sidebar large + v obsolète retirée), ShowcaseSection (supervision au lieu du dashboard worktree-first, onglets mobiles réels), WorkspaceShowcase (diff-add/del, bouton commit accent-solid) ; framing « worktree-first » résiduel levé (i18n). - og-cover.png rafraîchi (carte Emerald). Docs : README.md + README.fr.md (retrait « Status: MVP », ajout du langage Emerald + thème clair/sombre/système, VSIX 0.2.0 -> 0.3.0, section Screenshots), vscode README (VSIX 0.3.0), brand README (thème clair), CHANGELOG daemon 3.2.0. Captures IDE sombre + clair (brand/screenshot-ide-*.png).
This commit is contained in:
25
README.fr.md
25
README.fr.md
@@ -10,12 +10,28 @@
|
||||
<a href="README.md">English</a> · <strong>Français</strong>
|
||||
</p>
|
||||
|
||||
**Statut : MVP.** Le dashboard worktree-first, la découverte et la reprise de sessions, le cycle de vie des worktrees multi-repo, le démarrage de sessions sur votre branche principale ou n'importe quel worktree, les états de session en temps réel, le terminal web, la supervision mobile (PWA installable, Web Push quand une session vous attend, répondre à une demande sans ouvrir de terminal) et les groupes de travail (piloter plusieurs repos liés depuis une seule session Claude) sont implémentés et testés.
|
||||
La découverte et la reprise de sessions, le cycle de vie des worktrees multi-repo, le démarrage de sessions sur votre branche principale ou n'importe quel worktree, les états de session en temps réel, le terminal web, la supervision mobile (PWA installable, Web Push quand une session vous attend, répondre à une demande sans ouvrir de terminal) et les groupes de travail (piloter plusieurs repos liés depuis une seule session Claude) sont tous implémentés et testés.
|
||||
|
||||
La dernière étape transforme Arboretum en véritable **IDE IA multi-projet** : un espace de travail unique (route `/ide`, la vue par défaut sur desktop) qui réunit **tous** vos projets ouverts à la fois, fini l'IDE par projet. Un arbre unique à gauche (projet, puis checkout principal et worktrees, puis session Claude), un éditeur Monaco à onglets au centre (plusieurs fichiers de projets différents côte à côte, diffs inline), les terminaux Claude en dock bas, et des panneaux Git / Sessions / Groupes via une barre d'activité. Il est fourni à la fois en **app de bureau native** (Linux, Windows, macOS) et en interface web, adossé à un watcher de système de fichiers en temps réel, au staging sélectif / discard / amend / fetch / pull, à l'archivage automatique des sessions, à la synchronisation des réglages en temps réel, et à des services git distants chiffrés (GitHub / GitLab / Gitea) avec clone HTTPS.
|
||||
|
||||
---
|
||||
|
||||
## Captures d'écran
|
||||
|
||||
<p align="center">
|
||||
<img src="brand/screenshot-ide-dark.png" alt="Arboretum, l'IDE IA multi-projet (thème sombre)" width="900">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<em>Un espace de travail pour tous les projets : arbre unifié, éditeur Monaco à onglets avec diffs inline, terminaux Claude en dock. Thèmes sombre et clair.</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="brand/screenshot-ide-light.png" alt="Arboretum, l'IDE IA multi-projet (thème clair)" width="900">
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## Le problème
|
||||
|
||||
Travailler avec des agents de code IA a changé notre usage de git : une feature = un worktree = une session Claude Code, plusieurs en parallèle. Mais l'outillage n'a pas suivi :
|
||||
@@ -28,7 +44,7 @@ Travailler avec des agents de code IA a changé notre usage de git : une feature
|
||||
|
||||
Un unique daemon Node.js que vous lancez sur votre machine de dev (en app de bureau native, ou via `npx @johanleroy/git-arboretum`), servant une interface utilisable depuis votre ordinateur, téléphone ou tablette :
|
||||
|
||||
- **Dashboard worktree-first, multi-repo** : chaque worktree de chaque repo enregistré, avec son état git (branche, ahead/behind, fichiers modifiés) *et* l'état de sa session Claude Code (busy / en attente d'entrée / idle / reprenable).
|
||||
- **Dashboard worktree multi-repo** : chaque worktree de chaque repo enregistré, avec son état git (branche, ahead/behind, fichiers modifiés) *et* l'état de sa session Claude Code (busy / en attente d'entrée / idle / reprenable).
|
||||
- **Cycle de vie complet des worktrees** : créer (avec des hooks post-création par repo : `npm ci`, copie de `.env`…), adopter des worktrees créés à la main, supprimer avec garde-fous, élaguer les orphelins.
|
||||
- **Découverte & reprise de sessions** : les sessions lancées dans votre propre terminal apparaissent automatiquement ; reprenez les sessions mortes, observez ou forkez les vivantes. Ne corrompt jamais une session vivante. Masquez les anciennes qui encombrent la liste (un clic efface tout l'historique externe ; elles restent reprenables).
|
||||
- **Terminal web** : terminal xterm.js complet vers chaque session managée, qui survit aux déconnexions du navigateur ; vraiment plein écran, avec l'invite ancrée en bas et tout l'historique défilable au-dessus.
|
||||
@@ -129,6 +145,7 @@ Au-delà de la supervision, Arboretum est un véritable **IDE multi-projet** (ro
|
||||
- **Dock de terminaux.** Les terminaux de session Claude vivent dans un dock bas, un onglet par session ; ouvrez-en plusieurs à la fois (à travers les projets), répondez à une demande en attente directement là. Les terminaux survivent aux changements d'onglet et aux déconnexions du navigateur.
|
||||
- **Panneau Git.** Indexez ou désindexez des fichiers individuels, jetez des changements, écrivez un message et **committez** (tout ou seulement l'indexé), **amendez** le dernier commit (refusé une fois poussé), puis **fetch**, **pull** (fast-forward ou rebase) et **push**. Les compteurs indexés / non indexés / conflits détaillés et le dernier commit restent à jour via un **watcher de système de fichiers en temps réel** : la vue s'actualise dès qu'une session Claude touche un fichier.
|
||||
- **Deep links & mobile.** `/workspace/:repoId/:wt` ouvre toujours un worktree précis directement dans l'IDE (utilisé par l'extension VS Code). Sur mobile, l'IDE dégrade en panneau unique, et le dashboard des worktrees et les vues de session restent la PWA de supervision installable.
|
||||
- **Clair ou sombre, au choix.** Un langage visuel épuré et cohérent (le design system « Emerald » : Inter + JetBrains Mono, un accent emerald) avec une bascule de thème **clair / sombre / système** mémorisée d'une session à l'autre. L'éditeur Monaco, les terminaux et les diffs suivent tous le thème actif.
|
||||
|
||||
Comme toute action git dans Arboretum, ces opérations s'exécutent **en tant que vous** (le daemon tourne sous votre compte), cohérent avec le modèle de sécurité : un terminal web, c'est de l'exécution de code à distance par conception.
|
||||
|
||||
@@ -146,10 +163,10 @@ Elle est distribuée en **VSIX privé**. Buildez-la et packagez-la depuis le mon
|
||||
|
||||
```bash
|
||||
npm run build:vscode
|
||||
cd packages/vscode && npx @vscode/vsce package --no-dependencies # → git-arboretum-0.2.0.vsix
|
||||
cd packages/vscode && npx @vscode/vsce package --no-dependencies # → git-arboretum-0.3.0.vsix
|
||||
```
|
||||
|
||||
Puis installez-la via **Extensions : Installer à partir d'un VSIX…** (ou `code --install-extension git-arboretum-0.2.0.vsix`), lancez **Arboretum: Sign In** et collez un token. Détails complets dans [`packages/vscode/README.md`](packages/vscode/README.md).
|
||||
Puis installez-la via **Extensions : Installer à partir d'un VSIX…** (ou `code --install-extension git-arboretum-0.3.0.vsix`), lancez **Arboretum: Sign In** et collez un token. Détails complets dans [`packages/vscode/README.md`](packages/vscode/README.md).
|
||||
|
||||
## Accès distant depuis votre téléphone
|
||||
|
||||
|
||||
25
README.md
25
README.md
@@ -10,12 +10,28 @@
|
||||
<strong>English</strong> · <a href="README.fr.md">Français</a>
|
||||
</p>
|
||||
|
||||
**Status: MVP.** The worktree-first dashboard, session discovery & resume, multi-repo worktree lifecycle, sessions on your main branch or any worktree, live session states, the web terminal, mobile supervision (installable PWA, Web Push when a session needs you, answer a prompt without opening a terminal), and work groups (drive several related repos from a single Claude session) are implemented and tested.
|
||||
Session discovery & resume, multi-repo worktree lifecycle, sessions on your main branch or any worktree, live session states, the web terminal, mobile supervision (installable PWA, Web Push when a session needs you, answer a prompt without opening a terminal), and work groups (drive several related repos from a single Claude session) are all implemented and tested.
|
||||
|
||||
The latest milestone turns Arboretum into a real **multi-project AI IDE**: a single workspace (route `/ide`, the default view on desktop) that holds **all** your open projects at once, no more one IDE window per project. A single left tree (project, then main checkout and worktrees, then Claude session), a tabbed Monaco editor at the center (several files from different projects side by side, inline diffs), Claude terminals in a bottom dock, and Git / Sessions / Groups panels via an activity bar. It ships both as a **native desktop app** (Linux, Windows, macOS) and the same web UI, backed by a real-time file-system watcher, selective staging / discard / amend / fetch / pull, automatic session archival, real-time settings sync, and encrypted remote git services (GitHub / GitLab / Gitea) with HTTPS clone.
|
||||
|
||||
---
|
||||
|
||||
## Screenshots
|
||||
|
||||
<p align="center">
|
||||
<img src="brand/screenshot-ide-dark.png" alt="Arboretum, the multi-project AI IDE (dark theme)" width="900">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<em>One workspace for every project: a unified tree, a tabbed Monaco editor with inline diffs, and docked Claude terminals. Dark and light themes.</em>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="brand/screenshot-ide-light.png" alt="Arboretum, the multi-project AI IDE (light theme)" width="900">
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## The problem
|
||||
|
||||
Working with AI coding agents changed how we use git: one feature = one worktree = one Claude Code session, several of them in parallel. But the tooling didn't follow:
|
||||
@@ -28,7 +44,7 @@ Working with AI coding agents changed how we use git: one feature = one worktree
|
||||
|
||||
A single Node.js daemon you run on your dev machine (as a native desktop app, or via `npx @johanleroy/git-arboretum`), serving a UI usable from your desktop, phone or tablet:
|
||||
|
||||
- **Worktree-first, multi-repo dashboard**: every worktree of every registered repo, with its git state (branch, ahead/behind, dirty files) *and* the state of its Claude Code session (busy / waiting for input / idle / resumable).
|
||||
- **Multi-repo worktree dashboard**: every worktree of every registered repo, with its git state (branch, ahead/behind, dirty files) *and* the state of its Claude Code session (busy / waiting for input / idle / resumable).
|
||||
- **Full worktree lifecycle**: create (with per-repo post-create hooks: `npm ci`, copy `.env`…), adopt worktrees created by hand, delete with guardrails, prune orphans.
|
||||
- **Session discovery & resume**: sessions you launched in your own terminal show up automatically; resume dead ones, observe or fork live ones. Never corrupts a live session. Hide the old ones that clutter the list (one click clears the whole external history; they stay resumable).
|
||||
- **Web terminal**: full xterm.js terminal to every managed session, surviving browser disconnects; truly fullscreen, with the prompt pinned to the bottom and full scrollback above.
|
||||
@@ -129,6 +145,7 @@ Beyond supervising, Arboretum is a full **multi-project IDE** (route `/ide`, the
|
||||
- **Terminal dock.** Claude session terminals live in a bottom dock with one tab per session; open several at once (across projects), answer a waiting prompt right there. Terminals survive tab switches and browser disconnects.
|
||||
- **Git panel.** Stage or unstage individual files, discard changes, write a message and **commit** (everything or just what's staged), **amend** the last commit (refused once pushed), then **fetch**, **pull** (fast-forward or rebase) and **push**. Detailed staged / unstaged / conflict counts and the last commit stay current via a **real-time file-system watcher**, so the view updates the moment a Claude session touches a file.
|
||||
- **Deep links & mobile.** `/workspace/:repoId/:wt` still opens a specific worktree straight in the IDE (used by the VS Code extension). On mobile the IDE degrades to a single panel, and the worktree dashboard and session views remain the installable supervision PWA.
|
||||
- **Light or dark, your call.** A clean, consistent visual language (the "Emerald" design system: Inter + JetBrains Mono, an emerald accent) with a **light / dark / system** theme toggle remembered across sessions. The Monaco editor, the terminals and the diffs all follow the active theme.
|
||||
|
||||
Like every git action in Arboretum, these run **as you** (the daemon runs under your account), consistent with the security model: a web terminal is remote code execution by design.
|
||||
|
||||
@@ -146,10 +163,10 @@ It is distributed as a **private VSIX**. Build and package it from the monorepo:
|
||||
|
||||
```bash
|
||||
npm run build:vscode
|
||||
cd packages/vscode && npx @vscode/vsce package --no-dependencies # → git-arboretum-0.2.0.vsix
|
||||
cd packages/vscode && npx @vscode/vsce package --no-dependencies # → git-arboretum-0.3.0.vsix
|
||||
```
|
||||
|
||||
Then install it via **Extensions: Install from VSIX…** (or `code --install-extension git-arboretum-0.2.0.vsix`), run **Arboretum: Sign In** and paste a token. Full details in [`packages/vscode/README.md`](packages/vscode/README.md).
|
||||
Then install it via **Extensions: Install from VSIX…** (or `code --install-extension git-arboretum-0.3.0.vsix`), run **Arboretum: Sign In** and paste a token. Full details in [`packages/vscode/README.md`](packages/vscode/README.md).
|
||||
|
||||
## Remote access from your phone
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ branches, cyan session nodes, a `>_` prompt at the base) on a dark background.
|
||||
| --- | --- |
|
||||
| `arboretum-logo-source.png` | Master artwork (opaque dark background). Keep; everything else derives from it. |
|
||||
| `arboretum-logo.png` | Full logo, **transparent**. Best on dark surfaces (the wordmark is light). |
|
||||
| `arboretum-logo-on-dark.png` | Full logo on the app background `#09090b`. Safe on any theme, used in the README. |
|
||||
| `arboretum-logo-on-dark.png` | Full logo on a dark backdrop (`#09090b`, the app's dark theme; the UI now also ships a light theme on `#fafafa`). Safe on any theme, used in the README. |
|
||||
| `arboretum-mark.png` | Square, **transparent**, tree only (no wordmark). Ideal **Gitea repo avatar**, reads on both light and dark. |
|
||||
|
||||
The transparent versions are extracted by luminance (alpha ∝ brightness), the clean
|
||||
|
||||
BIN
brand/screenshot-ide-dark.png
Normal file
BIN
brand/screenshot-ide-dark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 113 KiB |
BIN
brand/screenshot-ide-light.png
Normal file
BIN
brand/screenshot-ide-light.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 113 KiB |
15
package-lock.json
generated
15
package-lock.json
generated
@@ -998,16 +998,6 @@
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
},
|
||||
"node_modules/@fontsource/jetbrains-mono": {
|
||||
"version": "5.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.2.8.tgz",
|
||||
"integrity": "sha512-6w8/SG4kqvIMu7xd7wt6x3idn1Qux3p9N62s6G3rfldOUYHpWcc2FKrqf+Vo44jRvqWj2oAtTHrZXEP23oSKwQ==",
|
||||
"dev": true,
|
||||
"license": "OFL-1.1",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
},
|
||||
"node_modules/@homebridge/node-pty-prebuilt-multiarch": {
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/@homebridge/node-pty-prebuilt-multiarch/-/node-pty-prebuilt-multiarch-0.13.1.tgz",
|
||||
@@ -7977,13 +7967,14 @@
|
||||
},
|
||||
"packages/site": {
|
||||
"name": "@arboretum/site",
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"dependencies": {
|
||||
"vue": "^3.5.38",
|
||||
"vue-i18n": "^11.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fontsource/jetbrains-mono": "^5.1.0",
|
||||
"@fontsource-variable/inter": "^5.0.0",
|
||||
"@fontsource-variable/jetbrains-mono": "^5.0.0",
|
||||
"@tailwindcss/vite": "^4.3.0",
|
||||
"@vitejs/plugin-vue": "^6.0.7",
|
||||
"tailwindcss": "^4.3.0",
|
||||
|
||||
25
packages/server/CHANGELOG.md
Normal file
25
packages/server/CHANGELOG.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Changelog
|
||||
|
||||
Notable changes to `@johanleroy/git-arboretum` (the Arboretum daemon). The VS Code
|
||||
extension keeps its own changelog in `packages/vscode/CHANGELOG.md`.
|
||||
|
||||
## 3.2.0
|
||||
|
||||
Visual overhaul: the web UI adopts the "Emerald" design system and gains a full theme system. No protocol or API change (fully additive, backward compatible).
|
||||
|
||||
- **Light and dark themes.** A `light` / `dark` / `system` toggle (in the activity bar and in Settings), remembered across sessions and applied before the first paint (no flash of the wrong theme). The Monaco editor, the xterm terminals (now with a full 16-color ANSI palette) and the diffs all re-theme live.
|
||||
- **Emerald design language.** Self-hosted Inter + JetBrains Mono, an emerald accent, consistent rounded corners, mono status pills and dots, refined focus and text selection, thin scrollbars, and a terminal signature motif.
|
||||
- **Marketing site aligned.** git-arboretum.com now mirrors the app: the same tokens, fonts and theme toggle, with up-to-date IDE mockups.
|
||||
|
||||
## 3.1.0
|
||||
|
||||
- The IDE becomes the single shell of the web UI; the legacy worktree-first dashboard is retired as the default surface (still reachable at `/dashboard`).
|
||||
- Desktop app metadata fixes (window logo on Debian / Wayland).
|
||||
|
||||
## 3.0.0
|
||||
|
||||
- **Multi-project IDE** milestone: one workspace (route `/ide`, the default on desktop) holding every open project at once, a unified project / worktree / session tree, a tabbed Monaco editor with inline diffs, docked session terminals, and Git / Sessions / Groups panels. Ships as a **native Electron desktop app** (Linux, Windows, macOS) alongside the same web UI.
|
||||
|
||||
## 2.0.0
|
||||
|
||||
- "Real worktree IDE" foundations: a pure git engine, a real-time file-system watcher, the file API, selective staging / discard / amend / fetch / pull, automatic session archival, real-time settings sync, and encrypted remote git services (GitHub / GitLab / Gitea) with HTTPS clone.
|
||||
@@ -5,6 +5,25 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="color-scheme" content="dark" />
|
||||
<meta name="theme-color" content="#09090b" />
|
||||
<!-- Anti-FOUC : applique la préférence de thème (arb-theme) avant le premier paint.
|
||||
Doit rester inline/synchrone. Synchronisé avec src/lib/theme.ts. -->
|
||||
<script>
|
||||
(function () {
|
||||
try {
|
||||
var mode = localStorage.getItem('arb-theme') || 'dark';
|
||||
var dark = mode === 'dark' || (mode === 'system' && matchMedia('(prefers-color-scheme: dark)').matches);
|
||||
var theme = dark ? 'dark' : 'light';
|
||||
var bg = dark ? '#09090b' : '#fafafa';
|
||||
var el = document.documentElement;
|
||||
el.dataset.theme = theme;
|
||||
el.style.backgroundColor = bg;
|
||||
var cs = document.querySelector('meta[name=color-scheme]');
|
||||
if (cs) cs.setAttribute('content', theme);
|
||||
var tc = document.querySelector('meta[name=theme-color]');
|
||||
if (tc) tc.setAttribute('content', bg);
|
||||
} catch (e) {}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<title>Arboretum · Mission control for your AI coding agents</title>
|
||||
<meta
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@arboretum/site",
|
||||
"private": true,
|
||||
"version": "0.2.0",
|
||||
"version": "0.3.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -13,7 +13,8 @@
|
||||
"vue-i18n": "^11.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@fontsource/jetbrains-mono": "^5.1.0",
|
||||
"@fontsource-variable/inter": "^5.0.0",
|
||||
"@fontsource-variable/jetbrains-mono": "^5.0.0",
|
||||
"@tailwindcss/vite": "^4.3.0",
|
||||
"@vitejs/plugin-vue": "^6.0.7",
|
||||
"tailwindcss": "^4.3.0",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 48 KiB |
@@ -27,13 +27,14 @@ watch(
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
// Grain de fond + halo emerald (décoratifs) : portés tels quels du design.
|
||||
// Grain de fond + halo accent (décoratifs), tokenisés pour suivre le thème clair/sombre.
|
||||
const grainStyle = {
|
||||
position: 'fixed',
|
||||
inset: '0',
|
||||
zIndex: 0,
|
||||
opacity: '0.025',
|
||||
pointerEvents: 'none',
|
||||
backgroundImage: 'radial-gradient(rgba(255,255,255,.025) 1px,transparent 1.4px)',
|
||||
backgroundImage: 'radial-gradient(var(--color-fg) 1px,transparent 1.4px)',
|
||||
backgroundSize: '34px 34px',
|
||||
WebkitMaskImage: 'radial-gradient(ellipse 120% 70% at 50% -4%,#000,transparent 70%)',
|
||||
maskImage: 'radial-gradient(ellipse 120% 70% at 50% -4%,#000,transparent 70%)',
|
||||
@@ -47,8 +48,9 @@ const glowStyle = {
|
||||
width: '1100px',
|
||||
height: '680px',
|
||||
zIndex: 0,
|
||||
opacity: '0.12',
|
||||
pointerEvents: 'none',
|
||||
background: 'radial-gradient(50% 50% at 50% 50%,rgba(16,185,129,.12),transparent 64%)',
|
||||
background: 'radial-gradient(50% 50% at 50% 50%,var(--color-accent),transparent 64%)',
|
||||
filter: 'blur(18px)',
|
||||
} as const;
|
||||
</script>
|
||||
|
||||
@@ -9,7 +9,7 @@ const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer class="relative z-[1] border-t border-[#1c1c1f]">
|
||||
<footer class="relative z-[1] border-t border-border-soft">
|
||||
<div class="mx-auto flex max-w-[1200px] flex-wrap items-center justify-between gap-5 px-6 py-[34px]">
|
||||
<div class="flex items-center gap-[11px]">
|
||||
<img
|
||||
@@ -20,28 +20,28 @@ const { t } = useI18n();
|
||||
class="h-[22px] w-[22px] object-contain"
|
||||
style="filter: grayscale(1) brightness(1.5); opacity: 0.4"
|
||||
/>
|
||||
<span class="font-mono text-sm text-zinc-300">git-arboretum.com</span>
|
||||
<span class="ml-1 text-[13px] text-zinc-600">· {{ t('footTag') }}</span>
|
||||
<span class="font-mono text-sm text-fg-muted">git-arboretum.com</span>
|
||||
<span class="ml-1 text-[13px] text-fg-subtle">· {{ t('footTag') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-[22px] text-sm">
|
||||
<a :href="REPO" target="_blank" rel="noopener" class="inline-flex items-center gap-1.5 text-zinc-400 no-underline transition-colors hover:text-emerald-400">
|
||||
<a :href="REPO" target="_blank" rel="noopener" class="inline-flex items-center gap-1.5 text-fg-muted no-underline transition-colors hover:text-accent">
|
||||
<IconGitea :size="15" />
|
||||
Gitea
|
||||
</a>
|
||||
<a :href="LICENSE" target="_blank" rel="noopener" class="text-zinc-400 no-underline transition-colors hover:text-emerald-400">
|
||||
<a :href="LICENSE" target="_blank" rel="noopener" class="text-fg-muted no-underline transition-colors hover:text-accent">
|
||||
{{ t('license') }}
|
||||
</a>
|
||||
<a
|
||||
:href="COFFEE"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="inline-flex items-center gap-1.5 text-zinc-400 no-underline transition-colors hover:text-amber-400 focus-visible:rounded-md focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-amber-400/70"
|
||||
class="inline-flex items-center gap-1.5 text-fg-muted no-underline transition-colors hover:text-warn focus-visible:rounded-md focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-warn/70"
|
||||
>
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 2v2" /><path d="M14 2v2" /><path d="M5 8h13a1 1 0 0 1 1 1v2a4 4 0 0 1-4 4H8a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1Z" /><path d="M6 15a4 4 0 0 0 4 4h2a4 4 0 0 0 4-4" /><path d="M19 9h1.5a2.5 2.5 0 0 1 0 5H18" /></svg>
|
||||
{{ t('coffee') }}
|
||||
</a>
|
||||
<div class="flex items-center gap-2">
|
||||
<code class="font-mono text-[12.5px] text-zinc-600">{{ INSTALL_COMMAND }}</code>
|
||||
<code class="font-mono text-[12.5px] text-fg-subtle">{{ INSTALL_COMMAND }}</code>
|
||||
<CopyButton variant="icon" :text="INSTALL_COMMAND" :label="t('copyCommand')" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { REPO } from '../lib/links';
|
||||
import LangToggle from './LangToggle.vue';
|
||||
import ThemeToggle from './ThemeToggle.vue';
|
||||
import IconGitea from './icons/IconGitea.vue';
|
||||
|
||||
const { t } = useI18n();
|
||||
@@ -18,10 +19,10 @@ const navLinks = [
|
||||
|
||||
<template>
|
||||
<header
|
||||
class="sticky top-0 z-50 border-b border-[#1c1c1f] bg-[rgba(9,9,11,0.72)] backdrop-blur-[14px]"
|
||||
class="sticky top-0 z-50 border-b border-border-soft bg-surface-0/72 backdrop-blur-[14px]"
|
||||
>
|
||||
<div class="mx-auto flex h-16 max-w-[1200px] items-center justify-between gap-6 px-6">
|
||||
<a href="#top" class="flex items-center gap-2.5 text-zinc-100 no-underline">
|
||||
<a href="#top" class="flex items-center gap-2.5 text-fg no-underline">
|
||||
<img
|
||||
src="/assets/arboretum-mark.png"
|
||||
alt="Arboretum"
|
||||
@@ -37,20 +38,21 @@ const navLinks = [
|
||||
v-for="link in navLinks"
|
||||
:key="link.href"
|
||||
:href="link.href"
|
||||
class="text-[14.5px] text-zinc-400 no-underline transition-colors hover:text-zinc-100"
|
||||
class="text-[14.5px] text-fg-muted no-underline transition-colors hover:text-fg"
|
||||
>
|
||||
{{ t(link.key) }}
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="flex items-center gap-3.5">
|
||||
<ThemeToggle />
|
||||
<LangToggle />
|
||||
<a
|
||||
:href="REPO"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
aria-label="Gitea"
|
||||
class="inline-flex items-center gap-[7px] rounded-lg border border-zinc-800 px-[13px] py-[7px] text-[13.5px] font-medium text-zinc-300 no-underline transition-colors hover:border-emerald-500/50 hover:text-emerald-400 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-emerald-500/70"
|
||||
class="inline-flex items-center gap-[7px] rounded-lg border border-border px-[13px] py-[7px] text-[13.5px] font-medium text-fg-muted no-underline transition-colors hover:border-accent hover:text-accent"
|
||||
>
|
||||
<IconGitea :size="16" />
|
||||
Gitea
|
||||
|
||||
@@ -28,9 +28,9 @@ const { copied, copy } = useCopy();
|
||||
type="button"
|
||||
:aria-label="label || t('copy')"
|
||||
:class="[
|
||||
'inline-flex flex-none items-center gap-[7px] border-l border-zinc-800 bg-zinc-900 font-mono text-[13px] font-semibold transition-colors hover:bg-zinc-800 hover:text-emerald-400 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-emerald-500/70',
|
||||
'inline-flex flex-none items-center gap-[7px] border-l border-border bg-surface-1 font-mono text-[13px] font-semibold transition-colors hover:bg-surface-2 hover:text-accent focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-accent/70',
|
||||
barClass,
|
||||
copied ? 'text-emerald-400' : 'text-zinc-300',
|
||||
copied ? 'text-accent' : 'text-fg-muted',
|
||||
]"
|
||||
@click="copy(text)"
|
||||
>
|
||||
@@ -44,10 +44,10 @@ const { copied, copy } = useCopy();
|
||||
:aria-label="label || t('copy')"
|
||||
:title="copied ? t('copied') : label || t('copy')"
|
||||
:class="[
|
||||
'inline-flex flex-none items-center justify-center rounded-md border p-1.5 transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-emerald-500/70',
|
||||
'inline-flex flex-none items-center justify-center rounded-md border p-1.5 transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent/70',
|
||||
copied
|
||||
? 'border-emerald-500/40 bg-zinc-800 text-emerald-400'
|
||||
: 'border-zinc-800 bg-zinc-900/80 text-zinc-400 hover:border-emerald-500/40 hover:bg-zinc-800 hover:text-emerald-400',
|
||||
? 'border-accent/40 bg-surface-2 text-accent'
|
||||
: 'border-border bg-surface-1/80 text-fg-muted hover:border-accent/40 hover:bg-surface-2 hover:text-accent',
|
||||
]"
|
||||
@click="copy(text)"
|
||||
>
|
||||
|
||||
@@ -16,30 +16,30 @@ const bullets = ['dlNoNode', 'dlBundled', 'dlAutoUpdate'] as const;
|
||||
<template>
|
||||
<section id="download" class="mx-auto max-w-[1200px] scroll-mt-[84px] px-6 pb-[100px]">
|
||||
<div v-reveal class="mb-[40px] max-w-[720px]">
|
||||
<div class="mb-3 font-mono text-xs uppercase tracking-[0.12em] text-emerald-400">{{ t('dlKicker') }}</div>
|
||||
<h2 class="m-0 mb-4 text-[clamp(26px,3vw,38px)] font-semibold leading-[1.15] tracking-[-0.025em] text-zinc-50">
|
||||
<div class="mb-3 font-mono text-xs uppercase tracking-[0.12em] text-accent">{{ t('dlKicker') }}</div>
|
||||
<h2 class="m-0 mb-4 text-[clamp(26px,3vw,38px)] font-semibold leading-[1.15] tracking-[-0.025em] text-fg">
|
||||
{{ t('dlTitle') }}
|
||||
</h2>
|
||||
<p class="m-0 text-[16.5px] leading-[1.6] text-zinc-400">{{ t('dlBody') }}</p>
|
||||
<p class="m-0 text-[16.5px] leading-[1.6] text-fg-muted">{{ t('dlBody') }}</p>
|
||||
</div>
|
||||
|
||||
<div v-reveal class="grid gap-4 md:grid-cols-3">
|
||||
<div
|
||||
v-for="p in platforms"
|
||||
:key="p.key"
|
||||
class="flex flex-col gap-1 rounded-[14px] border border-zinc-800 bg-[#09090b] p-5 shadow-card"
|
||||
class="flex flex-col gap-1 rounded-[14px] border border-border bg-surface-0 p-5 shadow-card"
|
||||
>
|
||||
<div class="flex items-center gap-2 text-zinc-100">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-emerald-400" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" x2="12" y1="15" y2="3" /></svg>
|
||||
<div class="flex items-center gap-2 text-fg">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-accent" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" x2="12" y1="15" y2="3" /></svg>
|
||||
<span class="text-[15px] font-semibold">{{ t(p.key) }}</span>
|
||||
</div>
|
||||
<span class="pl-[26px] font-mono text-[12.5px] text-zinc-500">{{ t(p.hint) }}</span>
|
||||
<span class="pl-[26px] font-mono text-[12.5px] text-fg-subtle">{{ t(p.hint) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul v-reveal class="mt-6 flex flex-wrap gap-x-6 gap-y-2">
|
||||
<li v-for="b in bullets" :key="b" class="flex items-center gap-2 text-[14px] text-zinc-300">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" class="text-emerald-400" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg>
|
||||
<li v-for="b in bullets" :key="b" class="flex items-center gap-2 text-[14px] text-fg-muted">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" class="text-accent" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg>
|
||||
{{ t(b) }}
|
||||
</li>
|
||||
</ul>
|
||||
@@ -49,7 +49,7 @@ const bullets = ['dlNoNode', 'dlBundled', 'dlAutoUpdate'] as const;
|
||||
:href="RELEASES"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="inline-flex items-center gap-2 rounded-lg bg-emerald-500 px-4 py-2.5 text-[14.5px] font-semibold text-emerald-950 no-underline transition-colors hover:bg-emerald-400 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-emerald-500/70"
|
||||
class="inline-flex items-center gap-2 rounded-lg bg-accent-solid px-4 py-2.5 text-[14.5px] font-semibold text-white no-underline transition-colors hover:bg-accent-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent/70"
|
||||
>
|
||||
{{ t('dlGet') }}
|
||||
</a>
|
||||
@@ -57,12 +57,12 @@ const bullets = ['dlNoNode', 'dlBundled', 'dlAutoUpdate'] as const;
|
||||
:href="DESKTOP_SRC"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="inline-flex items-center gap-2 rounded-lg border border-zinc-800 px-4 py-2.5 text-[14.5px] font-medium text-zinc-300 no-underline transition-colors hover:border-emerald-500/50 hover:text-emerald-400 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-emerald-500/70"
|
||||
class="inline-flex items-center gap-2 rounded-lg border border-border px-4 py-2.5 text-[14.5px] font-medium text-fg-muted no-underline transition-colors hover:border-accent/50 hover:text-accent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent/70"
|
||||
>
|
||||
{{ t('dlSource') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<p v-reveal class="mt-4 max-w-[720px] text-[13.5px] leading-[1.55] text-zinc-500">{{ t('dlNote') }}</p>
|
||||
<p v-reveal class="mt-4 max-w-[720px] text-[13.5px] leading-[1.55] text-fg-subtle">{{ t('dlNote') }}</p>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -25,26 +25,26 @@ function panelStyle(i: number) {
|
||||
<template>
|
||||
<section id="faq" class="mx-auto max-w-[780px] scroll-mt-[84px] px-6 pb-[100px]">
|
||||
<div v-reveal class="mb-[38px] text-center">
|
||||
<div class="mb-3.5 font-mono text-xs uppercase tracking-[0.14em] text-emerald-400">{{ t('navFaq') }}</div>
|
||||
<h2 class="m-0 text-[clamp(28px,3.4vw,40px)] font-semibold tracking-[-0.025em] text-zinc-50">{{ t('faqTitle') }}</h2>
|
||||
<div class="mb-3.5 font-mono text-xs uppercase tracking-[0.14em] text-accent">{{ t('navFaq') }}</div>
|
||||
<h2 class="m-0 text-[clamp(28px,3.4vw,40px)] font-semibold tracking-[-0.025em] text-fg">{{ t('faqTitle') }}</h2>
|
||||
</div>
|
||||
|
||||
<div v-reveal>
|
||||
<div v-for="(f, i) in items" :key="i" class="border-t" :class="open === i ? 'border-emerald-400/40' : 'border-zinc-800'">
|
||||
<div v-for="(f, i) in items" :key="i" class="border-t" :class="open === i ? 'border-accent/40' : 'border-border'">
|
||||
<button
|
||||
type="button"
|
||||
:aria-expanded="open === i"
|
||||
:aria-controls="`faq-panel-${i}`"
|
||||
class="flex w-full cursor-pointer items-center justify-between gap-4 border-none bg-transparent px-1 py-5 text-left text-[clamp(16px,2vw,18px)] font-medium text-zinc-50 focus-visible:rounded-md focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-emerald-500/70"
|
||||
class="flex w-full cursor-pointer items-center justify-between gap-4 border-none bg-transparent px-1 py-5 text-left text-[clamp(16px,2vw,18px)] font-medium text-fg focus-visible:rounded-md focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent"
|
||||
@click="toggle(i)"
|
||||
>
|
||||
<span>{{ f.q }}</span>
|
||||
<span class="flex flex-none text-emerald-400 transition-transform duration-300" :class="open === i ? 'rotate-180' : ''">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#34d399" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<span class="flex flex-none text-accent transition-transform duration-300" :class="open === i ? 'rotate-180' : ''">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--color-accent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</span>
|
||||
</button>
|
||||
<div :id="`faq-panel-${i}`" :style="panelStyle(i)" :inert="open !== i" :aria-hidden="open !== i">
|
||||
<p class="m-0 max-w-[640px] px-1 pb-5 text-[15.5px] leading-[1.6] text-zinc-400">{{ f.a }}</p>
|
||||
<p class="m-0 max-w-[640px] px-1 pb-5 text-[15.5px] leading-[1.6] text-fg-muted">{{ f.a }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,14 +4,14 @@ defineProps<{ title: string }>();
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="rounded-xl border border-zinc-800 bg-zinc-900/50 p-6 transition-[transform,border-color] duration-[180ms] ease-in-out hover:-translate-y-[3px] hover:border-emerald-400/40"
|
||||
class="rounded-xl border border-border bg-surface-1/50 p-6 transition-[transform,border-color] duration-[180ms] ease-in-out hover:-translate-y-[3px] hover:border-accent/40"
|
||||
>
|
||||
<div
|
||||
class="mb-4 inline-flex h-[42px] w-[42px] items-center justify-center rounded-[10px] bg-emerald-500/10 text-emerald-400"
|
||||
class="mb-4 inline-flex h-[42px] w-[42px] items-center justify-center rounded-[10px] bg-accent/10 text-accent"
|
||||
>
|
||||
<slot name="icon" />
|
||||
</div>
|
||||
<h3 class="m-0 mb-[7px] text-lg font-semibold text-zinc-50">{{ title }}</h3>
|
||||
<p class="m-0 text-[14.5px] leading-[1.55] text-zinc-400"><slot /></p>
|
||||
<h3 class="m-0 mb-[7px] text-lg font-semibold text-fg">{{ title }}</h3>
|
||||
<p class="m-0 text-[14.5px] leading-[1.55] text-fg-muted"><slot /></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -7,8 +7,8 @@ const { t } = useI18n();
|
||||
<template>
|
||||
<section id="features" class="mx-auto max-w-[1200px] scroll-mt-[84px] px-6 pb-[100px]">
|
||||
<div v-reveal class="mb-[46px] text-center">
|
||||
<div class="mb-3.5 font-mono text-xs uppercase tracking-[0.14em] text-emerald-400">{{ t('featKicker') }}</div>
|
||||
<h2 class="m-0 text-[clamp(28px,3.4vw,40px)] font-semibold tracking-[-0.025em] text-zinc-50">
|
||||
<div class="mb-3.5 font-mono text-xs uppercase tracking-[0.14em] text-accent">{{ t('featKicker') }}</div>
|
||||
<h2 class="m-0 text-[clamp(28px,3.4vw,40px)] font-semibold tracking-[-0.025em] text-fg">
|
||||
{{ t('featTitle') }}
|
||||
</h2>
|
||||
</div>
|
||||
@@ -32,9 +32,9 @@ const { t } = useI18n();
|
||||
<template #icon>
|
||||
<svg width="21" height="21" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 12h-4l-3 9L9 3l-3 9H2" /></svg>
|
||||
</template>
|
||||
<span class="text-sky-300">{{ t('busy') }}</span> ·
|
||||
<span class="text-amber-300">{{ t('waiting') }}</span> ·
|
||||
<span class="text-emerald-400">{{ t('idle') }}</span> · {{ t('feat3Desc') }}
|
||||
<span class="text-info">{{ t('busy') }}</span> ·
|
||||
<span class="text-warn">{{ t('waiting') }}</span> ·
|
||||
<span class="text-accent">{{ t('idle') }}</span> · {{ t('feat3Desc') }}
|
||||
</FeatureCard>
|
||||
|
||||
<FeatureCard :title="t('feat4Title')">
|
||||
|
||||
@@ -12,8 +12,8 @@ const { t } = useI18n();
|
||||
<section class="mx-auto max-w-[1200px] px-6 pb-[100px]">
|
||||
<div
|
||||
v-reveal
|
||||
class="rounded-[20px] border border-zinc-800 px-6 py-[clamp(36px,5vw,64px)] text-center"
|
||||
style="background: radial-gradient(120% 140% at 50% 0%, rgba(16, 185, 129, 0.1), transparent 60%), #0c0c0e"
|
||||
class="rounded-[20px] border border-border px-6 py-[clamp(36px,5vw,64px)] text-center"
|
||||
style="background: radial-gradient(120% 140% at 50% 0%, color-mix(in srgb, var(--color-accent) 10%, transparent), transparent 60%), var(--color-surface-0)"
|
||||
>
|
||||
<img
|
||||
src="/assets/arboretum-mark.png"
|
||||
@@ -22,16 +22,16 @@ const { t } = useI18n();
|
||||
height="52"
|
||||
class="mx-auto mb-5 block h-[52px] w-[52px] object-contain"
|
||||
/>
|
||||
<h2 class="m-0 mb-3.5 text-[clamp(30px,4vw,46px)] font-bold leading-[1.08] tracking-[-0.03em] text-zinc-50">
|
||||
<h2 class="m-0 mb-3.5 text-[clamp(30px,4vw,46px)] font-bold leading-[1.08] tracking-[-0.03em] text-fg">
|
||||
{{ t('ctaTitle') }}
|
||||
</h2>
|
||||
<p class="mx-auto mb-7 max-w-[480px] text-[17px] text-zinc-400">{{ t('ctaBody') }}</p>
|
||||
<p class="mx-auto mb-7 max-w-[480px] text-[17px] text-fg-muted">{{ t('ctaBody') }}</p>
|
||||
|
||||
<div
|
||||
class="mx-auto flex max-w-[460px] items-stretch overflow-hidden rounded-[10px] border border-zinc-800 bg-zinc-950"
|
||||
class="mx-auto flex max-w-[460px] items-stretch overflow-hidden rounded-[10px] border border-border bg-surface-0"
|
||||
>
|
||||
<div class="flex min-w-0 flex-1 items-center gap-2.5 overflow-x-auto whitespace-nowrap px-4 font-mono text-sm">
|
||||
<span class="text-emerald-400">$</span><span class="text-zinc-200">{{ INSTALL_COMMAND }}</span>
|
||||
<span class="text-accent">$</span><span class="text-fg">{{ INSTALL_COMMAND }}</span>
|
||||
</div>
|
||||
<CopyButton variant="bar" :text="INSTALL_COMMAND" :label="t('copyCommand')" bar-class="px-4" />
|
||||
</div>
|
||||
@@ -41,7 +41,7 @@ const { t } = useI18n();
|
||||
:href="REPO"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="inline-flex items-center gap-2 text-[14.5px] text-zinc-400 no-underline transition-colors hover:text-emerald-400"
|
||||
class="inline-flex items-center gap-2 text-[14.5px] text-fg-muted no-underline transition-colors hover:text-accent"
|
||||
>
|
||||
<IconGitea :size="16" />
|
||||
{{ t('ctaSource') }}
|
||||
|
||||
@@ -4,35 +4,47 @@ import { useI18n } from 'vue-i18n';
|
||||
import { useHeroTimeline, type SessionState } from '../composables/useHeroTimeline';
|
||||
import { DLG_OPTS } from '../i18n/content';
|
||||
import type { AppLocale } from '../i18n';
|
||||
import IconGitea from './icons/IconGitea.vue';
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
const { anim } = useHeroTimeline('hero-stage');
|
||||
|
||||
// Couleurs des lignes de terminal par type (porté de renderVals.C).
|
||||
// Couleurs des lignes de terminal par type, tokenisées (suivent le thème clair/sombre).
|
||||
const LINE_COLORS: Record<string, string> = {
|
||||
cmd: '#52525b',
|
||||
tool: '#a1a1aa',
|
||||
edit: '#34d399',
|
||||
add: '#34d399',
|
||||
del: '#f87171',
|
||||
ok: '#34d399',
|
||||
warn: '#fcd34d',
|
||||
run: '#7dd3fc',
|
||||
cmd: 'var(--color-fg-subtle)',
|
||||
tool: 'var(--color-fg-muted)',
|
||||
edit: 'var(--color-accent)',
|
||||
add: 'var(--color-accent)',
|
||||
del: 'var(--color-danger)',
|
||||
ok: 'var(--color-accent)',
|
||||
warn: 'var(--color-warn)',
|
||||
run: 'var(--color-info)',
|
||||
};
|
||||
|
||||
// Couleurs alignées sur SessionStateBadge de l'app (zinc/emerald/amber/sky de Tailwind).
|
||||
const PILL: Record<SessionState, { bg: string; fg: string; dot: string; pulse: string }> = {
|
||||
idle: { bg: '#022c22', fg: '#34d399', dot: '#34d399', pulse: '' },
|
||||
busy: { bg: '#082f49', fg: '#7dd3fc', dot: '#7dd3fc', pulse: 'pulseSky 2s ease-in-out infinite' },
|
||||
waiting: { bg: '#451a03', fg: '#fcd34d', dot: '#fcd34d', pulse: 'pulseAmber 2.2s ease-in-out infinite' },
|
||||
// Pastille d'état, alignée sur SessionStateBadge + lib/status-tokens de l'app
|
||||
// (waiting -> warn, busy -> info, idle -> accent), via color-mix pour le fond teinté.
|
||||
const PILL: Record<SessionState, { bg: string; fg: string; pulse: string }> = {
|
||||
idle: {
|
||||
bg: 'color-mix(in oklab, var(--color-accent) 15%, transparent)',
|
||||
fg: 'var(--color-accent)',
|
||||
pulse: '',
|
||||
},
|
||||
busy: {
|
||||
bg: 'color-mix(in oklab, var(--color-info) 15%, transparent)',
|
||||
fg: 'var(--color-info)',
|
||||
pulse: 'pulseSky 2s ease-in-out infinite',
|
||||
},
|
||||
waiting: {
|
||||
bg: 'color-mix(in oklab, var(--color-warn) 15%, transparent)',
|
||||
fg: 'var(--color-warn)',
|
||||
pulse: 'pulseAmber 2.2s ease-in-out infinite',
|
||||
},
|
||||
};
|
||||
|
||||
const pill = computed(() => PILL[anim.value.sess]);
|
||||
const sessText = computed(() => t(anim.value.sess));
|
||||
|
||||
const termLines = computed(() =>
|
||||
anim.value.lines.map((l) => ({ text: l.text, color: LINE_COLORS[l.k] ?? '#d4d4d8' })),
|
||||
anim.value.lines.map((l) => ({ text: l.text, color: LINE_COLORS[l.k] ?? 'var(--color-fg-muted)' })),
|
||||
);
|
||||
|
||||
// Options du dialogue de permission : reproduit DialogPrompt (boutons numérotés + Deny).
|
||||
@@ -48,137 +60,108 @@ const dlgOptions = computed(() =>
|
||||
|
||||
<template>
|
||||
<div id="hero-stage" class="relative min-w-[300px] flex-[1_1_480px]">
|
||||
<div
|
||||
class="relative overflow-hidden rounded-[14px] border border-zinc-800 bg-zinc-950 shadow-hero"
|
||||
>
|
||||
<div class="relative overflow-hidden rounded-[14px] border border-border bg-surface-0 shadow-hero">
|
||||
<!-- barre de fenêtre -->
|
||||
<div class="flex items-center gap-2 border-b border-[#1c1c1f] bg-[#0c0c0e] px-[14px] py-[11px]">
|
||||
<span class="h-2.5 w-2.5 rounded-full bg-zinc-700"></span>
|
||||
<span class="h-2.5 w-2.5 rounded-full bg-zinc-700"></span>
|
||||
<span class="h-2.5 w-2.5 rounded-full bg-zinc-700"></span>
|
||||
<span class="ml-2 font-mono text-[11.5px] text-zinc-600">arboretum · localhost:7317</span>
|
||||
<div class="flex items-center gap-2 border-b border-border-soft bg-surface-1 px-[14px] py-[11px]">
|
||||
<span class="h-2.5 w-2.5 rounded-full bg-danger"></span>
|
||||
<span class="h-2.5 w-2.5 rounded-full bg-warn"></span>
|
||||
<span class="h-2.5 w-2.5 rounded-full bg-accent"></span>
|
||||
<span class="ml-2 font-mono text-[11.5px] text-fg-subtle">arboretum · localhost:7317</span>
|
||||
</div>
|
||||
|
||||
<div class="flex min-h-[440px]">
|
||||
<!-- sidebar (fidèle à AppSidebar : Worktrees / Sessions / Groups, puis Réglages / Aide / Gitea / Café) -->
|
||||
<div
|
||||
class="flex w-[190px] flex-none flex-col border-r border-[#1c1c1f] bg-zinc-900/40 px-2 py-3"
|
||||
>
|
||||
<div class="flex items-center gap-2 px-2 pb-3">
|
||||
<img
|
||||
src="/assets/arboretum-mark.png"
|
||||
alt=""
|
||||
width="22"
|
||||
height="22"
|
||||
class="h-[22px] w-[22px] object-contain"
|
||||
/>
|
||||
<span class="text-[14px] font-semibold text-zinc-100">Arboretum</span>
|
||||
<!-- rail ActivityBar (fidèle à ActivityBar.vue : icônes explorer / git / sessions / groups) -->
|
||||
<div class="flex w-12 flex-none flex-col items-center gap-1 border-r border-border bg-surface-1 py-2">
|
||||
<!-- explorer (actif) -->
|
||||
<div class="relative flex h-9 w-9 items-center justify-center rounded-[9px] bg-surface-2 text-accent">
|
||||
<span class="absolute top-1/2 left-0 h-5 w-0.5 -translate-y-1/2 rounded-full bg-accent"></span>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="18" height="18" x="3" y="3" rx="2" /><path d="M3 9h18" /><path d="M9 21V9" /></svg>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="mb-2 flex items-center gap-2 rounded-lg border border-zinc-800 bg-zinc-950/40 px-2.5 py-1.5"
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#52525b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8" /><path d="m21 21-4.3-4.3" /></svg>
|
||||
<span class="flex-1 text-[12px] text-zinc-500">{{ t('mSearch') }}</span>
|
||||
<span class="rounded border border-zinc-700 px-[5px] py-px font-mono text-[10px] text-zinc-500">⌘K</span>
|
||||
<!-- git -->
|
||||
<div class="flex h-9 w-9 items-center justify-center rounded-[9px] text-fg-subtle">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="6" x2="6" y1="3" y2="15" /><circle cx="18" cy="6" r="3" /><circle cx="6" cy="18" r="3" /><path d="M18 9a9 9 0 0 1-9 9" /></svg>
|
||||
</div>
|
||||
|
||||
<!-- nav primaire (fidèle à AppSidebar : IDE actif, puis Worktrees / Sessions / Groups) -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center gap-2.5 rounded-lg bg-zinc-800 px-2.5 py-1.5 text-zinc-100">
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="18" height="18" x="3" y="3" rx="2" /><path d="M3 9h18" /><path d="M9 21V9" /></svg>
|
||||
<span class="flex-1 text-[12.5px] font-medium">{{ t('mIde') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-zinc-400">
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="6" x2="6" y1="3" y2="15" /><circle cx="18" cy="6" r="3" /><circle cx="6" cy="18" r="3" /><path d="M18 9a9 9 0 0 1-9 9" /></svg>
|
||||
<span class="flex-1 text-[12.5px] font-medium">{{ t('mWorktrees') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-zinc-400">
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7 11 2-2-2-2" /><path d="M11 13h4" /><rect width="18" height="18" x="3" y="3" rx="2" ry="2" /></svg>
|
||||
<span class="flex-1 text-[12.5px] font-medium">{{ t('mSessions') }}</span>
|
||||
<span
|
||||
v-show="anim.att > 0"
|
||||
class="rounded-full bg-amber-500/20 px-1.5 text-[11px] font-semibold text-amber-300"
|
||||
>{{ anim.att }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-zinc-400">
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z" /><path d="M7 16.5 12 13.5" /><path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z" /><path d="m17 16.5 4.74-2.85" /><path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0z" /></svg>
|
||||
<span class="flex-1 text-[12.5px] font-medium">{{ t('mGroups') }}</span>
|
||||
</div>
|
||||
<!-- sessions (+ badge attention) -->
|
||||
<div class="relative flex h-9 w-9 items-center justify-center rounded-[9px] text-fg-subtle">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7 11 2-2-2-2" /><path d="M11 13h4" /><rect width="18" height="18" x="3" y="3" rx="2" ry="2" /></svg>
|
||||
<span v-show="anim.att > 0" class="absolute top-1 right-1 flex h-3.5 min-w-3.5 items-center justify-center rounded-full bg-warn px-1 text-[9px] font-semibold text-zinc-950">{{ anim.att }}</span>
|
||||
</div>
|
||||
<!-- groups -->
|
||||
<div class="flex h-9 w-9 items-center justify-center rounded-[9px] text-fg-subtle">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z" /><path d="M7 16.5 12 13.5" /><path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z" /><path d="m17 16.5 4.74-2.85" /><path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0z" /></svg>
|
||||
</div>
|
||||
|
||||
<div class="flex-1"></div>
|
||||
|
||||
<!-- nav secondaire -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-zinc-400">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" /><circle cx="12" cy="12" r="3" /></svg>
|
||||
<span class="flex-1 text-[12.5px] font-medium">{{ t('mSettings') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-zinc-400">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10" /><path d="m4.93 4.93 4.24 4.24" /><path d="m14.83 9.17 4.24-4.24" /><path d="m14.83 14.83 4.24 4.24" /><path d="m9.17 14.83-4.24 4.24" /><circle cx="12" cy="12" r="4" /></svg>
|
||||
<span class="flex-1 text-[12.5px] font-medium">{{ t('mHelp') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-zinc-400">
|
||||
<IconGitea :size="16" />
|
||||
<span class="flex-1 text-[12.5px] font-medium">{{ t('mGitea') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-zinc-400">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 2v2" /><path d="M14 2v2" /><path d="M16 8a1 1 0 0 1 1 1v8a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1h14a4 4 0 1 1 0 8h-1" /><path d="M6 2v2" /></svg>
|
||||
<span class="flex-1 text-[12.5px] font-medium">{{ t('coffee') }}</span>
|
||||
</div>
|
||||
<!-- bas : thème / réglages / aide -->
|
||||
<div class="flex h-9 w-9 items-center justify-center rounded-[9px] text-fg-subtle">
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79Z" /></svg>
|
||||
</div>
|
||||
<div class="flex h-9 w-9 items-center justify-center rounded-[9px] text-fg-subtle">
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3" /><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" /></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- pied : langue · notifications · déconnexion + version (fidèle à AppShellFooter) -->
|
||||
<div class="mt-2 flex items-center gap-2 border-t border-[#1c1c1f] px-1 pt-2.5">
|
||||
<span class="rounded border border-zinc-700 px-1.5 py-px font-mono text-[10px] text-zinc-400">{{ locale.toUpperCase() }}</span>
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#34d399" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10.268 21a2 2 0 0 0 3.464 0" /><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326" /></svg>
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#71717a" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" /><polyline points="16 17 21 12 16 7" /><line x1="21" x2="9" y1="12" y2="12" /></svg>
|
||||
<span class="ml-auto font-mono text-[10px] text-zinc-600">v2.0.0</span>
|
||||
<!-- panneau Projects (fidèle à ProjectTree : arbre projet -> worktree -> session) -->
|
||||
<div class="flex w-[176px] flex-none flex-col border-r border-border bg-surface-1 py-2">
|
||||
<div class="label-mono px-3 pb-2">{{ t('mProjects') }}</div>
|
||||
<div class="flex flex-col gap-px px-1 text-[12px]">
|
||||
<div class="flex items-center gap-1.5 rounded px-2 py-1 text-fg-muted">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 3h16a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z" /><circle cx="12" cy="10" r="2" /><path d="M12 12v4" /></svg>
|
||||
<span class="truncate">api</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 rounded bg-surface-2 px-2 py-1 pl-5 text-fg">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="var(--color-accent)" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="6" x2="6" y1="3" y2="15" /><circle cx="18" cy="6" r="3" /><circle cx="6" cy="18" r="3" /><path d="M18 9a9 9 0 0 1-9 9" /></svg>
|
||||
<span class="truncate font-mono text-accent">feat/auth</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 rounded px-2 py-0.5 pl-9 text-[11px] text-fg-muted">
|
||||
<span class="h-1.5 w-1.5 flex-none rounded-full" :style="{ background: pill.fg, animation: pill.pulse }"></span>
|
||||
<span class="truncate font-mono">claude</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 rounded px-2 py-1 text-fg-muted">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6" /></svg>
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 3h16a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z" /><circle cx="12" cy="10" r="2" /><path d="M12 12v4" /></svg>
|
||||
<span class="truncate">web</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1.5 rounded px-2 py-1 text-fg-muted">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6" /></svg>
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 3h16a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2Z" /><circle cx="12" cy="10" r="2" /><path d="M12 12v4" /></svg>
|
||||
<span class="truncate">docs</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- main -->
|
||||
<div class="flex min-w-0 flex-1 flex-col gap-[11px] p-[13px]">
|
||||
<div class="flex min-w-0 flex-1 flex-col gap-[11px] bg-surface-0 p-[13px]">
|
||||
<!-- terminal pane -->
|
||||
<div
|
||||
class="flex min-h-0 flex-1 flex-col overflow-hidden rounded-[10px] border border-zinc-800 bg-zinc-950"
|
||||
>
|
||||
<div class="flex items-center justify-between border-b border-[#1c1c1f] px-[11px] py-2">
|
||||
<span class="font-mono text-[11.5px] text-zinc-300">api · feat/auth</span>
|
||||
<div class="flex min-h-0 flex-1 flex-col overflow-hidden rounded-[10px] border border-border bg-surface-0">
|
||||
<div class="flex items-center justify-between border-b border-border-soft px-[11px] py-2">
|
||||
<span class="font-mono text-[11.5px] text-fg-muted">api · feat/auth</span>
|
||||
<span
|
||||
class="inline-flex items-center gap-1.5 rounded px-2 py-[3px] font-mono text-[11px]"
|
||||
class="inline-flex items-center gap-1.5 rounded-full px-2 py-[3px] font-mono text-[11px]"
|
||||
:style="{ background: pill.bg, color: pill.fg }"
|
||||
>
|
||||
<span
|
||||
class="h-1.5 w-1.5 flex-none rounded-full"
|
||||
:style="{ background: pill.dot, animation: pill.pulse }"
|
||||
></span>
|
||||
<span class="h-1.5 w-1.5 flex-none rounded-full" :style="{ background: pill.fg, animation: pill.pulse }"></span>
|
||||
{{ sessText }}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="flex min-h-[150px] flex-1 flex-col justify-end overflow-hidden p-[11px] font-mono text-xs leading-[1.7] text-zinc-300"
|
||||
>
|
||||
<div class="flex min-h-[150px] flex-1 flex-col justify-end overflow-hidden p-[11px] font-mono text-xs leading-[1.7] text-fg-muted">
|
||||
<div v-for="(ln, i) in termLines" :key="i" :style="{ color: ln.color, whiteSpace: 'pre' }">
|
||||
{{ ln.text }}
|
||||
</div>
|
||||
<div v-if="anim.cur">
|
||||
<span class="text-emerald-400">$</span>
|
||||
<span class="ml-1 inline-block h-[13px] w-[7px] animate-blink align-middle bg-zinc-200"></span>
|
||||
<span class="text-accent">$</span>
|
||||
<span class="ml-1 inline-block h-[13px] w-[7px] animate-blink bg-fg align-middle"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- dialogue de permission (fidèle à DialogPrompt : badge type + question + options + Deny) -->
|
||||
<div
|
||||
v-if="anim.dlg"
|
||||
class="animate-dlg-in rounded-lg border border-amber-900/60 bg-amber-950/30 p-[11px]"
|
||||
>
|
||||
<!-- dialogue de permission (fidèle à DialogPrompt) -->
|
||||
<div v-if="anim.dlg" class="animate-dlg-in rounded-lg border border-warn/40 bg-warn/10 p-[11px]">
|
||||
<div class="mb-2 flex items-center gap-2">
|
||||
<span class="inline-flex items-center rounded bg-amber-900 px-1.5 py-0.5 text-[11px] font-medium text-amber-200">{{ t('mPermission') }}</span>
|
||||
<span class="text-[12.5px] text-amber-100">{{ t('dlgQ') }}</span>
|
||||
<span class="inline-flex items-center rounded-full bg-warn/15 px-1.5 py-0.5 font-mono text-[11px] text-warn">{{ t('mPermission') }}</span>
|
||||
<span class="text-[12.5px] text-fg">{{ t('dlgQ') }}</span>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-[6px]">
|
||||
<span
|
||||
@@ -186,15 +169,15 @@ const dlgOptions = computed(() =>
|
||||
:key="i"
|
||||
class="inline-flex items-center gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs font-medium transition-colors"
|
||||
:class="op.answered
|
||||
? 'border-emerald-500/70 bg-emerald-950/50 text-emerald-300'
|
||||
? 'border-accent/70 bg-accent/15 text-accent'
|
||||
: op.highlighted
|
||||
? 'border-amber-400 bg-amber-950/50 text-amber-200'
|
||||
: 'border-zinc-700 bg-zinc-800 text-zinc-200'"
|
||||
? 'border-warn bg-warn/15 text-warn'
|
||||
: 'border-border bg-surface-2 text-fg'"
|
||||
>
|
||||
<span class="font-mono" :class="op.answered ? 'text-emerald-400' : 'text-amber-400'">{{ op.num }}</span>
|
||||
<span class="font-mono" :class="op.answered ? 'text-accent' : 'text-warn'">{{ op.num }}</span>
|
||||
{{ op.text }}
|
||||
</span>
|
||||
<span class="inline-flex items-center rounded-lg border border-red-900 bg-red-950 px-2.5 py-1.5 text-xs font-medium text-red-300">{{ t('mDeny') }}</span>
|
||||
<span class="inline-flex items-center rounded-lg border border-danger/40 bg-danger/10 px-2.5 py-1.5 text-xs font-medium text-danger">{{ t('mDeny') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -204,18 +187,18 @@ const dlgOptions = computed(() =>
|
||||
<!-- toast push -->
|
||||
<div
|
||||
v-if="anim.toast"
|
||||
class="animate-toast-in absolute bottom-[14px] right-[14px] flex w-[248px] items-start gap-2.5 rounded-[11px] border border-[rgba(120,53,15,0.5)] bg-[#0c0c0e] p-3 shadow-toast"
|
||||
class="animate-toast-in absolute right-[14px] bottom-[14px] flex w-[248px] items-start gap-2.5 rounded-[11px] border border-border bg-surface-1 p-3 shadow-toast"
|
||||
>
|
||||
<span class="inline-flex h-[30px] w-[30px] flex-none items-center justify-center rounded-lg bg-emerald-500/15">
|
||||
<span class="inline-flex h-[30px] w-[30px] flex-none items-center justify-center rounded-lg bg-accent/15">
|
||||
<img src="/assets/arboretum-mark.png" alt="" width="18" height="18" class="h-[18px] w-[18px] object-contain" />
|
||||
</span>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-mono text-[11px] text-emerald-400">Arboretum</span>
|
||||
<span class="text-[10px] text-zinc-600">now</span>
|
||||
<span class="font-mono text-[11px] text-accent">Arboretum</span>
|
||||
<span class="text-[10px] text-fg-subtle">now</span>
|
||||
</div>
|
||||
<div class="mt-[3px] text-[13px] font-semibold text-zinc-100">{{ t('toastTitle') }}</div>
|
||||
<div class="mt-px text-[11.5px] leading-[1.4] text-zinc-400">{{ t('toastBody') }}</div>
|
||||
<div class="mt-[3px] text-[13px] font-semibold text-fg">{{ t('toastTitle') }}</div>
|
||||
<div class="mt-px text-[11.5px] leading-[1.4] text-fg-muted">{{ t('toastBody') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,27 +21,27 @@ const { t } = useI18n();
|
||||
class="block h-[46px] w-[46px] object-contain"
|
||||
/>
|
||||
<span
|
||||
class="inline-flex items-center gap-2 rounded-full border border-emerald-500/30 bg-emerald-500/[0.07] px-[11px] py-[5px] font-mono text-[11.5px] uppercase tracking-[0.1em] text-emerald-400"
|
||||
class="inline-flex items-center gap-2 rounded-full border border-accent/30 bg-accent/[0.07] px-[11px] py-[5px] font-mono text-[11.5px] uppercase tracking-[0.1em] text-accent"
|
||||
>
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-emerald-400 shadow-[0_0_8px_#34d399]"></span>
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-accent shadow-[0_0_8px_var(--color-accent)]"></span>
|
||||
{{ t('heroBadge') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<h1 class="m-0 text-[clamp(40px,5.6vw,64px)] font-bold leading-[1.04] tracking-[-0.035em] text-zinc-50">
|
||||
<h1 class="m-0 text-[clamp(40px,5.6vw,64px)] font-bold leading-[1.04] tracking-[-0.035em] text-fg">
|
||||
{{ t('heroTitle') }}
|
||||
</h1>
|
||||
<p class="mt-[22px] max-w-[520px] text-[clamp(17px,1.7vw,20px)] leading-[1.55] text-zinc-400">
|
||||
<p class="mt-[22px] max-w-[520px] text-[clamp(17px,1.7vw,20px)] leading-[1.55] text-fg-muted">
|
||||
{{ t('heroSub') }}
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="mt-[30px] flex max-w-[480px] items-stretch overflow-hidden rounded-[10px] border border-zinc-800 bg-zinc-950/60"
|
||||
class="mt-[30px] flex max-w-[480px] items-stretch overflow-hidden rounded-[10px] border border-border bg-surface-0/60"
|
||||
>
|
||||
<div
|
||||
class="flex min-w-0 flex-1 items-center gap-2.5 overflow-x-auto whitespace-nowrap px-[15px] font-mono text-sm"
|
||||
>
|
||||
<span class="text-emerald-400">$</span><span class="text-zinc-200">{{ INSTALL_COMMAND }}</span>
|
||||
<span class="text-accent">$</span><span class="text-fg">{{ INSTALL_COMMAND }}</span>
|
||||
</div>
|
||||
<CopyButton variant="bar" :text="INSTALL_COMMAND" :label="t('copyCommand')" />
|
||||
</div>
|
||||
@@ -49,7 +49,7 @@ const { t } = useI18n();
|
||||
<div class="mt-6 flex flex-wrap gap-[13px]">
|
||||
<a
|
||||
href="#how"
|
||||
class="inline-flex items-center gap-[9px] rounded-[9px] bg-emerald-600 px-6 py-[13px] text-[15.5px] font-semibold text-white no-underline transition-[background,transform] duration-150 ease-in-out hover:-translate-y-px hover:bg-emerald-500 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-emerald-500/70"
|
||||
class="inline-flex items-center gap-[9px] rounded-[9px] bg-accent-solid px-6 py-[13px] text-[15.5px] font-semibold text-white no-underline transition-[background,transform] duration-150 ease-in-out hover:-translate-y-px hover:bg-accent-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent/70"
|
||||
>
|
||||
{{ t('getStarted') }}
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14" /><path d="m12 5 7 7-7 7" /></svg>
|
||||
@@ -58,7 +58,7 @@ const { t } = useI18n();
|
||||
:href="REPO"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="inline-flex items-center gap-[9px] rounded-[9px] border border-zinc-800 px-[22px] py-[13px] text-[15.5px] font-medium text-zinc-100 no-underline transition-colors hover:border-emerald-500/45 hover:text-emerald-400 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-emerald-500/70"
|
||||
class="inline-flex items-center gap-[9px] rounded-[9px] border border-border px-[22px] py-[13px] text-[15.5px] font-medium text-fg no-underline transition-colors hover:border-accent/45 hover:text-accent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent/70"
|
||||
>
|
||||
<IconGitea :size="17" />
|
||||
{{ t('gitea') }}
|
||||
|
||||
@@ -11,41 +11,41 @@ const LOCAL_URL = 'http://localhost:7317';
|
||||
<template>
|
||||
<section id="how" class="mx-auto max-w-[1200px] scroll-mt-[84px] px-6 pb-[100px]">
|
||||
<div v-reveal class="mb-[46px] text-center">
|
||||
<div class="mb-3.5 font-mono text-xs uppercase tracking-[0.14em] text-emerald-400">{{ t('howKicker') }}</div>
|
||||
<h2 class="m-0 text-[clamp(28px,3.4vw,40px)] font-semibold tracking-[-0.025em] text-zinc-50">{{ t('howTitle') }}</h2>
|
||||
<div class="mb-3.5 font-mono text-xs uppercase tracking-[0.14em] text-accent">{{ t('howKicker') }}</div>
|
||||
<h2 class="m-0 text-[clamp(28px,3.4vw,40px)] font-semibold tracking-[-0.025em] text-fg">{{ t('howTitle') }}</h2>
|
||||
</div>
|
||||
|
||||
<div v-reveal class="grid grid-cols-[repeat(auto-fit,minmax(280px,1fr))] gap-4">
|
||||
<div class="rounded-xl border border-zinc-800 bg-zinc-900/50 p-[26px]">
|
||||
<div class="mb-[18px] font-mono text-[13px] text-emerald-400">01</div>
|
||||
<h3 class="m-0 mb-2.5 text-lg font-semibold text-zinc-50">{{ t('step1Title') }}</h3>
|
||||
<p class="m-0 mb-4 text-[14.5px] leading-[1.55] text-zinc-400">{{ t('step1Desc') }}</p>
|
||||
<div class="flex items-center gap-2 rounded-lg border border-zinc-800 bg-zinc-950 p-[11px]">
|
||||
<code class="min-w-0 flex-1 overflow-x-auto whitespace-nowrap font-mono text-[12.5px] text-zinc-200">
|
||||
<span class="text-emerald-400">$ </span>{{ INSTALL_COMMAND }}
|
||||
<div class="rounded-xl border border-border bg-surface-1/50 p-[26px]">
|
||||
<div class="mb-[18px] font-mono text-[13px] text-accent">01</div>
|
||||
<h3 class="m-0 mb-2.5 text-lg font-semibold text-fg">{{ t('step1Title') }}</h3>
|
||||
<p class="m-0 mb-4 text-[14.5px] leading-[1.55] text-fg-muted">{{ t('step1Desc') }}</p>
|
||||
<div class="flex items-center gap-2 rounded-lg border border-border bg-surface-0 p-[11px]">
|
||||
<code class="min-w-0 flex-1 overflow-x-auto whitespace-nowrap font-mono text-[12.5px] text-fg">
|
||||
<span class="text-accent">$ </span>{{ INSTALL_COMMAND }}
|
||||
</code>
|
||||
<CopyButton variant="icon" :text="INSTALL_COMMAND" :label="t('copyCommand')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-xl border border-zinc-800 bg-zinc-900/50 p-[26px]">
|
||||
<div class="mb-[18px] font-mono text-[13px] text-emerald-400">02</div>
|
||||
<h3 class="m-0 mb-2.5 text-lg font-semibold text-zinc-50">{{ t('step2Title') }}</h3>
|
||||
<p class="m-0 mb-4 text-[14.5px] leading-[1.55] text-zinc-400">{{ t('step2Desc') }}</p>
|
||||
<div class="flex items-center gap-2 rounded-lg border border-zinc-800 bg-zinc-950 p-[11px]">
|
||||
<code class="min-w-0 flex-1 overflow-x-auto whitespace-nowrap font-mono text-[12.5px] text-sky-300">
|
||||
<div class="rounded-xl border border-border bg-surface-1/50 p-[26px]">
|
||||
<div class="mb-[18px] font-mono text-[13px] text-accent">02</div>
|
||||
<h3 class="m-0 mb-2.5 text-lg font-semibold text-fg">{{ t('step2Title') }}</h3>
|
||||
<p class="m-0 mb-4 text-[14.5px] leading-[1.55] text-fg-muted">{{ t('step2Desc') }}</p>
|
||||
<div class="flex items-center gap-2 rounded-lg border border-border bg-surface-0 p-[11px]">
|
||||
<code class="min-w-0 flex-1 overflow-x-auto whitespace-nowrap font-mono text-[12.5px] text-info">
|
||||
→ {{ LOCAL_URL }}
|
||||
</code>
|
||||
<CopyButton variant="icon" :text="LOCAL_URL" :label="t('copyUrl')" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-xl border border-zinc-800 bg-zinc-900/50 p-[26px]">
|
||||
<div class="mb-[18px] font-mono text-[13px] text-emerald-400">03</div>
|
||||
<h3 class="m-0 mb-2.5 text-lg font-semibold text-zinc-50">{{ t('step3Title') }}</h3>
|
||||
<p class="m-0 mb-4 text-[14.5px] leading-[1.55] text-zinc-400">{{ t('step3Desc') }}</p>
|
||||
<div class="rounded-xl border border-border bg-surface-1/50 p-[26px]">
|
||||
<div class="mb-[18px] font-mono text-[13px] text-accent">03</div>
|
||||
<h3 class="m-0 mb-2.5 text-lg font-semibold text-fg">{{ t('step3Title') }}</h3>
|
||||
<p class="m-0 mb-4 text-[14.5px] leading-[1.55] text-fg-muted">{{ t('step3Desc') }}</p>
|
||||
<div class="flex flex-wrap gap-1.5">
|
||||
<span class="rounded-md bg-emerald-500/10 px-[9px] py-1 font-mono text-[11px] text-emerald-400">{{ t('idle') }}</span>
|
||||
<span class="rounded-md bg-[rgba(8,47,73,0.7)] px-[9px] py-1 font-mono text-[11px] text-sky-300">{{ t('busy') }}</span>
|
||||
<span class="rounded-md bg-[rgba(69,26,3,0.6)] px-[9px] py-1 font-mono text-[11px] text-amber-300">{{ t('waiting') }}</span>
|
||||
<span class="rounded-md bg-accent/10 px-[9px] py-1 font-mono text-[11px] text-accent">{{ t('idle') }}</span>
|
||||
<span class="rounded-md bg-info/15 px-[9px] py-1 font-mono text-[11px] text-info">{{ t('busy') }}</span>
|
||||
<span class="rounded-md bg-warn/15 px-[9px] py-1 font-mono text-[11px] text-warn">{{ t('waiting') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,13 +13,13 @@ function set(l: AppLocale): void {
|
||||
<div
|
||||
role="group"
|
||||
aria-label="Language"
|
||||
class="flex items-center overflow-hidden rounded-lg border border-zinc-800 bg-zinc-950/40"
|
||||
class="flex items-center overflow-hidden rounded-lg border border-border bg-surface-0/40"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
:aria-pressed="locale === 'en'"
|
||||
class="cursor-pointer border-none px-[11px] py-1.5 font-mono text-xs font-semibold focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-emerald-500/70"
|
||||
:class="locale === 'en' ? 'bg-zinc-800 text-emerald-400' : 'bg-transparent text-zinc-500'"
|
||||
class="cursor-pointer border-none px-[11px] py-1.5 font-mono text-xs font-semibold focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-accent/70"
|
||||
:class="locale === 'en' ? 'bg-surface-2 text-accent' : 'bg-transparent text-fg-subtle'"
|
||||
@click="set('en')"
|
||||
>
|
||||
EN
|
||||
@@ -27,8 +27,8 @@ function set(l: AppLocale): void {
|
||||
<button
|
||||
type="button"
|
||||
:aria-pressed="locale === 'fr'"
|
||||
class="cursor-pointer border-none px-[11px] py-1.5 font-mono text-xs font-semibold focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-emerald-500/70"
|
||||
:class="locale === 'fr' ? 'bg-zinc-800 text-emerald-400' : 'bg-transparent text-zinc-500'"
|
||||
class="cursor-pointer border-none px-[11px] py-1.5 font-mono text-xs font-semibold focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-accent/70"
|
||||
:class="locale === 'fr' ? 'bg-surface-2 text-accent' : 'bg-transparent text-fg-subtle'"
|
||||
@click="set('fr')"
|
||||
>
|
||||
FR
|
||||
|
||||
@@ -5,10 +5,10 @@ const { t } = useI18n();
|
||||
|
||||
<template>
|
||||
<section v-reveal class="mx-auto max-w-[920px] px-6 pb-[92px] pt-12 text-center">
|
||||
<div class="mb-4 font-mono text-xs uppercase tracking-[0.14em] text-emerald-400">{{ t('probKicker') }}</div>
|
||||
<h2 class="m-0 text-[clamp(28px,3.6vw,42px)] font-semibold leading-[1.18] tracking-[-0.025em] text-zinc-50">
|
||||
<div class="mb-4 font-mono text-xs uppercase tracking-[0.14em] text-accent">{{ t('probKicker') }}</div>
|
||||
<h2 class="m-0 text-[clamp(28px,3.6vw,42px)] font-semibold leading-[1.18] tracking-[-0.025em] text-fg">
|
||||
{{ t('probTitle') }}
|
||||
</h2>
|
||||
<p class="mx-auto mt-[22px] max-w-[640px] text-[clamp(17px,1.6vw,19px)] text-zinc-400">{{ t('probBody') }}</p>
|
||||
<p class="mx-auto mt-[22px] max-w-[640px] text-[clamp(17px,1.6vw,19px)] text-fg-muted">{{ t('probBody') }}</p>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
@@ -15,12 +15,12 @@ const connections = [
|
||||
<section id="remotegit" class="mx-auto max-w-[1200px] scroll-mt-[84px] px-6 pb-[100px]">
|
||||
<div v-reveal class="flex flex-wrap items-center gap-12">
|
||||
<div class="min-w-[280px] flex-[1_1_360px]">
|
||||
<div class="mb-3 font-mono text-xs uppercase tracking-[0.12em] text-emerald-400">{{ t('rgKicker') }}</div>
|
||||
<h2 class="m-0 mb-4 text-[clamp(26px,3vw,34px)] font-semibold leading-[1.15] tracking-[-0.025em] text-zinc-50">
|
||||
<div class="mb-3 font-mono text-xs uppercase tracking-[0.12em] text-accent">{{ t('rgKicker') }}</div>
|
||||
<h2 class="m-0 mb-4 text-[clamp(26px,3vw,34px)] font-semibold leading-[1.15] tracking-[-0.025em] text-fg">
|
||||
{{ t('rgTitle') }}
|
||||
</h2>
|
||||
<p class="m-0 text-[16.5px] leading-[1.6] text-zinc-400">{{ t('rgBody') }}</p>
|
||||
<div class="mt-4 inline-flex items-center gap-2 rounded-lg border border-emerald-500/30 bg-emerald-500/[0.08] px-3 py-1.5 text-[13px] font-medium text-emerald-300">
|
||||
<p class="m-0 text-[16.5px] leading-[1.6] text-fg-muted">{{ t('rgBody') }}</p>
|
||||
<div class="mt-4 inline-flex items-center gap-2 rounded-lg border border-accent/30 bg-accent/[0.08] px-3 py-1.5 text-[13px] font-medium text-accent">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="18" height="11" x="3" y="11" rx="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" /></svg>
|
||||
{{ t('rgEncrypted') }}
|
||||
</div>
|
||||
@@ -28,8 +28,8 @@ const connections = [
|
||||
|
||||
<div class="flex min-w-[280px] flex-[1_1_440px] flex-col gap-3">
|
||||
<!-- carte connexions (fidèle à GitConnectionsSection) -->
|
||||
<div class="flex flex-col gap-2 rounded-xl border border-zinc-800 bg-zinc-900/50 p-3 shadow-card">
|
||||
<h3 class="flex items-center gap-2 text-sm font-semibold text-zinc-100">
|
||||
<div class="flex flex-col gap-2 rounded-xl border border-border bg-surface-1/50 p-3 shadow-card">
|
||||
<h3 class="flex items-center gap-2 text-sm font-semibold text-fg">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 2v6" /><path d="M15 2v6" /><path d="M12 17v5" /><path d="M5 8h14" /><path d="M6 11V8h12v3a6 6 0 0 1-12 0Z" /></svg>
|
||||
{{ t('rgConnections') }}
|
||||
</h3>
|
||||
@@ -38,19 +38,19 @@ const connections = [
|
||||
<li
|
||||
v-for="c in connections"
|
||||
:key="c.label"
|
||||
class="flex flex-wrap items-center gap-2 rounded-lg border border-zinc-800 bg-zinc-900/40 px-2.5 py-2"
|
||||
class="flex flex-wrap items-center gap-2 rounded-lg border border-border bg-surface-1/40 px-2.5 py-2"
|
||||
>
|
||||
<!-- icône service (Gitea / GitHub / GitLab) -->
|
||||
<span class="inline-flex h-6 w-6 flex-none items-center justify-center rounded-md bg-zinc-800 text-zinc-300">
|
||||
<span class="inline-flex h-6 w-6 flex-none items-center justify-center rounded-md bg-surface-2 text-fg-muted">
|
||||
<svg v-if="c.service === 'github'" width="14" height="14" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M12 1.3a10.7 10.7 0 0 0-3.38 20.86c.53.1.73-.23.73-.51l-.01-1.79c-2.98.65-3.61-1.44-3.61-1.44-.49-1.24-1.19-1.57-1.19-1.57-.97-.66.07-.65.07-.65 1.08.08 1.64 1.11 1.64 1.11.96 1.64 2.51 1.16 3.12.89.1-.69.37-1.16.68-1.43-2.38-.27-4.88-1.19-4.88-5.3 0-1.17.42-2.13 1.1-2.88-.11-.27-.48-1.36.1-2.84 0 0 .9-.29 2.96 1.1a10.3 10.3 0 0 1 5.38 0c2.05-1.39 2.95-1.1 2.95-1.1.59 1.48.22 2.57.11 2.84.69.75 1.1 1.71 1.1 2.88 0 4.12-2.51 5.02-4.9 5.29.39.33.73.98.73 1.97l-.01 2.92c0 .29.2.62.74.51A10.7 10.7 0 0 0 12 1.3" /></svg>
|
||||
<svg v-else-if="c.service === 'gitlab'" width="15" height="15" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="m12 21.42 3.68-11.33H8.32L12 21.42Z" /><path d="M12 21.42 8.32 10.09H3.16L12 21.42Z" /><path d="M3.16 10.09 2.04 13.53a.76.76 0 0 0 .28.85L12 21.42 3.16 10.09Z" /><path d="M3.16 10.09h5.16L6.1 3.26a.38.38 0 0 0-.72 0l-2.22 6.83Z" /><path d="M12 21.42l3.68-11.33h5.16L12 21.42Z" /><path d="m20.84 10.09 1.12 3.44a.76.76 0 0 1-.28.85L12 21.42l8.84-11.33Z" /><path d="M20.84 10.09h-5.16l2.22-6.83a.38.38 0 0 1 .72 0l2.22 6.83Z" /></svg>
|
||||
<svg v-else width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 10h12v4a4 4 0 0 1-4 4H8a4 4 0 0 1-4-4z" /><path d="M16 11h2.5a2.5 2.5 0 0 1 0 5H16" /><path d="M7 3c-.5.8.5 1.6 0 2.5" /><path d="M11 3c-.5.8.5 1.6 0 2.5" /></svg>
|
||||
</span>
|
||||
<span class="inline-flex items-center rounded bg-sky-950 px-1.5 py-0.5 text-[11px] font-medium text-sky-300">{{ c.service }}</span>
|
||||
<span class="font-medium text-zinc-200">{{ c.label }}</span>
|
||||
<span class="text-xs text-zinc-500">{{ c.username }}</span>
|
||||
<span class="font-mono text-xs text-zinc-600">••••{{ c.last4 }}</span>
|
||||
<span class="ml-auto inline-flex items-center gap-1 rounded bg-emerald-950 px-1.5 py-0.5 text-[11px] font-medium text-emerald-400">
|
||||
<span class="inline-flex items-center rounded bg-info/15 px-1.5 py-0.5 text-[11px] font-medium text-info">{{ c.service }}</span>
|
||||
<span class="font-medium text-fg">{{ c.label }}</span>
|
||||
<span class="text-xs text-fg-subtle">{{ c.username }}</span>
|
||||
<span class="font-mono text-xs text-fg-subtle">••••{{ c.last4 }}</span>
|
||||
<span class="ml-auto inline-flex items-center gap-1 rounded bg-accent/15 px-1.5 py-0.5 text-[11px] font-medium text-accent">
|
||||
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg>
|
||||
{{ t('rgTested') }}
|
||||
</span>
|
||||
@@ -58,7 +58,7 @@ const connections = [
|
||||
</ul>
|
||||
|
||||
<div>
|
||||
<span class="inline-flex items-center gap-1.5 rounded-lg border border-zinc-700 bg-zinc-800 px-2.5 py-1.5 text-xs font-medium text-zinc-200">
|
||||
<span class="inline-flex items-center gap-1.5 rounded-lg border border-border-strong bg-surface-2 px-2.5 py-1.5 text-xs font-medium text-fg">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
{{ t('rgAddConnection') }}
|
||||
</span>
|
||||
@@ -66,16 +66,16 @@ const connections = [
|
||||
</div>
|
||||
|
||||
<!-- progression de clone (fidèle à CloneRepoModal) -->
|
||||
<div class="flex flex-col gap-2 rounded-xl border border-zinc-800 bg-zinc-900/50 p-3 shadow-card">
|
||||
<div class="flex flex-col gap-2 rounded-xl border border-border bg-surface-1/50 p-3 shadow-card">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#34d399" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" x2="12" y1="15" y2="3" /></svg>
|
||||
<span class="font-mono text-[13px] text-zinc-200">github.com/jleroy/api</span>
|
||||
<span class="ml-auto font-mono text-xs text-zinc-500">62%</span>
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="var(--color-accent)" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" /><polyline points="7 10 12 15 17 10" /><line x1="12" x2="12" y1="15" y2="3" /></svg>
|
||||
<span class="font-mono text-[13px] text-fg">github.com/jleroy/api</span>
|
||||
<span class="ml-auto font-mono text-xs text-fg-subtle">62%</span>
|
||||
</div>
|
||||
<div class="h-2 w-full overflow-hidden rounded bg-zinc-800">
|
||||
<div class="h-full rounded bg-emerald-500" style="width: 62%"></div>
|
||||
<div class="h-2 w-full overflow-hidden rounded bg-surface-2">
|
||||
<div class="h-full rounded bg-accent" style="width: 62%"></div>
|
||||
</div>
|
||||
<span class="font-mono text-xs text-zinc-500">{{ t('rgCloneReceiving') }}… 62%</span>
|
||||
<span class="font-mono text-xs text-fg-subtle">{{ t('rgCloneReceiving') }}… 62%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -7,44 +7,44 @@ const { t } = useI18n();
|
||||
<section id="security" class="mx-auto max-w-[1200px] scroll-mt-[84px] px-6 pb-[100px]">
|
||||
<div
|
||||
v-reveal
|
||||
class="rounded-[18px] border border-emerald-500/[0.18] p-[clamp(28px,4vw,48px)]"
|
||||
style="background: linear-gradient(180deg, rgba(16, 185, 129, 0.05), rgba(16, 185, 129, 0))"
|
||||
class="rounded-[18px] border border-accent/[0.18] p-[clamp(28px,4vw,48px)]"
|
||||
style="background: linear-gradient(180deg, color-mix(in srgb, var(--color-accent) 5%, transparent), transparent)"
|
||||
>
|
||||
<div class="mb-[30px] flex flex-wrap items-end justify-between gap-3.5">
|
||||
<div>
|
||||
<div class="mb-3 font-mono text-xs uppercase tracking-[0.14em] text-emerald-400">{{ t('secKicker') }}</div>
|
||||
<h2 class="m-0 text-[clamp(26px,3.2vw,38px)] font-semibold leading-[1.12] tracking-[-0.025em] text-zinc-50">
|
||||
<div class="mb-3 font-mono text-xs uppercase tracking-[0.14em] text-accent">{{ t('secKicker') }}</div>
|
||||
<h2 class="m-0 text-[clamp(26px,3.2vw,38px)] font-semibold leading-[1.12] tracking-[-0.025em] text-fg">
|
||||
{{ t('secTitle') }}
|
||||
</h2>
|
||||
</div>
|
||||
<p class="m-0 max-w-[380px] text-[15.5px] leading-[1.55] text-zinc-400">{{ t('secIntro') }}</p>
|
||||
<p class="m-0 max-w-[380px] text-[15.5px] leading-[1.55] text-fg-muted">{{ t('secIntro') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-[repeat(auto-fit,minmax(200px,1fr))] gap-3.5">
|
||||
<div class="flex flex-col gap-2.5 rounded-[11px] border border-zinc-800 bg-[#0c0c0e] p-[18px]">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#34d399" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="18" height="11" x="3" y="11" rx="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" /></svg>
|
||||
<div class="text-[15px] font-semibold text-zinc-50">{{ t('sec1Title') }}</div>
|
||||
<div class="text-[13.5px] leading-[1.5] text-zinc-400">{{ t('sec1Desc') }}</div>
|
||||
<div class="flex flex-col gap-2.5 rounded-[11px] border border-border bg-surface-0 p-[18px]">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--color-accent)" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="18" height="11" x="3" y="11" rx="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" /></svg>
|
||||
<div class="text-[15px] font-semibold text-fg">{{ t('sec1Title') }}</div>
|
||||
<div class="text-[13.5px] leading-[1.5] text-fg-muted">{{ t('sec1Desc') }}</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2.5 rounded-[11px] border border-zinc-800 bg-[#0c0c0e] p-[18px]">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#34d399" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4" /><path d="m21 2-9.6 9.6" /><circle cx="7.5" cy="15.5" r="5.5" /></svg>
|
||||
<div class="text-[15px] font-semibold text-zinc-50">{{ t('sec2Title') }}</div>
|
||||
<div class="text-[13.5px] leading-[1.5] text-zinc-400">{{ t('sec2Desc') }}</div>
|
||||
<div class="flex flex-col gap-2.5 rounded-[11px] border border-border bg-surface-0 p-[18px]">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--color-accent)" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4" /><path d="m21 2-9.6 9.6" /><circle cx="7.5" cy="15.5" r="5.5" /></svg>
|
||||
<div class="text-[15px] font-semibold text-fg">{{ t('sec2Title') }}</div>
|
||||
<div class="text-[13.5px] leading-[1.5] text-fg-muted">{{ t('sec2Desc') }}</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2.5 rounded-[11px] border border-zinc-800 bg-[#0c0c0e] p-[18px]">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#34d399" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /><path d="m9 12 2 2 4-4" /></svg>
|
||||
<div class="text-[15px] font-semibold text-zinc-50">{{ t('sec3Title') }}</div>
|
||||
<div class="text-[13.5px] leading-[1.5] text-zinc-400">{{ t('sec3Desc') }}</div>
|
||||
<div class="flex flex-col gap-2.5 rounded-[11px] border border-border bg-surface-0 p-[18px]">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--color-accent)" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /><path d="m9 12 2 2 4-4" /></svg>
|
||||
<div class="text-[15px] font-semibold text-fg">{{ t('sec3Title') }}</div>
|
||||
<div class="text-[13.5px] leading-[1.5] text-fg-muted">{{ t('sec3Desc') }}</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2.5 rounded-[11px] border border-zinc-800 bg-[#0c0c0e] p-[18px]">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#34d399" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12.55a11 11 0 0 1 14.08 0" /><path d="M1.42 9a16 16 0 0 1 21.16 0" /><path d="M8.53 16.11a6 6 0 0 1 6.95 0" /><path d="M12 20h.01" /></svg>
|
||||
<div class="text-[15px] font-semibold text-zinc-50">{{ t('sec4Title') }}</div>
|
||||
<div class="text-[13.5px] leading-[1.5] text-zinc-400">{{ t('sec4Desc') }}</div>
|
||||
<div class="flex flex-col gap-2.5 rounded-[11px] border border-border bg-surface-0 p-[18px]">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--color-accent)" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12.55a11 11 0 0 1 14.08 0" /><path d="M1.42 9a16 16 0 0 1 21.16 0" /><path d="M8.53 16.11a6 6 0 0 1 6.95 0" /><path d="M12 20h.01" /></svg>
|
||||
<div class="text-[15px] font-semibold text-fg">{{ t('sec4Title') }}</div>
|
||||
<div class="text-[13.5px] leading-[1.5] text-fg-muted">{{ t('sec4Desc') }}</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2.5 rounded-[11px] border border-zinc-800 bg-[#0c0c0e] p-[18px]">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#34d399" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="18" height="11" x="3" y="11" rx="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" /><path d="M12 16v-2" /></svg>
|
||||
<div class="text-[15px] font-semibold text-zinc-50">{{ t('sec5Title') }}</div>
|
||||
<div class="text-[13.5px] leading-[1.5] text-zinc-400">{{ t('sec5Desc') }}</div>
|
||||
<div class="flex flex-col gap-2.5 rounded-[11px] border border-border bg-surface-0 p-[18px]">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--color-accent)" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="18" height="11" x="3" y="11" rx="2" /><path d="M7 11V7a5 5 0 0 1 10 0v4" /><path d="M12 16v-2" /></svg>
|
||||
<div class="text-[15px] font-semibold text-fg">{{ t('sec5Title') }}</div>
|
||||
<div class="text-[13.5px] leading-[1.5] text-fg-muted">{{ t('sec5Desc') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,246 +12,250 @@ const dlgOpts = computed(() => DLG_OPTS[locale.value as AppLocale]);
|
||||
|
||||
<template>
|
||||
<section id="showcase" class="mx-auto flex max-w-[1200px] scroll-mt-[84px] flex-col gap-[92px] px-6">
|
||||
<!-- a) dashboard worktree-first : rail « À traiter » + section de repo (fidèle à AttentionSection + RepoSection) -->
|
||||
<!-- a) supervision : rail « À traiter » + section de repo (AttentionList + worktrees / git) -->
|
||||
<div v-reveal class="flex flex-wrap items-center gap-12">
|
||||
<div class="min-w-[280px] flex-[1_1_360px]">
|
||||
<div class="mb-3 font-mono text-xs uppercase tracking-[0.12em] text-emerald-400">{{ t('scAKicker') }}</div>
|
||||
<h2 class="m-0 mb-4 text-[clamp(26px,3vw,34px)] font-semibold leading-[1.15] tracking-[-0.025em] text-zinc-50">
|
||||
<div class="mb-3 font-mono text-xs uppercase tracking-[0.12em] text-accent">{{ t('scAKicker') }}</div>
|
||||
<h2 class="m-0 mb-4 text-[clamp(26px,3vw,34px)] font-semibold leading-[1.15] tracking-[-0.025em] text-fg">
|
||||
{{ t('scATitle') }}
|
||||
</h2>
|
||||
<p class="m-0 text-[16.5px] leading-[1.6] text-zinc-400">{{ t('scABody') }}</p>
|
||||
<p class="m-0 text-[16.5px] leading-[1.6] text-fg-muted">{{ t('scABody') }}</p>
|
||||
</div>
|
||||
<div class="flex min-w-[280px] flex-[1_1_440px] flex-col gap-3">
|
||||
<!-- rail « À traiter » -->
|
||||
<div class="flex flex-col gap-2 rounded-xl border border-amber-900/50 bg-amber-950/20 p-3 shadow-card">
|
||||
<div class="flex flex-col gap-2 rounded-xl border border-warn/40 bg-warn/10 p-3 shadow-card">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#fbbf24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" /><path d="M12 9v4" /><path d="M12 17h.01" /></svg>
|
||||
<h3 class="text-sm font-semibold text-amber-200">{{ t('mAttn') }}</h3>
|
||||
<span class="rounded-full bg-amber-500/20 px-1.5 text-[11px] font-semibold text-amber-300">1</span>
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--color-warn)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" /><path d="M12 9v4" /><path d="M12 17h.01" /></svg>
|
||||
<h3 class="text-sm font-semibold text-warn">{{ t('mAttn') }}</h3>
|
||||
<span class="rounded-full bg-warn/20 px-1.5 text-[11px] font-semibold text-warn">1</span>
|
||||
</div>
|
||||
<span class="truncate font-mono text-xs text-amber-300/80">~/code/arboretum/api</span>
|
||||
<span class="truncate font-mono text-xs text-warn/80">~/code/arboretum/api</span>
|
||||
<!-- DialogPrompt -->
|
||||
<div class="rounded-lg border border-amber-900/60 bg-amber-950/30 p-2.5">
|
||||
<div class="rounded-lg border border-warn/40 bg-warn/10 p-2.5">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="inline-flex items-center rounded bg-amber-900 px-1.5 py-0.5 text-[11px] font-medium text-amber-200">{{ t('mPermission') }}</span>
|
||||
<span class="text-[12.5px] text-amber-100">{{ t('dlgQ') }}</span>
|
||||
<span class="inline-flex items-center rounded-full bg-warn/15 px-1.5 py-0.5 font-mono text-[11px] text-warn">{{ t('mPermission') }}</span>
|
||||
<span class="text-[12.5px] text-fg">{{ t('dlgQ') }}</span>
|
||||
</div>
|
||||
<div class="mt-2 flex flex-wrap gap-1.5">
|
||||
<span
|
||||
v-for="(op, i) in dlgOpts"
|
||||
:key="i"
|
||||
class="inline-flex items-center gap-1.5 rounded-lg border border-zinc-700 bg-zinc-800 px-2.5 py-1.5 text-xs font-medium text-zinc-200"
|
||||
class="inline-flex items-center gap-1.5 rounded-lg border border-border bg-surface-2 px-2.5 py-1.5 text-xs font-medium text-fg"
|
||||
>
|
||||
<span class="font-mono text-amber-400">{{ i + 1 }}</span> {{ op }}
|
||||
<span class="font-mono text-warn">{{ i + 1 }}</span> {{ op }}
|
||||
</span>
|
||||
<span class="inline-flex items-center rounded-lg border border-red-900 bg-red-950 px-2.5 py-1.5 text-xs font-medium text-red-300">{{ t('mDeny') }}</span>
|
||||
<span class="inline-flex items-center rounded-lg border border-danger/40 bg-danger/10 px-2.5 py-1.5 text-xs font-medium text-danger">{{ t('mDeny') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- section de repo : worktrees + état de session -->
|
||||
<div class="flex flex-col gap-2 rounded-xl border border-zinc-800 bg-zinc-900/50 p-3 shadow-card">
|
||||
<div class="flex flex-col gap-2 rounded-xl border border-border bg-surface-1/50 p-3 shadow-card">
|
||||
<header class="flex flex-wrap items-center gap-2">
|
||||
<h3 class="font-semibold text-zinc-100">arboretum</h3>
|
||||
<span class="truncate font-mono text-xs text-zinc-500">~/code/arboretum</span>
|
||||
<span class="ml-auto inline-flex items-center gap-1.5 rounded-lg border border-zinc-700 bg-zinc-800 px-2 py-1 text-xs font-medium text-zinc-200">
|
||||
<h3 class="font-semibold text-fg">arboretum</h3>
|
||||
<span class="truncate font-mono text-xs text-fg-subtle">~/code/arboretum</span>
|
||||
<span class="ml-auto inline-flex items-center gap-1.5 rounded-lg border border-border bg-surface-2 px-2 py-1 text-xs font-medium text-fg">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14" /><path d="M12 5v14" /></svg>
|
||||
{{ t('mStart') }}
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<!-- worktree : branche principale, session au repos -->
|
||||
<div class="rounded border border-zinc-800 bg-zinc-900/40 p-2">
|
||||
<div class="rounded border border-border bg-surface-1/40 p-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-mono text-sm text-zinc-200">main</span>
|
||||
<span class="inline-flex items-center rounded bg-zinc-800 px-1.5 py-0.5 text-[11px] font-medium text-zinc-400">{{ t('mMain') }}</span>
|
||||
<span class="ml-auto text-xs text-zinc-600">{{ t('mClean') }}</span>
|
||||
<span class="font-mono text-sm text-fg">main</span>
|
||||
<span class="inline-flex items-center rounded bg-surface-2 px-1.5 py-0.5 text-[11px] font-medium text-fg-muted">{{ t('mMain') }}</span>
|
||||
<span class="ml-auto text-xs text-fg-subtle">{{ t('mClean') }}</span>
|
||||
</div>
|
||||
<p class="truncate font-mono text-xs text-zinc-500">~/code/arboretum</p>
|
||||
<p class="truncate font-mono text-xs text-fg-subtle">~/code/arboretum</p>
|
||||
<div class="mt-1 flex items-center gap-2">
|
||||
<span class="inline-flex items-center gap-1 rounded bg-zinc-800/60 px-1.5 py-0.5">
|
||||
<span class="inline-flex items-center gap-1 rounded bg-emerald-950 px-1.5 py-0.5 text-[11px] font-medium text-emerald-400">
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-emerald-400"></span>{{ t('idle') }}
|
||||
<span class="inline-flex items-center gap-1 rounded bg-surface-2/60 px-1.5 py-0.5">
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-accent/15 px-1.5 py-0.5 text-[11px] font-medium text-accent">
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-accent"></span>{{ t('idle') }}
|
||||
</span>
|
||||
<span class="font-mono text-[11px] text-zinc-400">claude</span>
|
||||
<span class="font-mono text-[11px] text-fg-muted">claude</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- worktree : branche de feature, session occupée -->
|
||||
<div class="rounded border border-zinc-800 bg-zinc-900/40 p-2">
|
||||
<div class="rounded border border-border bg-surface-1/40 p-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-mono text-sm text-zinc-200">feat/auth</span>
|
||||
<span class="ml-auto flex items-center gap-2 text-xs text-zinc-500">
|
||||
<span class="font-mono text-sm text-fg">feat/auth</span>
|
||||
<span class="ml-auto flex items-center gap-2 text-xs text-fg-subtle">
|
||||
<span>↑2</span>
|
||||
<span class="text-amber-400">● {{ t('mDirty') }}</span>
|
||||
<span class="text-warn">● {{ t('mDirty') }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<p class="truncate font-mono text-xs text-zinc-500">~/code/arboretum/.wt/feat-auth</p>
|
||||
<p class="truncate font-mono text-xs text-fg-subtle">~/code/arboretum/.wt/feat-auth</p>
|
||||
<div class="mt-1 flex items-center gap-2">
|
||||
<span class="inline-flex items-center gap-1 rounded bg-zinc-800/60 px-1.5 py-0.5">
|
||||
<span class="inline-flex items-center gap-1 rounded bg-sky-950 px-1.5 py-0.5 text-[11px] font-medium text-sky-300">
|
||||
<span class="h-1.5 w-1.5 animate-pulse-sky rounded-full bg-sky-300"></span>{{ t('busy') }}
|
||||
<span class="inline-flex items-center gap-1 rounded bg-surface-2/60 px-1.5 py-0.5">
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-info/15 px-1.5 py-0.5 text-[11px] font-medium text-info">
|
||||
<span class="h-1.5 w-1.5 animate-pulse-sky rounded-full bg-info"></span>{{ t('busy') }}
|
||||
</span>
|
||||
<span class="font-mono text-[11px] text-zinc-400">claude</span>
|
||||
<span class="font-mono text-[11px] text-fg-muted">claude</span>
|
||||
</span>
|
||||
</div>
|
||||
<!-- actions git de la carte : commit / push (fidèle au WorktreeCard) -->
|
||||
<!-- actions git de la carte : commit / push -->
|
||||
<div class="mt-1 flex items-center gap-1.5">
|
||||
<span class="inline-flex items-center rounded border border-zinc-700 bg-zinc-800 px-1.5 py-0.5 text-[11px] font-medium text-zinc-300">{{ t('mCommit') }}</span>
|
||||
<span class="inline-flex items-center rounded border border-zinc-700 bg-zinc-800 px-1.5 py-0.5 text-[11px] font-medium text-zinc-300">{{ t('mPush') }} ↑2</span>
|
||||
<span class="inline-flex items-center rounded border border-border bg-surface-2 px-1.5 py-0.5 text-[11px] font-medium text-fg-muted">{{ t('mCommit') }}</span>
|
||||
<span class="inline-flex items-center rounded border border-border bg-surface-2 px-1.5 py-0.5 text-[11px] font-medium text-fg-muted">{{ t('mPush') }} ↑2</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- b) grille multi-terminaux (fidèle à TerminalCell : badge d'état + titre repo · branche + commande) -->
|
||||
<!-- b) grille multi-terminaux (badge d'état + titre repo · branche + commande) -->
|
||||
<div v-reveal class="flex flex-wrap-reverse items-center gap-12">
|
||||
<div class="min-w-[280px] flex-[1_1_440px]">
|
||||
<div class="grid grid-cols-2 gap-2.5 overflow-hidden rounded-xl border border-zinc-800 bg-[#0c0c0e] p-3 shadow-card">
|
||||
<div class="overflow-hidden rounded-lg border border-zinc-800 bg-[#09090b]">
|
||||
<div class="flex items-center gap-2 border-b border-zinc-800 px-2 py-1.5">
|
||||
<span class="inline-flex items-center gap-1 rounded bg-sky-950 px-1.5 py-0.5 text-[10px] font-medium text-sky-300"><span class="h-1.5 w-1.5 animate-pulse-sky rounded-full bg-sky-300"></span>{{ t('busy') }}</span>
|
||||
<span class="truncate font-mono text-[11px] text-zinc-300">api · feat/auth</span>
|
||||
<span class="ml-auto shrink-0 font-mono text-[11px] text-zinc-500">claude</span>
|
||||
<div class="grid grid-cols-2 gap-2.5 overflow-hidden rounded-xl border border-border bg-surface-1 p-3 shadow-card">
|
||||
<div class="overflow-hidden rounded-lg border border-border bg-surface-0">
|
||||
<div class="flex items-center gap-2 border-b border-border px-2 py-1.5">
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-info/15 px-1.5 py-0.5 text-[10px] font-medium text-info"><span class="h-1.5 w-1.5 animate-pulse-sky rounded-full bg-info"></span>{{ t('busy') }}</span>
|
||||
<span class="truncate font-mono text-[11px] text-fg-muted">api · feat/auth</span>
|
||||
<span class="ml-auto shrink-0 font-mono text-[11px] text-fg-subtle">claude</span>
|
||||
</div>
|
||||
<div class="p-[11px] font-mono text-[11px] leading-[1.75] text-zinc-300">
|
||||
<div class="text-emerald-400">● Edit session.ts</div>
|
||||
<div class="text-emerald-400">+ const token = sign(...)</div>
|
||||
<div class="text-red-400">- legacy cookie auth</div>
|
||||
<div class="text-zinc-500">refactoring guards…</div>
|
||||
<div class="p-[11px] font-mono text-[11px] leading-[1.75] text-fg-muted">
|
||||
<div class="text-accent">● Edit session.ts</div>
|
||||
<div class="text-accent">+ const token = sign(...)</div>
|
||||
<div class="text-danger">- legacy cookie auth</div>
|
||||
<div class="text-fg-subtle">refactoring guards…</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-hidden rounded-lg border border-zinc-800 bg-[#09090b]">
|
||||
<div class="flex items-center gap-2 border-b border-zinc-800 px-2 py-1.5">
|
||||
<span class="inline-flex items-center gap-1 rounded bg-emerald-950 px-1.5 py-0.5 text-[10px] font-medium text-emerald-400"><span class="h-1.5 w-1.5 rounded-full bg-emerald-400"></span>{{ t('idle') }}</span>
|
||||
<span class="truncate font-mono text-[11px] text-zinc-300">web · fix/cart</span>
|
||||
<span class="ml-auto shrink-0 font-mono text-[11px] text-zinc-500">claude</span>
|
||||
<div class="overflow-hidden rounded-lg border border-border bg-surface-0">
|
||||
<div class="flex items-center gap-2 border-b border-border px-2 py-1.5">
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-accent/15 px-1.5 py-0.5 text-[10px] font-medium text-accent"><span class="h-1.5 w-1.5 rounded-full bg-accent"></span>{{ t('idle') }}</span>
|
||||
<span class="truncate font-mono text-[11px] text-fg-muted">web · fix/cart</span>
|
||||
<span class="ml-auto shrink-0 font-mono text-[11px] text-fg-subtle">claude</span>
|
||||
</div>
|
||||
<div class="p-[11px] font-mono text-[11px] leading-[1.75] text-zinc-300">
|
||||
<div class="text-emerald-400">✓ all tests passing</div>
|
||||
<div class="text-zinc-500">24 passed, 0 failed</div>
|
||||
<div class="p-[11px] font-mono text-[11px] leading-[1.75] text-fg-muted">
|
||||
<div class="text-accent">✓ all tests passing</div>
|
||||
<div class="text-fg-subtle">24 passed, 0 failed</div>
|
||||
<div>
|
||||
<span class="text-emerald-400">$</span>
|
||||
<span class="ml-1 inline-block h-[11px] w-1.5 animate-blink align-middle bg-zinc-200"></span>
|
||||
<span class="text-accent">$</span>
|
||||
<span class="ml-1 inline-block h-[11px] w-1.5 animate-blink bg-fg align-middle"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-hidden rounded-lg border border-zinc-800 bg-[#09090b]">
|
||||
<div class="flex items-center gap-2 border-b border-zinc-800 px-2 py-1.5">
|
||||
<span class="inline-flex items-center gap-1 rounded bg-sky-950 px-1.5 py-0.5 text-[10px] font-medium text-sky-300"><span class="h-1.5 w-1.5 animate-pulse-sky rounded-full bg-sky-300"></span>{{ t('busy') }}</span>
|
||||
<span class="truncate font-mono text-[11px] text-zinc-300">db · refactor</span>
|
||||
<span class="ml-auto shrink-0 font-mono text-[11px] text-zinc-500">claude</span>
|
||||
<div class="overflow-hidden rounded-lg border border-border bg-surface-0">
|
||||
<div class="flex items-center gap-2 border-b border-border px-2 py-1.5">
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-info/15 px-1.5 py-0.5 text-[10px] font-medium text-info"><span class="h-1.5 w-1.5 animate-pulse-sky rounded-full bg-info"></span>{{ t('busy') }}</span>
|
||||
<span class="truncate font-mono text-[11px] text-fg-muted">db · refactor</span>
|
||||
<span class="ml-auto shrink-0 font-mono text-[11px] text-fg-subtle">claude</span>
|
||||
</div>
|
||||
<div class="p-[11px] font-mono text-[11px] leading-[1.75] text-zinc-300">
|
||||
<div class="text-emerald-400">● Write migration</div>
|
||||
<div class="text-zinc-500">0003_add_index.sql</div>
|
||||
<div class="text-zinc-500">applying to schema…</div>
|
||||
<div class="p-[11px] font-mono text-[11px] leading-[1.75] text-fg-muted">
|
||||
<div class="text-accent">● Write migration</div>
|
||||
<div class="text-fg-subtle">0003_add_index.sql</div>
|
||||
<div class="text-fg-subtle">applying to schema…</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-hidden rounded-lg border border-zinc-800 bg-[#09090b]">
|
||||
<div class="flex items-center gap-2 border-b border-zinc-800 px-2 py-1.5">
|
||||
<span class="inline-flex items-center gap-1 rounded bg-amber-950 px-1.5 py-0.5 text-[10px] font-medium text-amber-300"><span class="h-1.5 w-1.5 animate-pulse-amber rounded-full bg-amber-300"></span>{{ t('waiting') }}</span>
|
||||
<span class="truncate font-mono text-[11px] text-zinc-300">docs · api</span>
|
||||
<span class="ml-auto shrink-0 font-mono text-[11px] text-zinc-500">claude</span>
|
||||
<div class="overflow-hidden rounded-lg border border-border bg-surface-0">
|
||||
<div class="flex items-center gap-2 border-b border-border px-2 py-1.5">
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-warn/15 px-1.5 py-0.5 text-[10px] font-medium text-warn"><span class="h-1.5 w-1.5 animate-pulse-amber rounded-full bg-warn"></span>{{ t('waiting') }}</span>
|
||||
<span class="truncate font-mono text-[11px] text-fg-muted">docs · api</span>
|
||||
<span class="ml-auto shrink-0 font-mono text-[11px] text-fg-subtle">claude</span>
|
||||
</div>
|
||||
<div class="p-[11px] font-mono text-[11px] leading-[1.75] text-zinc-300">
|
||||
<div class="text-emerald-400">● Read README.md</div>
|
||||
<div class="text-amber-300">Permission required</div>
|
||||
<div class="text-zinc-500">waiting for input…</div>
|
||||
<div class="p-[11px] font-mono text-[11px] leading-[1.75] text-fg-muted">
|
||||
<div class="text-accent">● Read README.md</div>
|
||||
<div class="text-warn">Permission required</div>
|
||||
<div class="text-fg-subtle">waiting for input…</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="min-w-[280px] flex-[1_1_360px]">
|
||||
<div class="mb-3 font-mono text-xs uppercase tracking-[0.12em] text-emerald-400">{{ t('scBKicker') }}</div>
|
||||
<h2 class="m-0 mb-4 text-[clamp(26px,3vw,34px)] font-semibold leading-[1.15] tracking-[-0.025em] text-zinc-50">
|
||||
<div class="mb-3 font-mono text-xs uppercase tracking-[0.12em] text-accent">{{ t('scBKicker') }}</div>
|
||||
<h2 class="m-0 mb-4 text-[clamp(26px,3vw,34px)] font-semibold leading-[1.15] tracking-[-0.025em] text-fg">
|
||||
{{ t('scBTitle') }}
|
||||
</h2>
|
||||
<p class="m-0 text-[16.5px] leading-[1.6] text-zinc-400">{{ t('scBBody') }}</p>
|
||||
<p class="m-0 text-[16.5px] leading-[1.6] text-fg-muted">{{ t('scBBody') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- c) mobile : push + réponse au dialogue (fidèle à DialogPrompt) + barre d'onglets -->
|
||||
<!-- c) mobile : push + réponse au dialogue + barre d'onglets -->
|
||||
<div v-reveal class="flex flex-wrap items-center gap-12">
|
||||
<div class="min-w-[280px] flex-[1_1_380px]">
|
||||
<div class="mb-3 font-mono text-xs uppercase tracking-[0.12em] text-emerald-400">{{ t('scCKicker') }}</div>
|
||||
<h2 class="m-0 mb-4 text-[clamp(26px,3vw,34px)] font-semibold leading-[1.15] tracking-[-0.025em] text-zinc-50">
|
||||
<div class="mb-3 font-mono text-xs uppercase tracking-[0.12em] text-accent">{{ t('scCKicker') }}</div>
|
||||
<h2 class="m-0 mb-4 text-[clamp(26px,3vw,34px)] font-semibold leading-[1.15] tracking-[-0.025em] text-fg">
|
||||
{{ t('scCTitle') }}
|
||||
</h2>
|
||||
<p class="m-0 text-[16.5px] leading-[1.6] text-zinc-400">{{ t('scCBody') }}</p>
|
||||
<p class="m-0 text-[16.5px] leading-[1.6] text-fg-muted">{{ t('scCBody') }}</p>
|
||||
</div>
|
||||
<div class="flex min-w-[280px] flex-[1_1_320px] justify-center">
|
||||
<div class="relative w-[300px] overflow-hidden rounded-[40px] border-[9px] border-zinc-900 bg-zinc-950 shadow-phone">
|
||||
<div class="relative w-[300px] overflow-hidden rounded-[40px] border-[9px] border-surface-1 bg-surface-0 shadow-phone">
|
||||
<div class="flex h-[30px] items-center justify-center">
|
||||
<div class="h-1.5 w-[90px] rounded-full bg-zinc-800"></div>
|
||||
<div class="h-1.5 w-[90px] rounded-full bg-surface-2"></div>
|
||||
</div>
|
||||
<div class="px-3 pb-[64px]">
|
||||
<!-- toast push -->
|
||||
<div class="flex items-start gap-[11px] rounded-[15px] border border-[rgba(120,53,15,0.5)] bg-[#0c0c0e] p-[13px] shadow-toast-sm">
|
||||
<span class="inline-flex h-[30px] w-[30px] flex-none items-center justify-center rounded-lg bg-emerald-500/[0.14]">
|
||||
<div class="flex items-start gap-[11px] rounded-[15px] border border-border bg-surface-1 p-[13px] shadow-toast-sm">
|
||||
<span class="inline-flex h-[30px] w-[30px] flex-none items-center justify-center rounded-lg bg-accent/15">
|
||||
<img src="/assets/arboretum-mark.png" alt="" width="18" height="18" class="h-[18px] w-[18px] object-contain" />
|
||||
</span>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="font-mono text-[11px] text-emerald-400">Arboretum</span>
|
||||
<span class="text-[10px] text-zinc-600">now</span>
|
||||
<span class="font-mono text-[11px] text-accent">Arboretum</span>
|
||||
<span class="text-[10px] text-fg-subtle">now</span>
|
||||
</div>
|
||||
<div class="mt-[3px] text-[13.5px] font-semibold text-zinc-50">{{ t('toastTitle') }}</div>
|
||||
<div class="text-xs leading-[1.45] text-zinc-400">{{ t('toastBody') }}</div>
|
||||
<div class="mt-[3px] text-[13.5px] font-semibold text-fg">{{ t('toastTitle') }}</div>
|
||||
<div class="text-xs leading-[1.45] text-fg-muted">{{ t('toastBody') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- carte « À traiter » + DialogPrompt -->
|
||||
<div class="mt-[14px] flex flex-col gap-2 rounded-[15px] border border-amber-900/50 bg-amber-950/20 p-2.5">
|
||||
<div class="mt-[14px] flex flex-col gap-2 rounded-[15px] border border-warn/40 bg-warn/10 p-2.5">
|
||||
<div class="flex items-center gap-2">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#fbbf24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" /><path d="M12 9v4" /><path d="M12 17h.01" /></svg>
|
||||
<span class="text-[13px] font-semibold text-amber-200">{{ t('mAttn') }}</span>
|
||||
<span class="rounded-full bg-amber-500/20 px-1.5 text-[11px] font-semibold text-amber-300">1</span>
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="var(--color-warn)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" /><path d="M12 9v4" /><path d="M12 17h.01" /></svg>
|
||||
<span class="text-[13px] font-semibold text-warn">{{ t('mAttn') }}</span>
|
||||
<span class="rounded-full bg-warn/20 px-1.5 text-[11px] font-semibold text-warn">1</span>
|
||||
</div>
|
||||
<span class="font-mono text-[11px] text-amber-300/80">api · feat/auth</span>
|
||||
<div class="rounded-lg border border-amber-900/60 bg-amber-950/30 p-2.5">
|
||||
<span class="inline-flex items-center rounded bg-amber-900 px-1.5 py-0.5 text-[11px] font-medium text-amber-200">{{ t('mPermission') }}</span>
|
||||
<div class="mt-1.5 text-[13px] leading-[1.4] text-amber-100">{{ t('dlgQ') }}</div>
|
||||
<span class="font-mono text-[11px] text-warn/80">api · feat/auth</span>
|
||||
<div class="rounded-lg border border-warn/40 bg-warn/10 p-2.5">
|
||||
<span class="inline-flex items-center rounded-full bg-warn/15 px-1.5 py-0.5 font-mono text-[11px] text-warn">{{ t('mPermission') }}</span>
|
||||
<div class="mt-1.5 text-[13px] leading-[1.4] text-fg">{{ t('dlgQ') }}</div>
|
||||
<div class="mt-2 flex flex-col gap-1.5">
|
||||
<span
|
||||
v-for="(op, i) in dlgOpts"
|
||||
:key="i"
|
||||
class="flex items-center gap-2 rounded-lg border border-zinc-700 bg-zinc-800 px-2.5 py-2 text-[13px] font-medium text-zinc-200"
|
||||
class="flex items-center gap-2 rounded-lg border border-border bg-surface-2 px-2.5 py-2 text-[13px] font-medium text-fg"
|
||||
>
|
||||
<span class="font-mono text-amber-400">{{ i + 1 }}</span> {{ op }}
|
||||
<span class="font-mono text-warn">{{ i + 1 }}</span> {{ op }}
|
||||
</span>
|
||||
<span class="rounded-lg border border-red-900 bg-red-950 px-2.5 py-2 text-center text-[13px] font-medium text-red-300">{{ t('mDeny') }}</span>
|
||||
<span class="rounded-lg border border-danger/40 bg-danger/10 px-2.5 py-2 text-center text-[13px] font-medium text-danger">{{ t('mDeny') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- barre d'onglets mobile (fidèle à MobileTabBar) -->
|
||||
<div class="absolute inset-x-0 bottom-0 flex border-t border-zinc-800 bg-zinc-950/95 backdrop-blur">
|
||||
<div class="flex flex-1 flex-col items-center gap-0.5 py-2 text-zinc-500">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="18" height="18" x="3" y="3" rx="2" /><path d="M3 9h18" /><path d="M9 21V9" /></svg>
|
||||
<span class="text-[10px]">{{ t('mIde') }}</span>
|
||||
<!-- barre d'onglets mobile (fidèle a IdeShell : explorer / editor / terminal / git / sessions / groups) -->
|
||||
<div class="absolute inset-x-0 bottom-0 flex border-t border-border bg-surface-0/95 backdrop-blur">
|
||||
<div class="flex flex-1 flex-col items-center gap-0.5 py-2 text-fg-subtle">
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="18" height="18" x="3" y="3" rx="2" /><path d="M3 9h18" /><path d="M9 21V9" /></svg>
|
||||
<span class="text-[9px]">{{ t('mExplorer') }}</span>
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col items-center gap-0.5 py-2 text-zinc-500">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="6" x2="6" y1="3" y2="15" /><circle cx="18" cy="6" r="3" /><circle cx="6" cy="18" r="3" /><path d="M18 9a9 9 0 0 1-9 9" /></svg>
|
||||
<span class="text-[10px]">{{ t('mWorktrees') }}</span>
|
||||
<div class="flex flex-1 flex-col items-center gap-0.5 py-2 text-fg-subtle">
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" /><path d="M14 2v5h5" /><path d="m10 13-2 2 2 2" /><path d="m14 17 2-2-2-2" /></svg>
|
||||
<span class="text-[9px]">{{ t('mEditor') }}</span>
|
||||
</div>
|
||||
<div class="relative flex flex-1 flex-col items-center gap-0.5 py-2 text-emerald-400">
|
||||
<div class="flex flex-1 flex-col items-center gap-0.5 py-2 text-fg-subtle">
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7 11 2-2-2-2" /><path d="M11 13h4" /><rect width="18" height="18" x="3" y="3" rx="2" /></svg>
|
||||
<span class="text-[9px]">{{ t('mTerminal') }}</span>
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col items-center gap-0.5 py-2 text-fg-subtle">
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="18" cy="18" r="3" /><circle cx="6" cy="6" r="3" /><path d="M13 6h3a2 2 0 0 1 2 2v7" /><path d="M11 18H8a2 2 0 0 1-2-2V9" /></svg>
|
||||
<span class="text-[9px]">{{ t('mGit') }}</span>
|
||||
</div>
|
||||
<div class="relative flex flex-1 flex-col items-center gap-0.5 py-2 text-accent">
|
||||
<span class="relative">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7 11 2-2-2-2" /><path d="M11 13h4" /><rect width="18" height="18" x="3" y="3" rx="2" ry="2" /></svg>
|
||||
<span class="absolute -right-1.5 -top-1.5 flex h-3.5 min-w-3.5 items-center justify-center rounded-full bg-amber-500 px-1 text-[9px] font-semibold text-amber-950">1</span>
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 12h-4l-3 9L9 3l-3 9H2" /></svg>
|
||||
<span class="absolute -top-1.5 -right-1.5 flex h-3.5 min-w-3.5 items-center justify-center rounded-full bg-warn px-1 text-[9px] font-semibold text-zinc-950">1</span>
|
||||
</span>
|
||||
<span class="text-[10px]">{{ t('mSessions') }}</span>
|
||||
<span class="text-[9px]">{{ t('mSessions') }}</span>
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col items-center gap-0.5 py-2 text-zinc-500">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z" /><path d="M7 16.5 12 13.5" /><path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z" /><path d="m17 16.5 4.74-2.85" /><path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0z" /></svg>
|
||||
<span class="text-[10px]">{{ t('mGroups') }}</span>
|
||||
</div>
|
||||
<div class="flex flex-1 flex-col items-center gap-0.5 py-2 text-zinc-500">
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="4" x2="20" y1="12" y2="12" /><line x1="4" x2="20" y1="6" y2="6" /><line x1="4" x2="20" y1="18" y2="18" /></svg>
|
||||
<span class="text-[10px]">{{ t('mMore') }}</span>
|
||||
<div class="flex flex-1 flex-col items-center gap-0.5 py-2 text-fg-subtle">
|
||||
<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z" /><path d="M7 16.5 12 13.5" /><path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z" /><path d="m17 16.5 4.74-2.85" /><path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0z" /></svg>
|
||||
<span class="text-[9px]">{{ t('mGroups') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
64
packages/site/src/components/ThemeToggle.vue
Normal file
64
packages/site/src/components/ThemeToggle.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<script setup lang="ts">
|
||||
// Bascule de thème du site (dark -> light -> system), alignée sur l'app.
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { themeMode, cycleTheme } from '../lib/theme';
|
||||
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex h-9 w-9 items-center justify-center rounded-lg border border-border text-fg-muted transition-colors hover:border-accent hover:text-accent"
|
||||
:title="t('themeToggle')"
|
||||
:aria-label="t('themeToggle')"
|
||||
@click="cycleTheme()"
|
||||
>
|
||||
<!-- soleil (clair) -->
|
||||
<svg
|
||||
v-if="themeMode === 'light'"
|
||||
viewBox="0 0 24 24"
|
||||
width="17"
|
||||
height="17"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.8"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<circle cx="12" cy="12" r="4" />
|
||||
<path
|
||||
d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"
|
||||
/>
|
||||
</svg>
|
||||
<!-- écran (système) -->
|
||||
<svg
|
||||
v-else-if="themeMode === 'system'"
|
||||
viewBox="0 0 24 24"
|
||||
width="17"
|
||||
height="17"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.8"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect x="2" y="3" width="20" height="14" rx="2" />
|
||||
<path d="M8 21h8M12 17v4" />
|
||||
</svg>
|
||||
<!-- lune (sombre) -->
|
||||
<svg
|
||||
v-else
|
||||
viewBox="0 0 24 24"
|
||||
width="17"
|
||||
height="17"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.8"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79Z" />
|
||||
</svg>
|
||||
</button>
|
||||
</template>
|
||||
@@ -7,45 +7,45 @@ const { t } = useI18n();
|
||||
<section class="mx-auto max-w-[1200px] scroll-mt-[84px] px-6 py-[92px]">
|
||||
<div
|
||||
v-reveal
|
||||
class="flex flex-wrap items-center gap-12 rounded-[18px] border border-zinc-800 p-[clamp(28px,4vw,52px)]"
|
||||
style="background: linear-gradient(180deg, rgba(24, 24, 27, 0.55), rgba(12, 12, 14, 0.55))"
|
||||
class="flex flex-wrap items-center gap-12 rounded-[18px] border border-border p-[clamp(28px,4vw,52px)]"
|
||||
style="background: linear-gradient(180deg, color-mix(in srgb, var(--color-surface-1) 55%, transparent), color-mix(in srgb, var(--color-surface-0) 55%, transparent))"
|
||||
>
|
||||
<div class="min-w-[280px] flex-[1_1_360px]">
|
||||
<div class="mb-3 font-mono text-xs uppercase tracking-[0.12em] text-emerald-400">{{ t('grpKicker') }}</div>
|
||||
<h2 class="m-0 mb-4 text-[clamp(26px,3vw,36px)] font-semibold leading-[1.15] tracking-[-0.025em] text-zinc-50">
|
||||
<div class="mb-3 font-mono text-xs uppercase tracking-[0.12em] text-accent">{{ t('grpKicker') }}</div>
|
||||
<h2 class="m-0 mb-4 text-[clamp(26px,3vw,36px)] font-semibold leading-[1.15] tracking-[-0.025em] text-fg">
|
||||
{{ t('grpTitle') }}
|
||||
</h2>
|
||||
<p class="m-0 text-[16.5px] leading-[1.6] text-zinc-400">{{ t('grpBody') }}</p>
|
||||
<p class="m-0 text-[16.5px] leading-[1.6] text-fg-muted">{{ t('grpBody') }}</p>
|
||||
</div>
|
||||
<div class="flex min-w-[280px] flex-[1_1_360px] justify-center">
|
||||
<div class="flex w-full max-w-[380px] items-center">
|
||||
<div class="flex flex-none flex-col items-center gap-2">
|
||||
<div
|
||||
class="inline-flex h-[62px] w-[62px] items-center justify-center rounded-[15px] border border-emerald-500/35 bg-emerald-500/[0.12] text-emerald-400"
|
||||
class="inline-flex h-[62px] w-[62px] items-center justify-center rounded-[15px] border border-accent/35 bg-accent/[0.12] text-accent"
|
||||
>
|
||||
<svg width="26" height="26" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.97 12.92A2 2 0 0 0 2 14.63v3.24a2 2 0 0 0 .97 1.71l3 1.8a2 2 0 0 0 2.06 0L12 19v-5.5l-5-3-4.03 2.42Z" /><path d="M7 16.5 12 13.5" /><path d="M12 13.5V19l3.97 2.38a2 2 0 0 0 2.06 0l3-1.8a2 2 0 0 0 .97-1.71v-3.24a2 2 0 0 0-.97-1.71L17 10.5l-5 3Z" /><path d="m17 16.5 4.74-2.85" /><path d="M7.97 4.42A2 2 0 0 0 7 6.13v4.37l5 3 5-3V6.13a2 2 0 0 0-.97-1.71l-3-1.8a2 2 0 0 0-2.06 0z" /></svg>
|
||||
</div>
|
||||
<span class="font-mono text-[11px] text-zinc-300">{{ t('grpHub') }}</span>
|
||||
<span class="font-mono text-[11px] text-fg-muted">{{ t('grpHub') }}</span>
|
||||
</div>
|
||||
<div class="relative h-[96px] flex-1">
|
||||
<svg width="100%" height="100%" viewBox="0 0 100 96" preserveAspectRatio="none" class="absolute inset-0" aria-hidden="true">
|
||||
<path d="M0 48 H40 V14 H100" fill="none" stroke="rgba(16,185,129,.45)" stroke-width="1.4" />
|
||||
<path d="M0 48 H40 V48 H100" fill="none" stroke="rgba(16,185,129,.45)" stroke-width="1.4" />
|
||||
<path d="M0 48 H40 V82 H100" fill="none" stroke="rgba(16,185,129,.45)" stroke-width="1.4" />
|
||||
<path d="M0 48 H40 V14 H100" fill="none" stroke="var(--color-accent)" stroke-width="1.4" />
|
||||
<path d="M0 48 H40 V48 H100" fill="none" stroke="var(--color-accent)" stroke-width="1.4" />
|
||||
<path d="M0 48 H40 V82 H100" fill="none" stroke="var(--color-accent)" stroke-width="1.4" />
|
||||
</svg>
|
||||
</div>
|
||||
<div class="flex flex-none flex-col gap-2.5">
|
||||
<div class="flex items-center gap-2 rounded-lg border border-zinc-800 bg-[#0c0c0e] px-3 py-2">
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-sky-300"></span>
|
||||
<span class="font-mono text-[11px] text-zinc-300">api</span>
|
||||
<div class="flex items-center gap-2 rounded-lg border border-border bg-surface-0 px-3 py-2">
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-info"></span>
|
||||
<span class="font-mono text-[11px] text-fg-muted">api</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 rounded-lg border border-zinc-800 bg-[#0c0c0e] px-3 py-2">
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-sky-300"></span>
|
||||
<span class="font-mono text-[11px] text-zinc-300">web</span>
|
||||
<div class="flex items-center gap-2 rounded-lg border border-border bg-surface-0 px-3 py-2">
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-info"></span>
|
||||
<span class="font-mono text-[11px] text-fg-muted">web</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 rounded-lg border border-zinc-800 bg-[#0c0c0e] px-3 py-2">
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-sky-300"></span>
|
||||
<span class="font-mono text-[11px] text-zinc-300">sdk</span>
|
||||
<div class="flex items-center gap-2 rounded-lg border border-border bg-surface-0 px-3 py-2">
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-info"></span>
|
||||
<span class="font-mono text-[11px] text-fg-muted">sdk</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { useI18n } from 'vue-i18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// Lignes du diff (fidele a DiffViewer : add -> emerald-950/40, del -> rose-950/40).
|
||||
// Lignes du diff (fidèle a DiffViewer : fond teinté add/del, texte neutre).
|
||||
type DiffLine = { o: string; n: string; type: 'ctx' | 'add' | 'del' | 'hunk'; text: string };
|
||||
const diffLines: DiffLine[] = [
|
||||
{ o: '', n: '', type: 'hunk', text: '@@ -12,7 +12,9 @@ export function session(' },
|
||||
@@ -16,9 +16,9 @@ const diffLines: DiffLine[] = [
|
||||
];
|
||||
|
||||
function rowClass(type: DiffLine['type']): string {
|
||||
if (type === 'add') return 'bg-emerald-950/40 text-emerald-200';
|
||||
if (type === 'del') return 'bg-rose-950/40 text-rose-200';
|
||||
return 'text-zinc-300';
|
||||
if (type === 'add') return 'diff-add';
|
||||
if (type === 'del') return 'diff-del';
|
||||
return 'diff-ctx';
|
||||
}
|
||||
function marker(type: DiffLine['type']): string {
|
||||
return type === 'add' ? '+' : type === 'del' ? '-' : ' ';
|
||||
@@ -28,30 +28,31 @@ function marker(type: DiffLine['type']): string {
|
||||
<template>
|
||||
<section id="workspace" class="mx-auto max-w-[1200px] scroll-mt-[84px] px-6 pb-[100px]">
|
||||
<div v-reveal class="mb-[40px] max-w-[760px]">
|
||||
<div class="mb-3 font-mono text-xs uppercase tracking-[0.12em] text-emerald-400">{{ t('wsKicker') }}</div>
|
||||
<h2 class="m-0 mb-4 text-[clamp(26px,3vw,38px)] font-semibold leading-[1.15] tracking-[-0.025em] text-zinc-50">
|
||||
<div class="mb-3 font-mono text-xs uppercase tracking-[0.12em] text-accent">{{ t('wsKicker') }}</div>
|
||||
<h2 class="m-0 mb-4 text-[clamp(26px,3vw,38px)] font-semibold leading-[1.15] tracking-[-0.025em] text-fg">
|
||||
{{ t('wsTitle') }}
|
||||
</h2>
|
||||
<p class="m-0 text-[16.5px] leading-[1.6] text-zinc-400">{{ t('wsBody') }}</p>
|
||||
<p class="m-0 text-[16.5px] leading-[1.6] text-fg-muted">{{ t('wsBody') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- maquette IDE multi-projet : barre d'activite, arbre unifie, onglets d'editeur, dock bas -->
|
||||
<div v-reveal class="overflow-hidden rounded-[14px] border border-zinc-800 bg-[#09090b] shadow-card">
|
||||
<div v-reveal class="overflow-hidden rounded-[14px] border border-border bg-surface-0 shadow-card">
|
||||
<!-- barre superieure -->
|
||||
<header class="flex items-center gap-3 border-b border-zinc-800 px-3 py-2">
|
||||
<span class="flex items-center gap-2 text-sm font-medium text-zinc-200">
|
||||
<header class="flex items-center gap-3 border-b border-border px-3 py-2">
|
||||
<span class="flex items-center gap-2 text-sm font-medium text-fg">
|
||||
<img src="/assets/arboretum-mark.png" alt="" width="16" height="16" class="h-4 w-4 object-contain" />
|
||||
Arboretum
|
||||
</span>
|
||||
<span class="ml-auto flex items-center gap-1.5 text-[11px] text-zinc-500">
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-amber-400"></span>{{ t('waiting') }}
|
||||
<span class="ml-auto flex items-center gap-1.5 text-[11px] text-fg-subtle">
|
||||
<span class="h-1.5 w-1.5 rounded-full bg-warn"></span>{{ t('waiting') }}
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<div class="flex">
|
||||
<!-- barre d'activite -->
|
||||
<nav class="flex w-11 flex-none flex-col items-center gap-1 border-r border-zinc-800 bg-zinc-900/50 py-2 text-zinc-500">
|
||||
<span class="flex h-9 w-9 items-center justify-center rounded-lg bg-zinc-700 text-zinc-100" :title="t('wsFiles')">
|
||||
<nav class="flex w-11 flex-none flex-col items-center gap-1 border-r border-border bg-surface-1 py-2 text-fg-subtle">
|
||||
<span class="relative flex h-9 w-9 items-center justify-center rounded-lg bg-surface-2 text-accent" :title="t('wsFiles')">
|
||||
<span class="absolute top-1/2 left-0 h-5 w-0.5 -translate-y-1/2 rounded-full bg-accent"></span>
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-2.5a1 1 0 0 1-.8-.4l-.9-1.2A1 1 0 0 0 14 3h-3a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z" /><path d="M3 5a2 2 0 0 0 2 2h3" /><path d="M3 3v13a2 2 0 0 0 2 2h3" /></svg>
|
||||
</span>
|
||||
<span class="flex h-9 w-9 items-center justify-center rounded-lg" :title="t('wsChanges')">
|
||||
@@ -63,39 +64,39 @@ function marker(type: DiffLine['type']): string {
|
||||
</nav>
|
||||
|
||||
<!-- arbre unifie : plusieurs projets a la fois -->
|
||||
<aside class="hidden w-[248px] flex-none flex-col border-r border-zinc-800 md:flex">
|
||||
<div class="px-3 py-2 text-[11px] font-semibold uppercase tracking-wide text-zinc-500">Projects</div>
|
||||
<aside class="hidden w-[248px] flex-none flex-col border-r border-border md:flex">
|
||||
<div class="label-mono px-3 py-2">Projects</div>
|
||||
<div class="px-1 pb-2 font-mono text-xs">
|
||||
<!-- projet api (ouvert) -->
|
||||
<div class="flex items-center gap-1 rounded py-0.5 pl-1.5 pr-2 text-zinc-200">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-zinc-500" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<div class="flex items-center gap-1 rounded py-0.5 pr-2 pl-1.5 text-fg">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-fg-subtle" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<span class="font-medium">api</span>
|
||||
</div>
|
||||
<!-- worktree feat/auth (actif) -->
|
||||
<div class="flex items-center gap-1 rounded bg-zinc-800/70 py-0.5 pl-[18px] pr-2 text-zinc-100">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-zinc-500" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-zinc-500" aria-hidden="true"><line x1="6" x2="6" y1="3" y2="15" /><circle cx="18" cy="6" r="3" /><circle cx="6" cy="18" r="3" /><path d="M18 9a9 9 0 0 1-9 9" /></svg>
|
||||
<span class="truncate">feat/auth</span>
|
||||
<div class="flex items-center gap-1 rounded bg-surface-2 py-0.5 pr-2 pl-[18px] text-fg">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-fg-subtle" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="var(--color-accent)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0" aria-hidden="true"><line x1="6" x2="6" y1="3" y2="15" /><circle cx="18" cy="6" r="3" /><circle cx="6" cy="18" r="3" /><path d="M18 9a9 9 0 0 1-9 9" /></svg>
|
||||
<span class="truncate text-accent">feat/auth</span>
|
||||
</div>
|
||||
<!-- session claude corrélée -->
|
||||
<div class="flex items-center gap-1.5 rounded py-0.5 pl-[38px] pr-2 text-zinc-300">
|
||||
<span class="inline-flex items-center gap-1 rounded bg-sky-950 px-1.5 py-[1px] text-[10px] text-sky-300"><span class="h-1.5 w-1.5 animate-pulse-sky rounded-full bg-sky-300"></span>{{ t('busy') }}</span>
|
||||
<div class="flex items-center gap-1.5 rounded py-0.5 pr-2 pl-[38px] text-fg-muted">
|
||||
<span class="inline-flex items-center gap-1 rounded-full bg-info/15 px-1.5 py-[1px] text-[10px] text-info"><span class="h-1.5 w-1.5 animate-pulse-sky rounded-full bg-info"></span>{{ t('busy') }}</span>
|
||||
<span class="truncate">claude</span>
|
||||
</div>
|
||||
<!-- fichiers du worktree -->
|
||||
<div class="flex items-center justify-between gap-1 rounded bg-zinc-800 py-0.5 pl-[30px] pr-2 text-zinc-100">
|
||||
<span class="truncate">session.ts</span><span class="font-bold text-amber-400">M</span>
|
||||
<div class="flex items-center justify-between gap-1 rounded bg-surface-2 py-0.5 pr-2 pl-[30px] text-fg">
|
||||
<span class="truncate">session.ts</span><span class="font-bold text-warn">M</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between gap-1 rounded py-0.5 pl-[30px] pr-2 text-zinc-300">
|
||||
<span class="truncate">token.ts</span><span class="font-bold text-emerald-400">A</span>
|
||||
<div class="flex items-center justify-between gap-1 rounded py-0.5 pr-2 pl-[30px] text-fg-muted">
|
||||
<span class="truncate">token.ts</span><span class="font-bold text-accent">A</span>
|
||||
</div>
|
||||
<!-- projet web (replie) -->
|
||||
<div class="flex items-center gap-1 rounded py-0.5 pl-1.5 pr-2 text-zinc-300">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-zinc-500" aria-hidden="true"><path d="m9 18 6-6-6-6" /></svg>
|
||||
<div class="flex items-center gap-1 rounded py-0.5 pr-2 pl-1.5 text-fg-muted">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-fg-subtle" aria-hidden="true"><path d="m9 18 6-6-6-6" /></svg>
|
||||
<span class="font-medium">web</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 rounded py-0.5 pl-1.5 pr-2 text-zinc-300">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-zinc-500" aria-hidden="true"><path d="m9 18 6-6-6-6" /></svg>
|
||||
<div class="flex items-center gap-1 rounded py-0.5 pr-2 pl-1.5 text-fg-muted">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-fg-subtle" aria-hidden="true"><path d="m9 18 6-6-6-6" /></svg>
|
||||
<span class="font-medium">docs</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -104,21 +105,21 @@ function marker(type: DiffLine['type']): string {
|
||||
<!-- centre : onglets d'editeur + diff + dock terminaux -->
|
||||
<main class="flex min-w-0 flex-1 flex-col">
|
||||
<!-- onglets d'editeur (plusieurs fichiers, potentiellement de projets differents) -->
|
||||
<div class="flex items-stretch overflow-x-auto border-b border-zinc-800 text-xs">
|
||||
<span class="flex items-center gap-1.5 border-r border-zinc-800 bg-[#09090b] px-3 py-1.5 text-zinc-100">
|
||||
session.ts <span class="h-1.5 w-1.5 rounded-full bg-amber-400"></span>
|
||||
<div class="flex items-stretch overflow-x-auto border-b border-border text-xs">
|
||||
<span class="flex items-center gap-1.5 border-r border-border bg-surface-0 px-3 py-1.5 text-fg">
|
||||
session.ts <span class="h-1.5 w-1.5 rounded-full bg-warn"></span>
|
||||
</span>
|
||||
<span class="flex items-center gap-1.5 border-r border-zinc-800 px-3 py-1.5 text-zinc-400">token.ts</span>
|
||||
<span class="flex items-center gap-1.5 border-r border-zinc-800 px-3 py-1.5 text-zinc-500">app.vue</span>
|
||||
<span class="flex items-center gap-1.5 border-r border-border px-3 py-1.5 text-fg-muted">token.ts</span>
|
||||
<span class="flex items-center gap-1.5 border-r border-border px-3 py-1.5 text-fg-subtle">app.vue</span>
|
||||
</div>
|
||||
<!-- barre : bascule Editor/Diff + chemin -->
|
||||
<div class="flex items-center gap-2 border-b border-zinc-800 px-2 py-1">
|
||||
<div class="inline-flex rounded-lg border border-zinc-800 bg-zinc-950/40 p-0.5">
|
||||
<span class="rounded-md px-2.5 py-1 text-xs font-medium text-zinc-400">{{ t('wsEditor') }}</span>
|
||||
<span class="rounded-md bg-zinc-800 px-2.5 py-1 text-xs font-medium text-zinc-100">{{ t('wsDiff') }}</span>
|
||||
<div class="flex items-center gap-2 border-b border-border px-2 py-1">
|
||||
<div class="inline-flex rounded-lg border border-border bg-surface-0/40 p-0.5">
|
||||
<span class="rounded-[9px] px-2.5 py-1 text-xs font-medium text-fg-muted">{{ t('wsEditor') }}</span>
|
||||
<span class="rounded-[9px] bg-surface-2 px-2.5 py-1 text-xs font-medium text-fg">{{ t('wsDiff') }}</span>
|
||||
</div>
|
||||
<span class="truncate font-mono text-[11px] text-zinc-500">api · src/core/session.ts</span>
|
||||
<span class="ml-auto inline-flex items-center gap-1.5 rounded-md bg-emerald-500 px-2.5 py-1 text-xs font-semibold text-emerald-950">
|
||||
<span class="truncate font-mono text-[11px] text-fg-subtle">api · src/core/session.ts</span>
|
||||
<span class="ml-auto inline-flex items-center gap-1.5 rounded-[9px] bg-accent-solid px-2.5 py-1 text-xs font-semibold text-white">
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5" /></svg>
|
||||
{{ t('wsCommitBtn') }}
|
||||
</span>
|
||||
@@ -127,46 +128,46 @@ function marker(type: DiffLine['type']): string {
|
||||
<div class="overflow-x-auto">
|
||||
<table class="w-full border-collapse font-mono text-xs leading-5">
|
||||
<tbody>
|
||||
<tr v-for="(l, i) in diffLines" :key="i" :class="l.type === 'hunk' ? 'bg-zinc-900/60 text-sky-300/80' : rowClass(l.type)">
|
||||
<td class="w-10 select-none px-2 text-right text-zinc-600">{{ l.o }}</td>
|
||||
<td class="w-10 select-none px-2 text-right text-zinc-600">{{ l.n }}</td>
|
||||
<td class="whitespace-pre px-2">{{ l.type === 'hunk' ? l.text : marker(l.type) + l.text }}</td>
|
||||
<tr v-for="(l, i) in diffLines" :key="i" :class="l.type === 'hunk' ? 'bg-surface-1/60 text-info/80' : rowClass(l.type)">
|
||||
<td class="w-10 px-2 text-right text-fg-subtle select-none">{{ l.o }}</td>
|
||||
<td class="w-10 px-2 text-right text-fg-subtle select-none">{{ l.n }}</td>
|
||||
<td class="px-2 whitespace-pre">{{ l.type === 'hunk' ? l.text : marker(l.type) + l.text }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- dock terminaux en bas (onglets par session) -->
|
||||
<div class="border-t border-zinc-800">
|
||||
<div class="flex items-stretch border-b border-zinc-800 text-[11px]">
|
||||
<span class="flex items-center gap-1 px-2 py-1.5 font-semibold uppercase tracking-wide text-zinc-500">
|
||||
<div class="border-t border-border">
|
||||
<div class="flex items-stretch border-b border-border text-[11px]">
|
||||
<span class="label-mono flex items-center gap-1 px-2 py-1.5">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m7 11 2-2-2-2" /><path d="M11 13h4" /><rect width="18" height="18" x="3" y="3" rx="2" /></svg>
|
||||
{{ t('wsTerminal') }}
|
||||
</span>
|
||||
<span class="flex items-center gap-1.5 border-l border-zinc-800 bg-[#09090b] px-2.5 py-1.5 font-mono text-zinc-100">
|
||||
<span class="h-1.5 w-1.5 animate-pulse-sky rounded-full bg-sky-300"></span>api · feat/auth
|
||||
<span class="flex items-center gap-1.5 border-l border-border bg-surface-0 px-2.5 py-1.5 font-mono text-fg">
|
||||
<span class="h-1.5 w-1.5 animate-pulse-sky rounded-full bg-info"></span>api · feat/auth
|
||||
</span>
|
||||
<span class="flex items-center gap-1.5 border-l border-zinc-800 px-2.5 py-1.5 font-mono text-zinc-400">web · main</span>
|
||||
<span class="flex items-center gap-1.5 border-l border-border px-2.5 py-1.5 font-mono text-fg-muted">web · main</span>
|
||||
</div>
|
||||
<div class="min-h-[112px] p-[11px] font-mono text-[11px] leading-[1.7] text-zinc-300">
|
||||
<div class="text-emerald-400">● Edit session.ts</div>
|
||||
<div class="text-emerald-400">+ const token = sign(...)</div>
|
||||
<div class="text-zinc-500">running tests…</div>
|
||||
<div class="text-emerald-400">✓ 24 passed</div>
|
||||
<div><span class="text-emerald-400">$</span><span class="ml-1 inline-block h-[11px] w-1.5 animate-blink align-middle bg-zinc-200"></span></div>
|
||||
<div class="min-h-[112px] p-[11px] font-mono text-[11px] leading-[1.7] text-fg-muted">
|
||||
<div class="text-accent">● Edit session.ts</div>
|
||||
<div class="text-accent">+ const token = sign(...)</div>
|
||||
<div class="text-fg-subtle">running tests…</div>
|
||||
<div class="text-accent">✓ 24 passed</div>
|
||||
<div><span class="text-accent">$</span><span class="ml-1 inline-block h-[11px] w-1.5 animate-blink bg-fg align-middle"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- barre de statut -->
|
||||
<footer class="flex items-center gap-3 border-t border-zinc-800 px-3 py-1 text-[11px] text-zinc-500">
|
||||
<span class="flex items-center gap-1 font-mono text-zinc-400">
|
||||
<footer class="flex items-center gap-3 border-t border-border px-3 py-1 text-[11px] text-fg-subtle">
|
||||
<span class="flex items-center gap-1 font-mono text-fg-muted">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="6" x2="6" y1="3" y2="15" /><circle cx="18" cy="6" r="3" /><circle cx="6" cy="18" r="3" /><path d="M18 9a9 9 0 0 1-9 9" /></svg>
|
||||
feat/auth
|
||||
</span>
|
||||
<span class="text-emerald-400">↑2</span>
|
||||
<span class="text-amber-400">○3</span>
|
||||
<span class="text-accent">↑2</span>
|
||||
<span class="text-warn">○3</span>
|
||||
<span class="ml-auto">api · {{ t('busy') }}</span>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ export const FAQS: Record<AppLocale, FaqItem[]> = {
|
||||
en: [
|
||||
{
|
||||
q: 'How is this different from running Claude Code in a terminal?',
|
||||
a: 'It runs the very same interactive claude CLI. Arboretum just wraps it in a managed terminal you reach from a browser. What you gain on top: a worktree-first dashboard across all your repos, live busy / waiting / available states, a multi-terminal grid, push notifications, and the ability to answer a prompt from your phone. It supervises your sessions; it does not change how Claude Code works.',
|
||||
a: 'It runs the very same interactive claude CLI. Arboretum just wraps it in a managed terminal you reach from a browser. What you gain on top: a multi-project IDE across all your repos and worktrees, live busy / waiting / available states, a multi-terminal grid, push notifications, and the ability to answer a prompt from your phone. It supervises your sessions; it does not change how Claude Code works.',
|
||||
},
|
||||
{
|
||||
q: 'Does it need the cloud?',
|
||||
@@ -45,7 +45,7 @@ export const FAQS: Record<AppLocale, FaqItem[]> = {
|
||||
fr: [
|
||||
{
|
||||
q: 'En quoi est-ce différent de lancer Claude Code dans un terminal ?',
|
||||
a: "C'est exactement le même CLI claude interactif. Arboretum l'enveloppe simplement dans un terminal managé accessible depuis un navigateur. Ce que vous gagnez en plus : un dashboard worktree-first sur tous vos repos, les états occupée / en attente / disponible en direct, une grille multi-terminaux, des notifications push, et la possibilité de répondre à une question depuis votre téléphone. Il supervise vos sessions ; il ne change rien au fonctionnement de Claude Code.",
|
||||
a: "C'est exactement le même CLI claude interactif. Arboretum l'enveloppe simplement dans un terminal managé accessible depuis un navigateur. Ce que vous gagnez en plus : un IDE multi-projet sur tous vos repos et worktrees, les états occupée / en attente / disponible en direct, une grille multi-terminaux, des notifications push, et la possibilité de répondre à une question depuis votre téléphone. Il supervise vos sessions ; il ne change rien au fonctionnement de Claude Code.",
|
||||
},
|
||||
{
|
||||
q: 'A-t-il besoin du cloud ?',
|
||||
|
||||
@@ -6,6 +6,7 @@ export default {
|
||||
navHow: 'How it works',
|
||||
navSecurity: 'Security',
|
||||
navFaq: 'FAQ',
|
||||
themeToggle: 'Toggle theme',
|
||||
gitea: 'View on Gitea',
|
||||
heroBadge: 'Mission control for AI coding agents',
|
||||
heroTitle: 'Mission control for your AI coding agents',
|
||||
@@ -38,7 +39,7 @@ export default {
|
||||
feat9Desc: 'Drive worktrees and attach sessions in native terminals, right inside your editor.',
|
||||
feat10Title: 'New project in one click',
|
||||
feat10Desc: 'Create a project folder anywhere (optional git init) and start a Claude session in it, from any device.',
|
||||
scAKicker: 'Worktree-first dashboard',
|
||||
scAKicker: 'Session supervision',
|
||||
scATitle: 'See what needs you, before anything stalls',
|
||||
scABody:
|
||||
'A live list of every worktree and its session state. The Needs attention rail floats the agents waiting on a decision to the top, so nothing sits blocked while you are heads-down elsewhere. Commit, push or promote a branch to main straight from the card, and hide the noisy history left by past terminal sessions so the list stays actionable.',
|
||||
@@ -82,7 +83,12 @@ export default {
|
||||
license: 'MIT License',
|
||||
coffee: 'Buy me a coffee',
|
||||
mIde: 'IDE',
|
||||
mProjects: 'Projects',
|
||||
mWorktrees: 'Worktrees',
|
||||
mExplorer: 'Explorer',
|
||||
mEditor: 'Editor',
|
||||
mTerminal: 'Terminal',
|
||||
mGit: 'Git',
|
||||
mSessions: 'Sessions',
|
||||
mGroups: 'Groups',
|
||||
mSettings: 'Settings',
|
||||
|
||||
@@ -6,6 +6,7 @@ export default {
|
||||
navHow: 'Comment ça marche',
|
||||
navSecurity: 'Sécurité',
|
||||
navFaq: 'FAQ',
|
||||
themeToggle: 'Changer de thème',
|
||||
gitea: 'Voir sur Gitea',
|
||||
heroBadge: 'Poste de commandement pour agents de code IA',
|
||||
heroTitle: 'Le poste de commandement de vos agents de code IA',
|
||||
@@ -38,7 +39,7 @@ export default {
|
||||
feat9Desc: 'Pilotez vos worktrees et attachez vos sessions dans des terminaux natifs, directement dans votre éditeur.',
|
||||
feat10Title: 'Nouveau projet en un clic',
|
||||
feat10Desc: "Créez un dossier de projet où vous voulez (git init optionnel) et lancez-y une session Claude, depuis n'importe quel appareil.",
|
||||
scAKicker: 'Dashboard worktree-first',
|
||||
scAKicker: 'Supervision des sessions',
|
||||
scATitle: 'Voyez ce qui vous attend, avant que ça ne bloque',
|
||||
scABody:
|
||||
"Une liste en direct de chaque worktree et de l'état de sa session. Le bandeau À traiter fait remonter les agents en attente d'une décision, pour que rien ne reste bloqué pendant que vous êtes concentré ailleurs. Commitez, poussez ou passez une branche en principal directement depuis la carte, et masquez l'historique encombrant laissé par vos anciennes sessions terminal pour garder la liste actionnable.",
|
||||
@@ -82,7 +83,12 @@ export default {
|
||||
license: 'Licence MIT',
|
||||
coffee: 'Paye-moi un café',
|
||||
mIde: 'IDE',
|
||||
mProjects: 'Projets',
|
||||
mWorktrees: 'Worktrees',
|
||||
mExplorer: 'Explorateur',
|
||||
mEditor: 'Éditeur',
|
||||
mTerminal: 'Terminal',
|
||||
mGit: 'Git',
|
||||
mSessions: 'Sessions',
|
||||
mGroups: 'Groupes',
|
||||
mSettings: 'Réglages',
|
||||
|
||||
73
packages/site/src/lib/theme.ts
Normal file
73
packages/site/src/lib/theme.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
// Thème clair / sombre du site vitrine (aligné sur l'app : lib/theme.ts de packages/web).
|
||||
// Singleton module Vue : `themeMode` (persisté en localStorage, clé `arb-theme`), `resolvedTheme`
|
||||
// (résout `system` via prefers-color-scheme), application au <html> (dataset.theme + metas).
|
||||
import { computed, ref, watch, type ComputedRef } from 'vue';
|
||||
|
||||
export type ThemeMode = 'dark' | 'light' | 'system';
|
||||
export type ResolvedTheme = 'dark' | 'light';
|
||||
|
||||
const STORAGE_KEY = 'arb-theme';
|
||||
// Doit rester synchronisé avec --color-surface-0 (style.css) : fond du <html> + metas.
|
||||
const BG: Record<ResolvedTheme, string> = { dark: '#09090b', light: '#fafafa' };
|
||||
|
||||
function initialMode(): ThemeMode {
|
||||
try {
|
||||
const saved = localStorage.getItem(STORAGE_KEY);
|
||||
if (saved === 'dark' || saved === 'light' || saved === 'system') return saved;
|
||||
} catch {
|
||||
/* localStorage indisponible */
|
||||
}
|
||||
return 'dark';
|
||||
}
|
||||
|
||||
export const themeMode = ref<ThemeMode>(initialMode());
|
||||
|
||||
const mql =
|
||||
typeof window !== 'undefined' && typeof window.matchMedia === 'function'
|
||||
? window.matchMedia('(prefers-color-scheme: dark)')
|
||||
: null;
|
||||
const systemDark = ref(mql?.matches ?? true);
|
||||
mql?.addEventListener('change', (e) => {
|
||||
systemDark.value = e.matches;
|
||||
});
|
||||
|
||||
export const resolvedTheme: ComputedRef<ResolvedTheme> = computed(() => {
|
||||
if (themeMode.value === 'system') return systemDark.value ? 'dark' : 'light';
|
||||
return themeMode.value;
|
||||
});
|
||||
|
||||
function applyTheme(resolved: ResolvedTheme): void {
|
||||
if (typeof document === 'undefined') return;
|
||||
const root = document.documentElement;
|
||||
root.dataset.theme = resolved;
|
||||
root.style.backgroundColor = BG[resolved];
|
||||
const setMeta = (name: string, content: string): void => {
|
||||
document.querySelector(`meta[name="${name}"]`)?.setAttribute('content', content);
|
||||
};
|
||||
setMeta('color-scheme', resolved);
|
||||
setMeta('theme-color', BG[resolved]);
|
||||
}
|
||||
|
||||
watch(
|
||||
resolvedTheme,
|
||||
(r) => {
|
||||
applyTheme(r);
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, themeMode.value);
|
||||
} catch {
|
||||
/* quota / mode privé */
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
export function setThemeMode(mode: ThemeMode): void {
|
||||
themeMode.value = mode;
|
||||
}
|
||||
|
||||
/** Cycle dark -> light -> system (pour un bouton unique). */
|
||||
export function cycleTheme(): void {
|
||||
const order: ThemeMode[] = ['dark', 'light', 'system'];
|
||||
const next = order[(order.indexOf(themeMode.value) + 1) % order.length];
|
||||
if (next) themeMode.value = next;
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
import { createApp } from 'vue';
|
||||
// Polices self-host (subset latin) : bundlées par Vite, aucune requête Google Fonts.
|
||||
import '@fontsource/jetbrains-mono/latin-400.css';
|
||||
import '@fontsource/jetbrains-mono/latin-500.css';
|
||||
import '@fontsource/jetbrains-mono/latin-600.css';
|
||||
import '@fontsource/jetbrains-mono/latin-700.css';
|
||||
// Polices auto-hébergées (fontes variables), bundlées par Vite, aucune requête Google Fonts.
|
||||
import '@fontsource-variable/inter';
|
||||
import '@fontsource-variable/jetbrains-mono';
|
||||
import App from './App.vue';
|
||||
import { i18n } from './i18n';
|
||||
// Effet de bord : active le watcher d'application du thème clair/sombre.
|
||||
import './lib/theme';
|
||||
import { reveal } from './composables/useScrollReveal';
|
||||
import './style.css';
|
||||
|
||||
|
||||
@@ -1,15 +1,41 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
/* La palette du design correspond à zinc/emerald/amber/sky/red de Tailwind.
|
||||
On ajoute seulement les tokens hors échelle : ombres, police mono, animations. */
|
||||
/* ============================================================
|
||||
Tokens de design « Emerald » : alignés sur packages/web/src/style.css.
|
||||
Défauts SOMBRES dans @theme ; thème CLAIR via html[data-theme="light"]
|
||||
(les utilitaires référencent les vars, l'override cascade sans duplication).
|
||||
============================================================ */
|
||||
@theme {
|
||||
--font-mono: 'JetBrains Mono', ui-monospace, monospace;
|
||||
/* Surfaces / bordures / texte / accent / tons sémantiques (défauts sombres). */
|
||||
--color-surface-0: #09090b;
|
||||
--color-surface-1: #18181b;
|
||||
--color-surface-2: #27272a;
|
||||
--color-surface-3: #3f3f46;
|
||||
--color-border: #27272a;
|
||||
--color-border-strong: #3f3f46;
|
||||
--color-border-soft: #1c1c1f;
|
||||
--color-fg: #f4f4f5;
|
||||
--color-fg-muted: #a1a1aa;
|
||||
--color-fg-subtle: #52525b;
|
||||
--color-accent: #34d399;
|
||||
--color-accent-solid: #059669;
|
||||
--color-accent-hover: #10b981;
|
||||
--color-coffee: #f59e0b;
|
||||
--color-info: #7dd3fc;
|
||||
--color-warn: #fcd34d;
|
||||
--color-danger: #f87171;
|
||||
|
||||
/* Polices auto-hébergées (fontes variables). */
|
||||
--font-sans: 'Inter Variable', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
|
||||
--font-mono: 'JetBrains Mono Variable', ui-monospace, 'Fira Code', Menlo, Consolas, monospace;
|
||||
|
||||
/* Ombres (site) + shadow-pop Emerald. */
|
||||
--shadow-hero: 0 30px 80px -30px rgb(0 0 0 / 0.85);
|
||||
--shadow-card: 0 24px 60px -30px rgb(0 0 0 / 0.8);
|
||||
--shadow-phone: 0 30px 70px -28px rgb(0 0 0 / 0.9);
|
||||
--shadow-toast: 0 12px 32px -12px rgb(0 0 0 / 0.7);
|
||||
--shadow-toast-sm: 0 10px 24px -12px rgb(0 0 0 / 0.6);
|
||||
--shadow-pop: 0 24px 48px -20px rgb(0 0 0 / 0.65);
|
||||
|
||||
--animate-blink: blink 1.05s steps(1) infinite;
|
||||
--animate-pulse-sky: pulseSky 2s ease-in-out infinite;
|
||||
@@ -18,26 +44,46 @@
|
||||
--animate-dlg-in: dlgIn 0.35s ease both;
|
||||
}
|
||||
|
||||
/* JetBrains Mono (Open Font License) : @font-face fournis par @fontsource,
|
||||
importés dans main.ts et bundlés par Vite (self-host, pas de CDN Google). */
|
||||
html[data-theme='light'] {
|
||||
--color-surface-0: #fafafa;
|
||||
--color-surface-1: #ffffff;
|
||||
--color-surface-2: #f4f4f5;
|
||||
--color-surface-3: #e4e4e7;
|
||||
--color-border: #e4e4e7;
|
||||
--color-border-strong: #d4d4d8;
|
||||
--color-border-soft: #ececee;
|
||||
--color-fg: #18181b;
|
||||
--color-fg-muted: #52525b;
|
||||
--color-fg-subtle: #a1a1aa;
|
||||
--color-accent: #059669;
|
||||
--color-accent-solid: #059669;
|
||||
--color-accent-hover: #047857;
|
||||
--color-coffee: #d97706;
|
||||
--color-info: #0284c7;
|
||||
--color-warn: #d97706;
|
||||
--color-danger: #dc2626;
|
||||
/* Ombres plus douces en clair. */
|
||||
--shadow-hero: 0 30px 80px -30px rgb(24 24 27 / 0.18);
|
||||
--shadow-card: 0 24px 60px -30px rgb(24 24 27 / 0.16);
|
||||
--shadow-phone: 0 30px 70px -28px rgb(24 24 27 / 0.2);
|
||||
--shadow-toast: 0 12px 32px -12px rgb(24 24 27 / 0.14);
|
||||
--shadow-toast-sm: 0 10px 24px -12px rgb(24 24 27 / 0.12);
|
||||
--shadow-pop: 0 18px 38px -22px rgb(24 24 27 / 0.22);
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: #09090b;
|
||||
color: #f4f4f5;
|
||||
font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
|
||||
background-color: var(--color-surface-0);
|
||||
color: var(--color-fg);
|
||||
font-family: var(--font-sans);
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
::selection {
|
||||
background: rgba(16, 185, 129, 0.3);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%,
|
||||
@@ -89,14 +135,39 @@ body {
|
||||
}
|
||||
|
||||
@layer base {
|
||||
/* Tailwind v4 ne met plus cursor:pointer sur les boutons → on le rétablit. */
|
||||
/* Tailwind v4 ne met plus cursor:pointer sur les boutons : on le rétablit. */
|
||||
button:not(:disabled),
|
||||
[role='button']:not([aria-disabled='true']) {
|
||||
cursor: pointer;
|
||||
}
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--color-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
::selection {
|
||||
background: var(--color-accent);
|
||||
color: var(--color-surface-0);
|
||||
}
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--color-surface-3) transparent;
|
||||
}
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--color-surface-3);
|
||||
border-radius: 9999px;
|
||||
border: 2px solid transparent;
|
||||
background-clip: content-box;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--color-border-strong);
|
||||
}
|
||||
}
|
||||
|
||||
/* Révélation au scroll (directive v-reveal) */
|
||||
/* Révélation au scroll (directive v-reveal). */
|
||||
.reveal {
|
||||
opacity: 0;
|
||||
transform: translateY(22px);
|
||||
@@ -118,3 +189,89 @@ body {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
/* Pastille (pill mono) + point pulsé. */
|
||||
.badge {
|
||||
@apply inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 font-mono text-[11px];
|
||||
}
|
||||
.chip {
|
||||
@apply inline-flex items-center rounded-full border border-border bg-surface-0 px-[9px] py-1 font-mono text-[11px] text-fg-muted;
|
||||
}
|
||||
.status {
|
||||
@apply inline-flex items-center gap-1.5 font-mono text-[11px] tracking-[0.05em] text-fg-muted uppercase;
|
||||
}
|
||||
.status-dot {
|
||||
@apply inline-block h-[7px] w-[7px] rounded-full;
|
||||
}
|
||||
.eyebrow {
|
||||
@apply font-mono text-[13px] text-accent;
|
||||
}
|
||||
.label-mono {
|
||||
@apply font-mono text-[11px] tracking-[0.08em] text-fg-subtle uppercase;
|
||||
}
|
||||
/* Caret clignotant des mockups terminaux (réutilise le keyframe `blink` du site). */
|
||||
.caret {
|
||||
@apply ml-0.5 inline-block h-[15px] w-2 bg-accent align-[-2px];
|
||||
animation: blink 1.05s steps(1) infinite;
|
||||
}
|
||||
.card {
|
||||
@apply rounded-[14px] border border-border bg-surface-1/50 p-3;
|
||||
}
|
||||
.card-hover {
|
||||
@apply transition-[transform,border-color,box-shadow] hover:-translate-y-1 hover:border-accent hover:shadow-pop;
|
||||
}
|
||||
.card-inset {
|
||||
@apply rounded-[10px] border border-border bg-surface-0/40 p-2;
|
||||
}
|
||||
/* Boutons Emerald (pour réutilisation dans les mockups). */
|
||||
.btn {
|
||||
@apply inline-flex items-center justify-center gap-[9px] rounded-[11px] border border-border bg-surface-2 px-3 py-1.5 text-sm font-semibold text-fg transition-[background-color,border-color,color,transform] hover:border-accent hover:text-accent;
|
||||
}
|
||||
.btn-primary {
|
||||
@apply inline-flex items-center justify-center gap-[9px] rounded-[11px] border border-accent-solid bg-accent-solid px-3 py-1.5 text-sm font-semibold text-white transition-[background-color,border-color,color,transform] hover:-translate-y-px hover:bg-accent-hover;
|
||||
}
|
||||
.btn-ghost {
|
||||
@apply inline-flex items-center justify-center gap-[9px] rounded-[11px] px-3 py-1.5 text-sm font-semibold text-fg-muted transition-[background-color,color] hover:bg-surface-2/60 hover:text-fg;
|
||||
}
|
||||
/* Calques décoratifs (halo accent + grain). */
|
||||
.halo {
|
||||
position: absolute;
|
||||
top: -40px;
|
||||
left: 50%;
|
||||
width: 1100px;
|
||||
max-width: 140%;
|
||||
height: 560px;
|
||||
transform: translateX(-50%);
|
||||
background: radial-gradient(50% 60% at 50% 0%, var(--color-accent) 0%, transparent 70%);
|
||||
opacity: 0.12;
|
||||
filter: blur(18px);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
.grain {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: radial-gradient(var(--color-fg) 1px, transparent 1px);
|
||||
background-size: 34px 34px;
|
||||
opacity: 0.025;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
-webkit-mask-image: linear-gradient(to bottom, #000, transparent 70%);
|
||||
mask-image: linear-gradient(to bottom, #000, transparent 70%);
|
||||
}
|
||||
/* Lignes de diff : fond teinté sémantique, texte neutre lisible (clair + sombre). */
|
||||
.diff-add {
|
||||
@apply bg-accent/12 text-fg;
|
||||
}
|
||||
.diff-del {
|
||||
@apply bg-danger/12 text-fg;
|
||||
}
|
||||
.diff-ctx {
|
||||
@apply text-fg-muted;
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme='light'] .halo {
|
||||
opacity: 0.06;
|
||||
}
|
||||
|
||||
@@ -53,13 +53,13 @@ The extension is bundled with esbuild (`@arboretum/shared` is inlined → the VS
|
||||
npm install
|
||||
npm run build:vscode # builds @arboretum/shared then bundles the extension
|
||||
cd packages/vscode && npx @vscode/vsce package --no-dependencies
|
||||
# → git-arboretum-0.2.0.vsix
|
||||
# → git-arboretum-0.3.0.vsix
|
||||
```
|
||||
|
||||
Install it with **Extensions: Install from VSIX…** in the Command Palette, or:
|
||||
|
||||
```bash
|
||||
code --install-extension git-arboretum-0.2.0.vsix # also: codium / cursor
|
||||
code --install-extension git-arboretum-0.3.0.vsix # also: codium / cursor
|
||||
```
|
||||
|
||||
### Other distribution channels (optional)
|
||||
|
||||
Reference in New Issue
Block a user