refactor(site): visuels fidèles à l'app réelle + arguments alignés sur le réel

- HeroMockup : sidebar fidèle (Worktrees actif / Sessions + badge / Groups, puis
  Réglages / Aide / Gitea / Café) + pied langue·notif·déconnexion·version ; dialogue
  au format DialogPrompt (badge Permission + options numérotées + Deny) ; couleurs
  d'état alignées sur SessionStateBadge.
- ShowcaseSection : a) dashboard worktree-first (rail « À traiter » + RepoSection
  + WorktreeCard avec états), b) grille fidèle à TerminalCell (badge d'état texte
  + repo·branche + commande), c) mobile avec DialogPrompt réel + MobileTabBar.
- Retrait de la notion d'IDE (titre des fonctionnalités + FAQ) ; FAQ recentrée
  sur ce que fait réellement l'app.
This commit is contained in:
2026-06-22 12:41:10 +02:00
parent 8036de2f0d
commit 5166b14d5c
5 changed files with 265 additions and 163 deletions

View File

@@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n';
import { useHeroTimeline, type SessionState } from '../composables/useHeroTimeline'; import { useHeroTimeline, type SessionState } from '../composables/useHeroTimeline';
import { DLG_OPTS } from '../i18n/content'; import { DLG_OPTS } from '../i18n/content';
import type { AppLocale } from '../i18n'; import type { AppLocale } from '../i18n';
import IconGitea from './icons/IconGitea.vue';
const { t, locale } = useI18n(); const { t, locale } = useI18n();
const { anim } = useHeroTimeline('hero-stage'); const { anim } = useHeroTimeline('hero-stage');
@@ -20,10 +21,11 @@ const LINE_COLORS: Record<string, string> = {
run: '#7dd3fc', run: '#7dd3fc',
}; };
// 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 }> = { const PILL: Record<SessionState, { bg: string; fg: string; dot: string; pulse: string }> = {
idle: { bg: 'rgba(2,44,34,.6)', fg: '#34d399', dot: '#34d399', pulse: '' }, idle: { bg: '#022c22', fg: '#34d399', dot: '#34d399', pulse: '' },
busy: { bg: 'rgba(8,47,73,.7)', fg: '#7dd3fc', dot: '#7dd3fc', pulse: 'pulseSky 2s ease-in-out infinite' }, busy: { bg: '#082f49', fg: '#7dd3fc', dot: '#7dd3fc', pulse: 'pulseSky 2s ease-in-out infinite' },
waiting: { bg: 'rgba(69,26,3,.7)', fg: '#fcd34d', dot: '#fcd34d', pulse: 'pulseAmber 2.2s ease-in-out infinite' }, waiting: { bg: '#451a03', fg: '#fcd34d', dot: '#fcd34d', pulse: 'pulseAmber 2.2s ease-in-out infinite' },
}; };
const pill = computed(() => PILL[anim.value.sess]); const pill = computed(() => PILL[anim.value.sess]);
@@ -33,36 +35,14 @@ 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] ?? '#d4d4d8' })),
); );
// Options du dialogue de permission — reproduit DialogPrompt (boutons numérotés + Deny).
const dlgOptions = computed(() => const dlgOptions = computed(() =>
DLG_OPTS[locale.value as AppLocale].map((txt, i) => { DLG_OPTS[locale.value as AppLocale].map((txt, i) => ({
const highlighted = anim.value.hl === i; num: anim.value.ans === i ? '✓' : String(i + 1),
const answered = anim.value.ans === i; text: txt,
const row: Record<string, string> = { highlighted: anim.value.hl === i,
display: 'flex', answered: anim.value.ans === i,
alignItems: 'center', })),
gap: '9px',
padding: '7px 9px',
borderRadius: '7px',
border: '1px solid transparent',
background: 'rgba(9,9,11,.4)',
transition: 'background .2s, border-color .2s',
};
if (highlighted) {
row.background = 'rgba(69,26,3,.5)';
row.border = '1px solid rgba(217,119,6,.6)';
}
if (answered) {
row.background = 'rgba(2,44,34,.45)';
row.border = '1px solid rgba(5,150,105,.6)';
}
return {
num: answered ? '✓' : String(i + 1),
text: txt,
rowStyle: row,
numColor: answered ? '#34d399' : '#fbbf24',
textColor: answered ? '#a7f3d0' : highlighted ? '#fde68a' : '#d4d4d8',
};
}),
); );
</script> </script>
@@ -79,59 +59,79 @@ const dlgOptions = computed(() =>
<span class="ml-2 font-mono text-[11.5px] text-zinc-600">arboretum · localhost:7317</span> <span class="ml-2 font-mono text-[11.5px] text-zinc-600">arboretum · localhost:7317</span>
</div> </div>
<div class="flex min-h-[360px]"> <div class="flex min-h-[440px]">
<!-- sidebar --> <!-- sidebar (fidèle à AppSidebar : Worktrees / Sessions / Groups, puis Réglages / Aide / Gitea / Café) -->
<div <div
class="flex w-[168px] flex-none flex-col border-r border-[#1c1c1f] bg-zinc-900/40 px-2.5 py-3" 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-1 pb-3"> <div class="flex items-center gap-2 px-2 pb-3">
<img <img
src="/assets/arboretum-mark.png" src="/assets/arboretum-mark.png"
alt="" alt=""
width="20" width="22"
height="20" height="22"
class="h-5 w-5 object-contain" class="h-[22px] w-[22px] object-contain"
/> />
<span class="font-mono text-[13px] font-semibold text-zinc-200">Arboretum</span> <span class="text-[14px] font-semibold text-zinc-100">Arboretum</span>
</div> </div>
<div <div
class="mb-3 flex items-center gap-2 rounded-lg border border-zinc-800 bg-zinc-950/50 px-[9px] py-[7px]" 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="13" height="13" 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> <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-[11.5px] text-zinc-600">{{ t('mSearch') }}</span> <span class="flex-1 text-[12px] text-zinc-500">{{ t('mSearch') }}</span>
<span class="rounded border border-zinc-800 px-[5px] py-px font-mono text-[10px] text-zinc-500">K</span> <span class="rounded border border-zinc-700 px-[5px] py-px font-mono text-[10px] text-zinc-500">K</span>
</div> </div>
<div class="flex flex-col gap-0.5"> <!-- nav primaire -->
<div class="flex items-center gap-[9px] rounded-lg bg-zinc-800 px-[9px] py-[7px] text-zinc-100"> <div class="flex flex-col gap-1">
<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="7" height="9" x="3" y="3" rx="1" /><rect width="7" height="5" x="14" y="3" rx="1" /><rect width="7" height="9" x="14" y="12" rx="1" /><rect width="7" height="5" x="3" y="16" rx="1" /></svg> <div class="flex items-center gap-2.5 rounded-lg bg-zinc-800 px-2.5 py-1.5 text-zinc-100">
<span class="text-[12.5px]">{{ t('mDash') }}</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"><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 <span
v-show="anim.att > 0" v-show="anim.att > 0"
class="ml-auto inline-flex h-[17px] min-w-[17px] items-center justify-center rounded-full px-[5px] font-mono text-[10px] font-bold" class="rounded-full bg-amber-500/20 px-1.5 text-[11px] font-semibold text-amber-300"
style="background: #f59e0b; color: #1c1300"
>{{ anim.att }}</span >{{ anim.att }}</span
> >
</div> </div>
<div class="flex items-center gap-[9px] rounded-lg px-[9px] py-[7px] text-zinc-400"> <div class="flex items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-zinc-400">
<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"><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> <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-[12.5px]">{{ t('mGroups') }}</span> <span class="flex-1 text-[12.5px] font-medium">{{ t('mGroups') }}</span>
</div> </div>
</div> </div>
<div class="flex-1"></div> <div class="flex-1"></div>
<div class="flex flex-col gap-0.5 border-t border-[#1c1c1f] pt-2.5"> <!-- nav secondaire -->
<div class="flex items-center gap-[9px] rounded-lg px-[9px] py-1.5 text-zinc-400"> <div class="flex flex-col gap-1">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" 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> <div class="flex items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-zinc-400">
<span class="text-[12.5px]">{{ t('mSettings') }}</span> <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>
<div class="flex items-center gap-[9px] rounded-lg px-[9px] py-1.5 text-zinc-400"> <div class="flex items-center gap-2.5 rounded-lg px-2.5 py-1.5 text-zinc-400">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10" /><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" /><path d="M12 17h.01" /></svg> <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="text-[12.5px]">{{ t('mHelp') }}</span> <span class="flex-1 text-[12.5px] font-medium">{{ t('mHelp') }}</span>
</div> </div>
<div class="px-[9px] pt-1.5 font-mono text-[10px] text-zinc-700">v0.4.2</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>
</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">v1.6.0</span>
</div> </div>
</div> </div>
@@ -142,9 +142,9 @@ const dlgOptions = computed(() =>
class="flex min-h-0 flex-1 flex-col overflow-hidden rounded-[10px] border border-zinc-800 bg-zinc-950" 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"> <div class="flex items-center justify-between border-b border-[#1c1c1f] px-[11px] py-2">
<span class="font-mono text-[11.5px] text-zinc-400">api · feat/auth</span> <span class="font-mono text-[11.5px] text-zinc-300">api · feat/auth</span>
<span <span
class="inline-flex items-center gap-1.5 rounded-full px-[9px] py-[3px] font-mono text-[11px]" class="inline-flex items-center gap-1.5 rounded px-2 py-[3px] font-mono text-[11px]"
:style="{ background: pill.bg, color: pill.fg }" :style="{ background: pill.bg, color: pill.fg }"
> >
<span <span
@@ -167,24 +167,30 @@ const dlgOptions = computed(() =>
</div> </div>
</div> </div>
<!-- dialogue de permission --> <!-- dialogue de permission (fidèle à DialogPrompt : badge type + question + options + Deny) -->
<div <div
v-if="anim.dlg" v-if="anim.dlg"
class="animate-dlg-in rounded-[10px] border border-[rgba(120,53,15,0.6)] bg-[rgba(69,26,3,0.3)] p-[11px]" class="animate-dlg-in rounded-lg border border-amber-900/60 bg-amber-950/30 p-[11px]"
> >
<div class="mb-[9px] flex items-center gap-2"> <div class="mb-2 flex items-center gap-2">
<svg width="14" height="14" 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="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-zinc-200">{{ t('dlgQ') }}</span> <span class="text-[12.5px] text-amber-100">{{ t('dlgQ') }}</span>
</div> </div>
<div class="flex flex-col gap-[5px]"> <div class="flex flex-wrap gap-[6px]">
<div v-for="(op, i) in dlgOptions" :key="i" :style="op.rowStyle"> <span
<span v-for="(op, i) in dlgOptions"
class="w-[15px] flex-none text-center font-mono text-[11px] font-bold" :key="i"
:style="{ color: op.numColor }" class="inline-flex items-center gap-1.5 rounded-lg border px-2.5 py-1.5 text-xs font-medium transition-colors"
>{{ op.num }}</span :class="op.answered
> ? 'border-emerald-500/70 bg-emerald-950/50 text-emerald-300'
<span class="text-xs" :style="{ color: op.textColor }">{{ op.text }}</span> : op.highlighted
</div> ? 'border-amber-400 bg-amber-950/50 text-amber-200'
: 'border-zinc-700 bg-zinc-800 text-zinc-200'"
>
<span class="font-mono" :class="op.answered ? 'text-emerald-400' : 'text-amber-400'">{{ 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>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,67 +1,115 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
const { t } = useI18n(); import { DLG_OPTS } from '../i18n/content';
import type { AppLocale } from '../i18n';
const { t, locale } = useI18n();
// Options du dialogue (réutilisées pour le rail « À traiter » et la vue mobile).
const dlgOpts = computed(() => DLG_OPTS[locale.value as AppLocale]);
</script> </script>
<template> <template>
<section id="showcase" class="mx-auto flex max-w-[1200px] scroll-mt-[84px] flex-col gap-[92px] px-6"> <section id="showcase" class="mx-auto flex max-w-[1200px] scroll-mt-[84px] flex-col gap-[92px] px-6">
<!-- a) dashboard + needs attention --> <!-- a) dashboard worktree-first : rail « À traiter » + section de repo (fidèle à AttentionSection + RepoSection) -->
<div v-reveal class="flex flex-wrap items-center gap-12"> <div v-reveal class="flex flex-wrap items-center gap-12">
<div class="min-w-[280px] flex-[1_1_380px]"> <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> <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"> <h2 class="m-0 mb-4 text-[clamp(26px,3vw,34px)] font-semibold leading-[1.15] tracking-[-0.025em] text-zinc-50">
{{ t('scATitle') }} {{ t('scATitle') }}
</h2> </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-zinc-400">{{ t('scABody') }}</p>
</div> </div>
<div class="min-w-[280px] flex-[1_1_420px]"> <div class="flex min-w-[280px] flex-[1_1_440px] flex-col gap-3">
<div class="overflow-hidden rounded-xl border border-zinc-800 bg-[#0c0c0e] shadow-card"> <!-- rail « À traiter » -->
<div class="flex items-center gap-[9px] border-b border-[#1c1c1f] bg-[rgba(69,26,3,0.18)] px-[15px] py-3"> <div class="flex flex-col gap-2 rounded-xl border border-amber-900/50 bg-amber-950/20 p-3 shadow-card">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#fcd34d" 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> <div class="flex items-center gap-2">
<span class="text-sm font-semibold text-zinc-50">{{ t('mAttn') }}</span> <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>
<span class="ml-auto rounded-full bg-amber-300/[0.12] px-[9px] py-0.5 font-mono text-[11px] text-amber-300">2</span> <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>
</div> </div>
<div class="flex flex-col gap-2 p-2.5"> <span class="truncate font-mono text-xs text-amber-300/80">~/code/arboretum/api</span>
<div class="flex items-center gap-3 rounded-[9px] border border-[rgba(120,53,15,0.55)] bg-[rgba(69,26,3,0.3)] px-[13px] py-[11px]"> <!-- DialogPrompt -->
<span class="h-2 w-2 flex-none animate-pulse-amber rounded-full bg-amber-300"></span> <div class="rounded-lg border border-amber-900/60 bg-amber-950/30 p-2.5">
<div class="min-w-0 flex-1"> <div class="flex items-center gap-2">
<div class="font-mono text-[13px] text-zinc-200">api · feat/auth</div> <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="text-xs text-[#d4a55a]">Approve change to login.ts?</div> <span class="text-[12.5px] text-amber-100">{{ t('dlgQ') }}</span>
</div>
<button type="button" class="flex-none rounded-[7px] border border-amber-300/30 bg-amber-300/[0.12] px-3 py-[5px] text-xs font-semibold text-amber-300">Answer</button>
</div> </div>
<div class="flex items-center gap-3 rounded-[9px] border border-[rgba(120,53,15,0.55)] bg-[rgba(69,26,3,0.3)] px-[13px] py-[11px]"> <div class="mt-2 flex flex-wrap gap-1.5">
<span class="h-2 w-2 flex-none animate-pulse-amber rounded-full bg-amber-300"></span> <span
<div class="min-w-0 flex-1"> v-for="(op, i) in dlgOpts"
<div class="font-mono text-[13px] text-zinc-200">web · checkout</div> :key="i"
<div class="text-xs text-[#d4a55a]">Run the failing test again?</div> 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"
</div> >
<button type="button" class="flex-none rounded-[7px] border border-amber-300/30 bg-amber-300/[0.12] px-3 py-[5px] text-xs font-semibold text-amber-300">Answer</button> <span class="font-mono text-amber-400">{{ 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>
</div> </div>
<div class="mx-0.5 my-[3px] h-px bg-[#1c1c1f]"></div> </div>
<div class="flex items-center gap-3 rounded-[9px] px-[13px] py-[9px]"> </div>
<span class="h-2 w-2 flex-none animate-pulse-sky rounded-full bg-sky-300"></span>
<div class="min-w-0 flex-1 font-mono text-[13px] text-zinc-400">db · refactor</div> <!-- section de repo : worktrees + état de session -->
<span class="flex-none font-mono text-[11px] text-sky-300">{{ t('busy') }}</span> <div class="flex flex-col gap-2 rounded-xl border border-zinc-800 bg-zinc-900/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">
<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="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>
</div> </div>
<div class="flex items-center gap-3 rounded-[9px] px-[13px] py-[9px]"> <p class="truncate font-mono text-xs text-zinc-500">~/code/arboretum</p>
<span class="h-2 w-2 flex-none rounded-full bg-emerald-400"></span> <div class="mt-1 flex items-center gap-2">
<div class="min-w-0 flex-1 font-mono text-[13px] text-zinc-400">docs · main</div> <span class="inline-flex items-center gap-1 rounded bg-zinc-800/60 px-1.5 py-0.5">
<span class="flex-none font-mono text-[11px] text-emerald-400">{{ t('idle') }}</span> <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>
<span class="font-mono text-[11px] text-zinc-400">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="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>2</span>
<span class="text-amber-400"> {{ t('mDirty') }}</span>
</span>
</div>
<p class="truncate font-mono text-xs text-zinc-500">~/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>
<span class="font-mono text-[11px] text-zinc-400">claude</span>
</span>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- b) multi-terminal grid --> <!-- b) grille multi-terminaux (fidèle à TerminalCell : badge d'état + titre repo · branche + commande) -->
<div v-reveal class="flex flex-wrap-reverse items-center gap-12"> <div v-reveal class="flex flex-wrap-reverse items-center gap-12">
<div class="min-w-[280px] flex-[1_1_420px]"> <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="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-[9px] border border-zinc-800 bg-zinc-950"> <div class="overflow-hidden rounded-lg border border-zinc-800 bg-[#09090b]">
<div class="flex items-center justify-between border-b border-[#1c1c1f] px-[11px] py-2"> <div class="flex items-center gap-2 border-b border-zinc-800 px-2 py-1.5">
<span class="font-mono text-[11px] text-zinc-400">api · feat/auth</span> <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="h-1.5 w-1.5 animate-pulse-sky rounded-full bg-sky-300"></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> </div>
<div class="p-[11px] font-mono text-[11px] leading-[1.75] text-zinc-300"> <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">● Edit session.ts</div>
@@ -70,10 +118,11 @@ const { t } = useI18n();
<div class="text-zinc-500">refactoring guards…</div> <div class="text-zinc-500">refactoring guards…</div>
</div> </div>
</div> </div>
<div class="overflow-hidden rounded-[9px] border border-zinc-800 bg-zinc-950"> <div class="overflow-hidden rounded-lg border border-zinc-800 bg-[#09090b]">
<div class="flex items-center justify-between border-b border-[#1c1c1f] px-[11px] py-2"> <div class="flex items-center gap-2 border-b border-zinc-800 px-2 py-1.5">
<span class="font-mono text-[11px] text-zinc-400">web · fix/cart</span> <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="h-1.5 w-1.5 rounded-full bg-emerald-400"></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> </div>
<div class="p-[11px] font-mono text-[11px] leading-[1.75] text-zinc-300"> <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-emerald-400">✓ all tests passing</div>
@@ -84,10 +133,11 @@ const { t } = useI18n();
</div> </div>
</div> </div>
</div> </div>
<div class="overflow-hidden rounded-[9px] border border-zinc-800 bg-zinc-950"> <div class="overflow-hidden rounded-lg border border-zinc-800 bg-[#09090b]">
<div class="flex items-center justify-between border-b border-[#1c1c1f] px-[11px] py-2"> <div class="flex items-center gap-2 border-b border-zinc-800 px-2 py-1.5">
<span class="font-mono text-[11px] text-zinc-400">db · refactor</span> <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="h-1.5 w-1.5 animate-pulse-sky rounded-full bg-sky-300"></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> </div>
<div class="p-[11px] font-mono text-[11px] leading-[1.75] text-zinc-300"> <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-emerald-400">● Write migration</div>
@@ -95,20 +145,21 @@ const { t } = useI18n();
<div class="text-zinc-500">applying to schema…</div> <div class="text-zinc-500">applying to schema…</div>
</div> </div>
</div> </div>
<div class="overflow-hidden rounded-[9px] border border-zinc-800 bg-zinc-950"> <div class="overflow-hidden rounded-lg border border-zinc-800 bg-[#09090b]">
<div class="flex items-center justify-between border-b border-[#1c1c1f] px-[11px] py-2"> <div class="flex items-center gap-2 border-b border-zinc-800 px-2 py-1.5">
<span class="font-mono text-[11px] text-zinc-400">docs · api</span> <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="h-1.5 w-1.5 animate-pulse-sky rounded-full bg-sky-300"></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> </div>
<div class="p-[11px] font-mono text-[11px] leading-[1.75] text-zinc-300"> <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-emerald-400">● Read README.md</div>
<div class="text-zinc-500">## Quick start</div> <div class="text-amber-300">Permission required</div>
<div class="text-zinc-500">writing examples</div> <div class="text-zinc-500">waiting for input…</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="min-w-[280px] flex-[1_1_380px]"> <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> <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"> <h2 class="m-0 mb-4 text-[clamp(26px,3vw,34px)] font-semibold leading-[1.15] tracking-[-0.025em] text-zinc-50">
{{ t('scBTitle') }} {{ t('scBTitle') }}
@@ -117,7 +168,7 @@ const { t } = useI18n();
</div> </div>
</div> </div>
<!-- c) mobile --> <!-- c) mobile : push + réponse au dialogue (fidèle à DialogPrompt) + barre d'onglets -->
<div v-reveal class="flex flex-wrap items-center gap-12"> <div v-reveal class="flex flex-wrap items-center gap-12">
<div class="min-w-[280px] flex-[1_1_380px]"> <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> <div class="mb-3 font-mono text-xs uppercase tracking-[0.12em] text-emerald-400">{{ t('scCKicker') }}</div>
@@ -127,11 +178,12 @@ const { t } = useI18n();
<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-zinc-400">{{ t('scCBody') }}</p>
</div> </div>
<div class="flex min-w-[280px] flex-[1_1_320px] justify-center"> <div class="flex min-w-[280px] flex-[1_1_320px] justify-center">
<div class="relative w-[288px] 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-zinc-900 bg-zinc-950 shadow-phone">
<div class="flex h-[30px] items-center justify-center"> <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-zinc-800"></div>
</div> </div>
<div class="px-[13px] pb-[22px]"> <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"> <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]"> <span class="inline-flex h-[30px] w-[30px] flex-none items-center justify-center rounded-lg bg-emerald-500/[0.14]">
<img src="/assets/arboretum-mark.png" alt="" width="18" height="18" class="h-[18px] w-[18px] object-contain" /> <img src="/assets/arboretum-mark.png" alt="" width="18" height="18" class="h-[18px] w-[18px] object-contain" />
@@ -145,24 +197,54 @@ const { t } = useI18n();
<div class="text-xs leading-[1.45] text-zinc-400">{{ t('toastBody') }}</div> <div class="text-xs leading-[1.45] text-zinc-400">{{ t('toastBody') }}</div>
</div> </div>
</div> </div>
<div class="mt-[14px] overflow-hidden rounded-[15px] border border-zinc-800 bg-zinc-950">
<div class="flex items-center gap-2 border-b border-[#1c1c1f] px-[13px] py-[11px]"> <!-- carte « À traiter » + DialogPrompt -->
<span class="h-[7px] w-[7px] animate-pulse-amber rounded-full bg-amber-300"></span> <div class="mt-[14px] flex flex-col gap-2 rounded-[15px] border border-amber-900/50 bg-amber-950/20 p-2.5">
<span class="font-mono text-[11px] text-zinc-300">feat/auth</span> <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>
</div> </div>
<div class="p-[13px]"> <span class="font-mono text-[11px] text-amber-300/80">api · feat/auth</span>
<div class="text-[13px] leading-[1.5] text-zinc-300">{{ t('dlgQ') }}</div> <div class="rounded-lg border border-amber-900/60 bg-amber-950/30 p-2.5">
<div class="mt-2 rounded-lg border border-[#1c1c1f] bg-[#0c0c0e] p-[9px] font-mono text-[11px] leading-[1.6] text-zinc-500"> <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="text-emerald-400">+ verifyToken(req)</div> <div class="mt-1.5 text-[13px] leading-[1.4] text-amber-100">{{ t('dlgQ') }}</div>
<div class="text-red-400">- skipAuth()</div> <div class="mt-2 flex flex-col gap-1.5">
</div> <span
<div class="mt-[13px] flex gap-2"> v-for="(op, i) in dlgOpts"
<button type="button" class="flex-1 rounded-lg bg-emerald-600 py-2.5 text-[13px] font-semibold text-white">{{ t('approve') }}</button> :key="i"
<button type="button" class="flex-1 rounded-lg border border-zinc-800 bg-transparent py-2.5 text-[13px] font-medium text-zinc-300">{{ t('reject') }}</button> 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"
>
<span class="font-mono text-amber-400">{{ 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>
</div> </div>
</div> </div>
</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"><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>
<div class="relative flex flex-1 flex-col items-center gap-0.5 py-2 text-emerald-400">
<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>
</span>
<span class="text-[10px]">{{ 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>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -10,8 +10,8 @@ export interface FaqItem {
export const FAQS: Record<AppLocale, FaqItem[]> = { export const FAQS: Record<AppLocale, FaqItem[]> = {
en: [ en: [
{ {
q: 'Is Arboretum an IDE?', q: 'How is this different from running Claude Code in a terminal?',
a: "No — it's a command center. It doesn't replace your editor; it supervises the AI coding agents working across your repos: spawning sessions, surfacing their state, and letting you answer them.", 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 / idle 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?', q: 'Does it need the cloud?',
@@ -23,7 +23,7 @@ export const FAQS: Record<AppLocale, FaqItem[]> = {
}, },
{ {
q: 'Does it work on mobile?', q: 'Does it work on mobile?',
a: 'Yes. The dashboard is an installable PWA. Add it to your home screen, get Web Push the moment a session needs you, and approve or reject a prompt with a single tap.', a: 'Yes. The dashboard is an installable PWA. Add it to your home screen, get Web Push the moment a session needs you, and answer a prompt with a single tap.',
}, },
{ {
q: 'Is my code safe?', q: 'Is my code safe?',
@@ -32,8 +32,8 @@ export const FAQS: Record<AppLocale, FaqItem[]> = {
], ],
fr: [ fr: [
{ {
q: 'Arboretum est-il un IDE ?', q: 'En quoi est-ce différent de lancer Claude Code dans un terminal ?',
a: "Non — c'est un poste de commandement. Il ne remplace pas votre éditeur ; il supervise les agents de code IA qui travaillent sur vos repos : lancement des sessions, affichage de leur état, et réponse à leurs questions.", 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 / inactive 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 ?', q: 'A-t-il besoin du cloud ?',
@@ -45,7 +45,7 @@ export const FAQS: Record<AppLocale, FaqItem[]> = {
}, },
{ {
q: 'Fonctionne-t-il sur mobile ?', q: 'Fonctionne-t-il sur mobile ?',
a: "Oui. Le dashboard est une PWA installable. Ajoutez-le à l'écran d'accueil, recevez un Web Push dès qu'une session a besoin de vous, et approuvez ou refusez une question d'un toucher.", a: "Oui. Le dashboard est une PWA installable. Ajoutez-le à l'écran d'accueil, recevez un Web Push dès qu'une session a besoin de vous, et répondez à une question d'un seul toucher.",
}, },
{ {
q: 'Mon code est-il en sécurité ?', q: 'Mon code est-il en sécurité ?',

View File

@@ -15,7 +15,7 @@ export default {
probBody: probBody:
'Sessions scattered across tabs. No idea which agent is waiting on you. Step away from the desk and everything stalls. Arboretum is the single pane of glass.', 'Sessions scattered across tabs. No idea which agent is waiting on you. Step away from the desk and everything stalls. Arboretum is the single pane of glass.',
featKicker: 'Everything in one view', featKicker: 'Everything in one view',
featTitle: 'Built to supervise agents — not to be an IDE', featTitle: 'Built to supervise agents working in parallel',
feat1Title: 'Worktree-first', feat1Title: 'Worktree-first',
feat1Desc: 'Work on your main branch or spin up an isolated worktree — every session stays pinned to a branch. Branches, not chaos.', feat1Desc: 'Work on your main branch or spin up an isolated worktree — every session stays pinned to a branch. Branches, not chaos.',
feat2Title: 'Many sessions, one view', feat2Title: 'Many sessions, one view',
@@ -75,17 +75,24 @@ export default {
footTag: 'a garden of branches, one pane of glass.', footTag: 'a garden of branches, one pane of glass.',
license: 'MIT License', license: 'MIT License',
coffee: 'Buy me a coffee', coffee: 'Buy me a coffee',
mDash: 'Dashboard', mWorktrees: 'Worktrees',
mSessions: 'Sessions',
mGroups: 'Groups', mGroups: 'Groups',
mSettings: 'Settings', mSettings: 'Settings',
mHelp: 'Help', mHelp: 'Help',
mGitea: 'Gitea',
mMore: 'More',
mSearch: 'Search', mSearch: 'Search',
mAttn: 'Needs attention', mAttn: 'Needs attention',
waiting: 'waiting', waiting: 'waiting',
busy: 'busy', busy: 'busy',
idle: 'idle', idle: 'idle',
approve: 'Approve', mPermission: 'Permission',
reject: 'Reject', mDeny: 'Deny (Esc)',
mMain: 'main',
mClean: 'clean',
mDirty: '3 changed',
mStart: 'Start work',
toastTitle: 'Session waiting for you', toastTitle: 'Session waiting for you',
toastBody: 'api · feat/auth needs a decision', toastBody: 'api · feat/auth needs a decision',
dlgQ: 'Apply this change to login.ts?', dlgQ: 'Apply this change to login.ts?',

View File

@@ -15,7 +15,7 @@ export default {
probBody: probBody:
"Des sessions éparpillées dans des onglets. Aucune idée de quel agent vous attend. Vous quittez le bureau et tout se fige. Arboretum, c'est la vitre unique.", "Des sessions éparpillées dans des onglets. Aucune idée de quel agent vous attend. Vous quittez le bureau et tout se fige. Arboretum, c'est la vitre unique.",
featKicker: 'Tout dans une seule vue', featKicker: 'Tout dans une seule vue',
featTitle: 'Conçu pour superviser les agents — pas pour être un IDE', featTitle: 'Conçu pour superviser des agents qui travaillent en parallèle',
feat1Title: "Worktree d'abord", feat1Title: "Worktree d'abord",
feat1Desc: 'Travaillez sur votre branche principale ou créez un worktree isolé — chaque session reste rattachée à une branche. Des branches, pas du chaos.', feat1Desc: 'Travaillez sur votre branche principale ou créez un worktree isolé — chaque session reste rattachée à une branche. Des branches, pas du chaos.',
feat2Title: 'Plusieurs sessions, une vue', feat2Title: 'Plusieurs sessions, une vue',
@@ -75,17 +75,24 @@ export default {
footTag: 'un jardin de branches, une seule vitre.', footTag: 'un jardin de branches, une seule vitre.',
license: 'Licence MIT', license: 'Licence MIT',
coffee: 'Paye-moi un café', coffee: 'Paye-moi un café',
mDash: 'Tableau de bord', mWorktrees: 'Worktrees',
mSessions: 'Sessions',
mGroups: 'Groupes', mGroups: 'Groupes',
mSettings: 'Réglages', mSettings: 'Réglages',
mHelp: 'Aide', mHelp: 'Aide',
mGitea: 'Gitea',
mMore: 'Plus',
mSearch: 'Rechercher', mSearch: 'Rechercher',
mAttn: 'À traiter', mAttn: 'À traiter',
waiting: 'en attente', waiting: 'en attente',
busy: 'occupé', busy: 'occupée',
idle: 'au repos', idle: 'inactive',
approve: 'Approuver', mPermission: 'Permission',
reject: 'Refuser', mDeny: 'Refuser (Échap)',
mMain: 'principal',
mClean: 'propre',
mDirty: '3 modifiés',
mStart: 'Démarrer',
toastTitle: 'Une session vous attend', toastTitle: 'Une session vous attend',
toastBody: 'api · feat/auth attend une décision', toastBody: 'api · feat/auth attend une décision',
dlgQ: 'Appliquer ce changement à login.ts ?', dlgQ: 'Appliquer ce changement à login.ts ?',