feat: Forge design system app-wide + Stacks i18n
Build / build (push) Successful in 10m47s

Promotes the Forge visual language from the Stacks feature into a
global design system used across the app:

- app.css: Forge utilities (dot-grid backdrop, eyebrow, ember,
  display/lede, status pills, stat grid, panels, registration
  marks, alert, terminal, buttons). CSS variables alias the forge
  display font to the app's standard sans stack (Inter, now
  properly self-hosted via @fontsource/inter).
- +layout.svelte: reskinned sidebar brand, active nav rail,
  mobile top bar, global h1/h2 typography overrides, main dot-grid
  backdrop.
- Shared components reskinned: EmptyState (breathing-ember empty
  mark), StatusBadge (mono pills with pulse), ConfirmDialog
  (registration marks + forge buttons).
- Dashboard (+page.svelte): ForgeHero header, forge-stat-grid,
  Instrument-style section titles with accent.
- New ForgeHero component for reusable hero headers.

Stacks feature fully localized (EN + RU):
- 80+ keys under stacks.* covering list, new, detail, revisions,
  logs, errors, status labels, delete/rollback dialogs.
- Russian uses forge vocabulary (куются/наковальня/куём/etc).
- $t() wired through all three Stacks pages.
This commit is contained in:
2026-04-16 04:17:42 +03:00
parent 75424a5f25
commit 0fd92fdfa3
14 changed files with 1251 additions and 343 deletions
+76 -17
View File
@@ -129,13 +129,9 @@
{sidebarOpen ? 'translate-x-0' : '-translate-x-full'}"
>
<!-- Logo -->
<div class="flex h-16 items-center gap-2.5 border-b border-[var(--border-primary)] px-5">
<div class="flex h-8 w-8 items-center justify-center rounded-lg bg-[var(--color-brand-600)]">
<svg class="h-4.5 w-4.5 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 7.5l-2.25-1.313M21 7.5v2.25m0-2.25l-2.25 1.313M3 7.5l2.25-1.313M3 7.5l2.25 1.313M3 7.5v2.25m9 3l2.25-1.313M12 12.75l-2.25-1.313M12 12.75V15m0 6.75l2.25-1.313M12 21.75V19.5m0 2.25l-2.25-1.313m0-16.875L12 2.25l2.25 1.313M21 14.25v2.25l-2.25 1.313m-13.5 0L3 16.5v-2.25" />
</svg>
</div>
<span class="text-base font-bold text-[var(--text-primary)]">{$t('app.name')}</span>
<div class="brand flex h-16 items-center gap-2.5 border-b border-[var(--border-primary)] px-5">
<span class="forge-ember brand-ember"></span>
<span class="brand-name">{$t('app.name')}</span>
<!-- Close sidebar (mobile) -->
<button
@@ -153,10 +149,8 @@
{@const active = isActive(item.href, $page.url.pathname)}
<a
href={item.href}
class="group flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-150
{active
? 'bg-[var(--color-brand-50)] text-[var(--color-brand-700)] shadow-sm'
: 'text-[var(--text-secondary)] hover:bg-[var(--surface-card-hover)] hover:text-[var(--text-primary)]'}"
class="nav-item group flex items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-all duration-150
{active ? 'nav-active' : 'text-[var(--text-secondary)] hover:bg-[var(--surface-card-hover)] hover:text-[var(--text-primary)]'}"
>
{#if item.icon === 'dashboard'}
<IconDashboard size={18} class="{active ? 'text-[var(--color-brand-600)]' : 'text-[var(--text-tertiary)] group-hover:text-[var(--text-secondary)]'} transition-colors duration-150" />
@@ -276,12 +270,8 @@
>
<IconMenu size={22} />
</button>
<div class="flex h-7 w-7 items-center justify-center rounded-lg bg-[var(--color-brand-600)]">
<svg class="h-3.5 w-3.5 text-white" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 7.5l-2.25-1.313M21 7.5v2.25m0-2.25l-2.25 1.313M3 7.5l2.25-1.313M3 7.5l2.25 1.313M3 7.5v2.25m9 3l2.25-1.313M12 12.75l-2.25-1.313M12 12.75V15m0 6.75l2.25-1.313M12 21.75V19.5m0 2.25l-2.25-1.313m0-16.875L12 2.25l2.25 1.313M21 14.25v2.25l-2.25 1.313m-13.5 0L3 16.5v-2.25" />
</svg>
</div>
<span class="text-sm font-bold text-[var(--text-primary)]">{$t('app.name')}</span>
<span class="forge-ember"></span>
<span class="brand-name">{$t('app.name')}</span>
</header>
<!-- Page content -->
@@ -295,3 +285,72 @@
{/if}
<Toast />
<style>
/* ── Forge-themed layout shell ─────────────────────────────────── */
/* Page titles — larger + tighter tracking, but using the app's sans stack */
:global(main h1) {
font-family: var(--font-family-sans) !important;
font-weight: 700 !important;
letter-spacing: -0.02em !important;
font-size: clamp(1.875rem, 4vw, 2.5rem) !important;
line-height: 1.1 !important;
color: var(--text-primary);
}
:global(main h2) {
font-family: var(--font-family-sans);
font-weight: 600;
letter-spacing: -0.01em;
}
:global(main code) {
font-family: var(--forge-mono, 'JetBrains Mono', monospace);
}
.brand {
gap: 0.75rem;
}
.brand-ember {
width: 10px; height: 10px;
}
.brand-name {
font-family: var(--font-family-sans);
font-weight: 700;
font-size: 1.05rem;
line-height: 1;
letter-spacing: -0.02em;
color: var(--text-primary);
}
.nav-item :global(svg) { flex-shrink: 0; }
.nav-active {
background: var(--surface-card-hover);
color: var(--text-primary) !important;
position: relative;
}
.nav-active::before {
content: '';
position: absolute;
left: -12px; top: 20%; bottom: 20%;
width: 3px;
background: var(--color-brand-600);
border-radius: 0 3px 3px 0;
}
/* Apply dot-grid backdrop to main content */
:global(main) {
position: relative;
isolation: isolate;
}
:global(main)::before {
content: '';
position: absolute;
top: 0; left: 0; right: 0; height: 480px;
background-image: radial-gradient(var(--border-primary) 1px, transparent 1px);
background-size: 22px 22px;
-webkit-mask-image: radial-gradient(ellipse at 20% 0%, #000 0%, transparent 70%);
mask-image: radial-gradient(ellipse at 20% 0%, #000 0%, transparent 70%);
pointer-events: none;
z-index: -1;
opacity: 0.7;
}
</style>