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).
206 lines
13 KiB
Vue
206 lines
13 KiB
Vue
<script setup lang="ts">
|
|
import { computed } from 'vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { useHeroTimeline, type SessionState } from '../composables/useHeroTimeline';
|
|
import { DLG_OPTS } from '../i18n/content';
|
|
import type { AppLocale } from '../i18n';
|
|
|
|
const { t, locale } = useI18n();
|
|
const { anim } = useHeroTimeline('hero-stage');
|
|
|
|
// Couleurs des lignes de terminal par type, tokenisées (suivent le thème clair/sombre).
|
|
const LINE_COLORS: Record<string, string> = {
|
|
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)',
|
|
};
|
|
|
|
// 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] ?? 'var(--color-fg-muted)' })),
|
|
);
|
|
|
|
// Options du dialogue de permission : reproduit DialogPrompt (boutons numérotés + Deny).
|
|
const dlgOptions = computed(() =>
|
|
DLG_OPTS[locale.value as AppLocale].map((txt, i) => ({
|
|
num: anim.value.ans === i ? '✓' : String(i + 1),
|
|
text: txt,
|
|
highlighted: anim.value.hl === i,
|
|
answered: anim.value.ans === i,
|
|
})),
|
|
);
|
|
</script>
|
|
|
|
<template>
|
|
<div id="hero-stage" class="relative min-w-[300px] flex-[1_1_480px]">
|
|
<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-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]">
|
|
<!-- 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>
|
|
<!-- 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>
|
|
<!-- 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>
|
|
|
|
<!-- 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>
|
|
|
|
<!-- 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] bg-surface-0 p-[13px]">
|
|
<!-- terminal pane -->
|
|
<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-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.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-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-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) -->
|
|
<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-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
|
|
v-for="(op, i) in dlgOptions"
|
|
: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-accent/70 bg-accent/15 text-accent'
|
|
: op.highlighted
|
|
? 'border-warn bg-warn/15 text-warn'
|
|
: 'border-border bg-surface-2 text-fg'"
|
|
>
|
|
<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-danger/40 bg-danger/10 px-2.5 py-1.5 text-xs font-medium text-danger">{{ t('mDeny') }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- toast push -->
|
|
<div
|
|
v-if="anim.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-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-accent">Arboretum</span>
|
|
<span class="text-[10px] text-fg-subtle">now</span>
|
|
</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>
|
|
</template>
|