Files
allmy.work/docs/plans/2026-03-06-portfolio-foundation.md

1976 lines
57 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Portfolio Foundation Implementation Plan
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
**Goal:** Bootstrap the allmywork SvelteKit portfolio with a CLAUDE.md, a brutalist design system extracted from the Figma AI prototype, shared/ui components converted from React to Svelte 5, and Storybook stories for each component.
**Architecture:** Tailwind CSS v4 custom properties carry the design tokens (colors, typography, spacing, shadows). All UI primitives live in `src/shared/ui/`, each as a PascalCase folder with `ComponentName.svelte`, `types.ts`, `index.ts`, and `ComponentName.stories.svelte`. The FSD directory structure is already in place; this plan fills in the foundation layer only.
**Tech Stack:** SvelteKit 2 + Svelte 5 (runes), Tailwind CSS v4, Biome (lint/format), Vitest (unit tests), Storybook 10 with `@storybook/addon-svelte-csf`, Bun as package manager and runtime.
---
## Task 0: Write CLAUDE.md for the project
**Files:**
- Modify: `/home/ilia/Documents/Projects/allmywork/CLAUDE.md`
The existing CLAUDE.md covers project purpose and tooling. We need to add the code-style section that mirrors glyphdiff's conventions.
**Step 1: Read the existing CLAUDE.md**
Read `/home/ilia/Documents/Projects/allmywork/CLAUDE.md` in full.
**Step 2: Append the Code Style section**
Add the following section to the end of the existing CLAUDE.md (before any trailing lines):
```markdown
---
## Code Style
### Formatting (Biome)
- Indent: tabs
- Quote style: double quotes for JS/TS, double quotes for Svelte attributes
- Line width: 120 characters
- Semicolons: always
- Trailing commas: all
### TypeScript
- Strict mode: on
- Prefer `interface` over `type` for object shapes
- Always type component props via an `interface Props { ... }` block
- Use `$props()` rune — never `export let`
- Use `$derived()` and `$effect()` — never Svelte 4 `$:` syntax
- Use `$state()` for all reactive primitives
### Component Conventions
- File name: PascalCase (`Button.svelte`, `ProjectCard.svelte`)
- Store files: camelCase with `.svelte.ts` suffix (`sidebarStore.svelte.ts`)
- Story files: `.stories.svelte` suffix (Storybook CSF with Svelte addon)
- Test files: `.test.ts` for unit, `.svelte.test.ts` for component tests
- Each slice exports a public API via `index.ts`
### Svelte 5 Patterns
```svelte
<script lang="ts">
import { cn } from '$shared/lib/cn';
import type { Snippet } from 'svelte';
import type { HTMLButtonAttributes } from 'svelte/elements';
interface Props extends HTMLButtonAttributes {
variant?: 'primary' | 'secondary';
children: Snippet;
class?: string;
}
let { variant = 'primary', children, class: className, ...rest }: Props = $props();
const classes = $derived(cn(baseStyles, variantStyles[variant], className));
</script>
<button class={classes} {...rest}>
{@render children()}
</button>
```
### Comments
- Multiline JSDoc (`/** ... */`) for exported functions and component description headers
- Single-line (`//`) for inline logic remarks
- Principle: "less is more" — explain the *why*, not the *what*
### Import Order (Biome organizeImports)
1. Svelte/SvelteKit built-ins (`svelte`, `$app/...`)
2. FSD layer aliases (`$shared/...`, `$entities/...`, etc.)
3. Relative imports (`./`, `../`)
### Commit Messages
Format: `prefix(scope): short description`
Prefixes: `feat`, `fix`, `chore`, `refactor`, `docs`, `style`, `test`, `perf`, `ci`, `build`, `revert`
Example: `feat(shared/ui): add Button component with brutalist variants`
```
**Step 3: Verify the file looks correct**
Read the first and last 20 lines of the updated CLAUDE.md to confirm the edit landed cleanly.
**Step 4: Commit**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add CLAUDE.md
git commit -m "docs(CLAUDE.md): add code style section aligned with glyphdiff conventions"
```
---
## Task 1: Install Storybook and required dev dependencies
The project currently has no Storybook. This task wires it up before any stories are written.
**Files:**
- Modify: `/home/ilia/Documents/Projects/allmywork/package.json` (via bun add)
- Create: `/home/ilia/Documents/Projects/allmywork/.storybook/main.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/.storybook/preview.ts`
**Step 1: Install Storybook and addons**
```bash
cd /home/ilia/Documents/Projects/allmywork
bunx storybook@latest init --skip-install
```
Expected output: Storybook scaffolds `.storybook/` and adds scripts to package.json. Answer "yes" to any prompts.
**Step 2: Verify generated .storybook/main.ts**
Read `/home/ilia/Documents/Projects/allmywork/.storybook/main.ts`.
It should reference `@storybook/svelte-vite` as the framework.
If the `init` command did not produce a valid Svelte-Vite config, write `.storybook/main.ts` manually:
```typescript
import type { StorybookConfig } from '@storybook/svelte-vite';
const config: StorybookConfig = {
stories: ['../src/**/*.stories.svelte'],
addons: [
'@storybook/addon-svelte-csf',
'@storybook/addon-a11y',
'@storybook/addon-docs',
],
framework: {
name: '@storybook/svelte-vite',
options: {},
},
};
export default config;
```
**Step 3: Install missing addons if needed**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun add -D @storybook/addon-svelte-csf @storybook/addon-a11y @storybook/addon-docs
```
**Step 4: Add Storybook scripts to package.json (if not already present)**
Open `/home/ilia/Documents/Projects/allmywork/package.json` and confirm the `scripts` section contains:
```json
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
```
If missing, add them.
**Step 5: Run Storybook to confirm it starts**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run storybook &
```
Expected: Storybook starts on http://localhost:6006 with no fatal errors.
Kill it after confirming: `kill %1` or `Ctrl+C`.
**Step 6: Commit**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add .storybook package.json
git commit -m "chore(storybook): install and configure Storybook 10 with svelte-vite"
```
---
## Task 2: Set up the design system — CSS custom properties and Tailwind v4 tokens
Extract the design tokens from the Figma prototype's `theme.css` into the project's `app.css` and register them as Tailwind v4 theme values.
**Files:**
- Modify: `/home/ilia/Documents/Projects/allmywork/src/app/styles/app.css`
**Reference:** `/home/ilia/Downloads/High-End Portfolio Design System(1)/src/styles/theme.css` and `fonts.css`
**Step 1: Open app.css and replace its contents**
The file currently only contains `@import "tailwindcss";`. Replace it with the following:
```css
@import "tailwindcss";
/* ============================================================
FONTS
============================================================ */
@import url('https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght,SOFT,WONK@0,9..144,100..900,0..100,0..1;1,9..144,100..900,0..100,0..1&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Public+Sans:ital,wght@0,100..900;1,100..900&display=swap');
/* ============================================================
DESIGN TOKENS
============================================================ */
:root {
/* Typography scale — Augmented Fourth (×1.414) */
--font-size-base: 16px;
--text-xs: 0.707rem; /* 11.31px */
--text-sm: 0.840rem; /* 13.44px */
--text-base: 1rem; /* 16px */
--text-lg: 1.414rem; /* 22.62px */
--text-xl: 2rem; /* 32px */
--text-2xl: 2.828rem; /* 45.25px */
--text-3xl: 4rem; /* 64px */
--text-4xl: 5.657rem; /* 90.51px */
--text-5xl: 8rem; /* 128px */
/* Font families */
--font-heading: 'Fraunces', serif;
--font-body: 'Public Sans', sans-serif;
/* Font weights */
--font-weight-heading: 900;
--font-weight-body: 600;
--font-weight-normal: 400;
/* Line heights */
--line-height-tight: 1.2;
--line-height-normal: 1.5;
/* Fraunces variable font axes */
--fraunces-wonk: 1;
--fraunces-soft: 0;
/* Palette — Sanzo Wada inspired */
--ochre-clay: #D9B48F;
--slate-indigo: #3B4A59;
--burnt-oxide: #A64B35;
--carbon-black: #121212;
/* Semantic color aliases */
--background: var(--ochre-clay);
--foreground: var(--carbon-black);
--card: var(--ochre-clay);
--card-foreground: var(--carbon-black);
--primary: var(--burnt-oxide);
--primary-foreground: var(--ochre-clay);
--secondary: var(--slate-indigo);
--secondary-foreground: var(--ochre-clay);
--muted: var(--slate-indigo);
--muted-foreground: var(--ochre-clay);
--accent: var(--burnt-oxide);
--accent-foreground: var(--ochre-clay);
--destructive: #d4183d;
--destructive-foreground:#ffffff;
--border: var(--carbon-black);
--ring: var(--carbon-black);
/* Spacing — 8pt linear scale */
--space-1: 0.5rem; /* 8px */
--space-2: 1rem; /* 16px */
--space-3: 1.5rem; /* 24px */
--space-4: 2rem; /* 32px */
--space-5: 2.5rem; /* 40px */
--space-6: 3rem; /* 48px */
--space-8: 4rem; /* 64px */
--space-10: 5rem; /* 80px */
--space-12: 6rem; /* 96px */
--space-16: 8rem; /* 128px */
--space-20: 10rem; /* 160px */
/* Borders */
--border-width: 3px;
--radius: 0px;
/* Brutalist hard shadows */
--shadow-brutal-sm: 4px 4px 0 var(--carbon-black);
--shadow-brutal: 8px 8px 0 var(--carbon-black);
--shadow-brutal-lg: 12px 12px 0 var(--carbon-black);
}
/* ============================================================
TAILWIND v4 THEME REGISTRATION
============================================================ */
@theme inline {
/* Colors */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-ring: var(--ring);
/* Palette direct access */
--color-ochre-clay: var(--ochre-clay);
--color-slate-indigo: var(--slate-indigo);
--color-burnt-oxide: var(--burnt-oxide);
--color-carbon-black: var(--carbon-black);
/* Border radius (zero for brutalist aesthetic) */
--radius-sm: var(--radius);
--radius-md: var(--radius);
--radius-lg: var(--radius);
/* Box shadows */
--shadow-brutal-sm: var(--shadow-brutal-sm);
--shadow-brutal: var(--shadow-brutal);
--shadow-brutal-lg: var(--shadow-brutal-lg);
/* Font families */
--font-heading: var(--font-heading);
--font-body: var(--font-body);
}
/* ============================================================
BASE LAYER — GLOBAL ELEMENT DEFAULTS
============================================================ */
@layer base {
html {
font-size: var(--font-size-base);
}
body {
background-color: var(--background);
color: var(--foreground);
font-family: var(--font-body);
font-weight: var(--font-weight-body);
line-height: var(--line-height-tight);
overflow-x: hidden;
position: relative;
}
/* Paper grain texture overlay */
body::before {
content: '';
position: fixed;
inset: 0;
background-image:
repeating-linear-gradient(0deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px),
repeating-linear-gradient(90deg, transparent, transparent 2px, rgba(0,0,0,.02) 2px, rgba(0,0,0,.02) 4px);
opacity: .4;
pointer-events: none;
z-index: 1;
}
/* Ensure page content renders above texture */
body > * {
position: relative;
z-index: 2;
}
h1, .h1 { font-size: var(--text-4xl); }
h2, .h2 { font-size: var(--text-3xl); }
h3, .h3 { font-size: var(--text-2xl); }
h4, .h4 { font-size: var(--text-xl); }
h5, .h5 { font-size: var(--text-lg); }
h1, h2, h3, h4, h5,
.h1, .h2, .h3, .h4, .h5 {
font-family: var(--font-heading);
font-weight: var(--font-weight-heading);
line-height: var(--line-height-tight);
font-variation-settings: 'WONK' var(--fraunces-wonk), 'SOFT' var(--fraunces-soft);
color: var(--carbon-black);
margin: 0;
}
p {
font-family: var(--font-body);
font-size: var(--text-base);
font-weight: var(--font-weight-body);
line-height: var(--line-height-tight);
color: var(--carbon-black);
margin: 0;
}
label {
font-family: var(--font-body);
font-size: var(--text-sm);
font-weight: var(--font-weight-body);
text-transform: uppercase;
letter-spacing: .05em;
line-height: var(--line-height-tight);
}
button {
font-family: var(--font-body);
font-size: var(--text-base);
font-weight: var(--font-weight-body);
line-height: var(--line-height-tight);
cursor: pointer;
}
input, textarea, select {
font-family: var(--font-body);
font-size: var(--text-base);
font-weight: var(--font-weight-body);
line-height: var(--line-height-tight);
}
blockquote {
font-family: var(--font-heading);
font-size: var(--text-xl);
font-weight: var(--font-weight-heading);
font-variation-settings: 'WONK' var(--fraunces-wonk), 'SOFT' var(--fraunces-soft);
line-height: var(--line-height-tight);
border-left: var(--border-width) solid var(--carbon-black);
padding-left: var(--space-4);
margin: var(--space-6) 0;
}
a {
color: var(--burnt-oxide);
text-decoration: none;
border-bottom: 2px solid var(--carbon-black);
transition: border-bottom-width .15s ease;
}
a:hover {
border-bottom-width: 4px;
}
}
/* ============================================================
BRUTALIST UTILITY CLASSES
============================================================ */
.brutal-border { border: var(--border-width) solid var(--carbon-black); }
.brutal-border-top { border-top: var(--border-width) solid var(--carbon-black); }
.brutal-border-bottom { border-bottom: var(--border-width) solid var(--carbon-black); }
.brutal-border-left { border-left: var(--border-width) solid var(--carbon-black); }
.brutal-border-right { border-right: var(--border-width) solid var(--carbon-black); }
.brutal-shadow { box-shadow: var(--shadow-brutal); }
.brutal-shadow-sm { box-shadow: var(--shadow-brutal-sm); }
.brutal-shadow-lg { box-shadow: var(--shadow-brutal-lg); }
/* ============================================================
GRID UTILITIES
============================================================ */
.grid-muller {
display: grid;
gap: var(--space-3);
grid-template-columns: repeat(4, 1fr);
}
@media (min-width: 1024px) {
.grid-muller {
grid-template-columns: repeat(12, 1fr);
}
}
/* ============================================================
ANIMATION
============================================================ */
@keyframes fadeInUp {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-fade-in-up {
animation: fadeInUp .5s cubic-bezier(.4, 0, .2, 1);
}
```
**Step 2: Verify dev server still compiles**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run dev &
```
Expected: No CSS errors in the terminal. Visit http://localhost:5173 — the page background should be ochre-clay (#D9B48F).
Kill dev server: `kill %1`
**Step 3: Commit**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add src/app/styles/app.css
git commit -m "feat(design-system): add brutalist design tokens and Tailwind v4 theme from Figma prototype"
```
---
## Task 3: Add the `cn` utility to shared/lib
Many components will use `cn` (clsx + tailwind-merge) to compose class names. This task adds the helper before any component uses it.
**Files:**
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/lib/cn.ts`
- Modify: `/home/ilia/Documents/Projects/allmywork/src/shared/lib/index.ts`
**Step 1: Install clsx and tailwind-merge**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun add clsx tailwind-merge
```
Expected: Both packages appear in `package.json` under `dependencies`.
**Step 2: Write failing test**
Create `/home/ilia/Documents/Projects/allmywork/src/shared/lib/cn.test.ts`:
```typescript
import { describe, expect, it } from 'vitest';
import { cn } from './cn';
describe('cn', () => {
it('merges class strings', () => {
expect(cn('foo', 'bar')).toBe('foo bar');
});
it('deduplicates conflicting Tailwind classes (last wins)', () => {
expect(cn('p-4', 'p-8')).toBe('p-8');
});
it('ignores falsy values', () => {
expect(cn('foo', false, undefined, null, 'bar')).toBe('foo bar');
});
});
```
**Step 3: Run test to confirm it fails**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/lib/cn.test.ts
```
Expected: FAIL — `Cannot find module './cn'`
**Step 4: Implement cn.ts**
```typescript
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
/** Merge Tailwind classes without conflicts. Later classes win on collision. */
export function cn(...inputs: ClassValue[]): string {
return twMerge(clsx(inputs));
}
```
**Step 5: Run test to confirm it passes**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/lib/cn.test.ts
```
Expected: PASS (3 tests)
**Step 6: Export from shared/lib/index.ts**
Open `/home/ilia/Documents/Projects/allmywork/src/shared/lib/index.ts` and add:
```typescript
export { cn } from './cn';
```
**Step 7: Commit**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add src/shared/lib/cn.ts src/shared/lib/cn.test.ts src/shared/lib/index.ts package.json
git commit -m "feat(shared/lib): add cn utility (clsx + tailwind-merge)"
```
---
## Task 4: Implement `Badge` component
**Files:**
- Modify: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/` — replace flat files with folder-per-component layout
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Badge/types.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Badge/Badge.svelte`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Badge/index.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Badge/Badge.stories.svelte`
**Reference prototype:** `/home/ilia/Downloads/High-End Portfolio Design System(1)/src/app/components/Badge.tsx`
**Step 1: Create types.ts**
```typescript
export type BadgeVariant = 'default' | 'primary' | 'secondary' | 'outline';
```
**Step 2: Write failing component test**
Create `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Badge/Badge.test.ts`:
```typescript
import { render } from '@testing-library/svelte';
import { describe, expect, it } from 'vitest';
import Badge from './Badge.svelte';
describe('Badge', () => {
it('renders children text', () => {
const { getByText } = render(Badge, { props: { children: () => 'hello' } });
expect(getByText('hello')).toBeTruthy();
});
it('applies default variant class', () => {
const { container } = render(Badge, { props: { variant: 'default' } });
expect(container.querySelector('span')?.className).toContain('bg-carbon-black');
});
});
```
**Step 3: Run test to confirm failure**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Badge/Badge.test.ts
```
Expected: FAIL — component file not found.
**Step 4: Implement Badge.svelte**
```svelte
<!--
Component: Badge
Inline label for tags and status indicators. Brutalist border, uppercase, no radius.
-->
<script lang="ts">
import { cn } from '$shared/lib/cn';
import type { Snippet } from 'svelte';
import type { BadgeVariant } from './types';
interface Props {
/** Visual style variant @default 'default' */
variant?: BadgeVariant;
children?: Snippet;
class?: string;
}
let { variant = 'default', children, class: className }: Props = $props();
const variantStyles: Record<BadgeVariant, string> = {
default: 'brutal-border bg-carbon-black text-ochre-clay',
primary: 'brutal-border bg-burnt-oxide text-ochre-clay',
secondary: 'brutal-border bg-slate-indigo text-ochre-clay',
outline: 'brutal-border bg-transparent text-carbon-black',
};
const classes = $derived(cn(
'inline-block px-3 py-1 text-xs uppercase tracking-wider',
variantStyles[variant],
className,
));
</script>
<span class={classes}>
{#if children}
{@render children()}
{/if}
</span>
```
**Step 5: Create index.ts**
```typescript
export { default as Badge } from './Badge.svelte';
export type { BadgeVariant } from './types';
```
**Step 6: Run test to confirm it passes**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Badge/Badge.test.ts
```
Expected: PASS
**Step 7: Write Badge.stories.svelte**
```svelte
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import Badge from './Badge.svelte';
const { Story } = defineMeta({
title: 'Shared/Badge',
component: Badge,
tags: ['autodocs'],
parameters: {
layout: 'centered',
docs: { description: { component: 'Brutalist inline label. Uppercase, 3px border, zero radius.' } },
},
argTypes: {
variant: {
control: 'select',
options: ['default', 'primary', 'secondary', 'outline'],
},
},
});
</script>
<Story name="Default">
{#snippet template(args)}
<Badge {...args}>Tag</Badge>
{/snippet}
</Story>
<Story name="Primary" args={{ variant: 'primary' }}>
{#snippet template(args)}
<Badge {...args}>Primary</Badge>
{/snippet}
</Story>
<Story name="Secondary" args={{ variant: 'secondary' }}>
{#snippet template(args)}
<Badge {...args}>Secondary</Badge>
{/snippet}
</Story>
<Story name="Outline" args={{ variant: 'outline' }}>
{#snippet template(args)}
<Badge {...args}>Outline</Badge>
{/snippet}
</Story>
<Story name="All variants">
{#snippet template()}
<div class="flex gap-3 flex-wrap">
<Badge variant="default">Default</Badge>
<Badge variant="primary">Primary</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="outline">Outline</Badge>
</div>
{/snippet}
</Story>
```
**Step 8: Commit**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add src/shared/ui/Badge/
git commit -m "feat(shared/ui): add Badge component with brutalist variants and Storybook story"
```
---
## Task 5: Implement `Button` component (replacing the placeholder)
**Files:**
- Delete: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Button.svelte` (placeholder)
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Button/types.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Button/Button.svelte`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Button/index.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Button/Button.stories.svelte`
- Modify: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/index.ts`
**Reference prototype:** `/home/ilia/Downloads/High-End Portfolio Design System(1)/src/app/components/Button.tsx`
**Step 1: Create types.ts**
```typescript
export type ButtonVariant = 'primary' | 'secondary' | 'outline' | 'ghost';
export type ButtonSize = 'sm' | 'md' | 'lg';
```
**Step 2: Write failing test**
Create `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Button/Button.test.ts`:
```typescript
import { render } from '@testing-library/svelte';
import { describe, expect, it } from 'vitest';
import Button from './Button.svelte';
describe('Button', () => {
it('renders children', () => {
const { getByText } = render(Button, { props: { children: () => 'Click me' } });
expect(getByText('Click me')).toBeTruthy();
});
it('is disabled when disabled prop is true', () => {
const { container } = render(Button, { props: { disabled: true } });
expect(container.querySelector('button')?.disabled).toBe(true);
});
it('applies primary variant styles', () => {
const { container } = render(Button, { props: { variant: 'primary' } });
expect(container.querySelector('button')?.className).toContain('bg-burnt-oxide');
});
});
```
**Step 3: Run test to confirm failure**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Button/Button.test.ts
```
Expected: FAIL
**Step 4: Implement Button.svelte**
```svelte
<!--
Component: Button
Brutalist button. 3px border, hard shadow on hover, uppercase, zero radius.
Hover: translates 2px down-right with reduced shadow for press feel.
-->
<script lang="ts">
import { cn } from '$shared/lib/cn';
import type { Snippet } from 'svelte';
import type { HTMLButtonAttributes } from 'svelte/elements';
import type { ButtonSize, ButtonVariant } from './types';
interface Props extends HTMLButtonAttributes {
/** Visual style @default 'primary' */
variant?: ButtonVariant;
/** Size preset @default 'md' */
size?: ButtonSize;
children?: Snippet;
class?: string;
}
let {
variant = 'primary',
size = 'md',
children,
class: className,
type = 'button',
disabled,
...rest
}: Props = $props();
const base =
'brutal-border transition-all duration-200 ' +
'hover:translate-x-[2px] hover:translate-y-[2px] hover:shadow-[6px_6px_0_var(--carbon-black)] ' +
'active:translate-x-[4px] active:translate-y-[4px] active:shadow-[4px_4px_0_var(--carbon-black)] ' +
'uppercase tracking-wider inline-flex items-center justify-center ' +
'disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none';
const variantStyles: Record<ButtonVariant, string> = {
primary: 'brutal-shadow bg-burnt-oxide text-ochre-clay',
secondary: 'brutal-shadow bg-slate-indigo text-ochre-clay',
outline: 'brutal-shadow bg-transparent text-carbon-black',
ghost: 'bg-ochre-clay text-carbon-black shadow-none hover:shadow-[6px_6px_0_var(--carbon-black)]',
};
const sizeStyles: Record<ButtonSize, string> = {
sm: 'px-4 py-2 text-sm',
md: 'px-6 py-3 text-base',
lg: 'px-8 py-4 text-lg',
};
const classes = $derived(cn(base, variantStyles[variant], sizeStyles[size], className));
</script>
<button {type} {disabled} class={classes} {...rest}>
{#if children}
{@render children()}
{/if}
</button>
```
**Step 5: Create index.ts**
```typescript
export { default as Button } from './Button.svelte';
export type { ButtonVariant, ButtonSize } from './types';
```
**Step 6: Run test to confirm pass**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Button/Button.test.ts
```
Expected: PASS (3 tests)
**Step 7: Update shared/ui/index.ts**
Open `/home/ilia/Documents/Projects/allmywork/src/shared/ui/index.ts`. Replace the existing content that referenced the flat `Button.svelte` and `Input.svelte` with:
```typescript
export * from './Badge';
export * from './Button';
```
(More exports will be appended as components are added in subsequent tasks.)
**Step 8: Remove the placeholder flat files**
```bash
rm /home/ilia/Documents/Projects/allmywork/src/shared/ui/Button.svelte
rm /home/ilia/Documents/Projects/allmywork/src/shared/ui/Input.svelte
```
Note: `Input.svelte` is removed here because Task 8 will add a proper `Input/` folder.
**Step 9: Fix import in HomePage.svelte**
Open `/home/ilia/Documents/Projects/allmywork/src/pages/home/ui/HomePage.svelte`.
The existing import `import { Button } from '$shared/ui';` should still work because index.ts now re-exports Button. Verify the dev server compiles:
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run dev &
# visit http://localhost:5173 — page should load
kill %1
```
**Step 10: Write Button.stories.svelte**
```svelte
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import Button from './Button.svelte';
const { Story } = defineMeta({
title: 'Shared/Button',
component: Button,
tags: ['autodocs'],
parameters: {
layout: 'centered',
docs: { description: { component: 'Brutalist CTA button. Hard shadow, uppercase, hover translates 2px.' } },
},
argTypes: {
variant: { control: 'select', options: ['primary', 'secondary', 'outline', 'ghost'] },
size: { control: 'select', options: ['sm', 'md', 'lg'] },
},
});
</script>
<Story name="Primary" args={{ variant: 'primary', size: 'md' }}>
{#snippet template(args)}
<Button {...args}>Primary</Button>
{/snippet}
</Story>
<Story name="Secondary" args={{ variant: 'secondary', size: 'md' }}>
{#snippet template(args)}
<Button {...args}>Secondary</Button>
{/snippet}
</Story>
<Story name="Outline" args={{ variant: 'outline', size: 'md' }}>
{#snippet template(args)}
<Button {...args}>Outline</Button>
{/snippet}
</Story>
<Story name="Ghost" args={{ variant: 'ghost', size: 'md' }}>
{#snippet template(args)}
<Button {...args}>Ghost</Button>
{/snippet}
</Story>
<Story name="All sizes">
{#snippet template()}
<div class="flex gap-4 items-center">
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>
</div>
{/snippet}
</Story>
<Story name="Disabled">
{#snippet template()}
<Button disabled>Disabled</Button>
{/snippet}
</Story>
```
**Step 11: Commit**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add src/shared/ui/Button/ src/shared/ui/index.ts
git commit -m "feat(shared/ui): implement brutalist Button component replacing placeholder"
```
---
## Task 6: Implement `Card` component
**Files:**
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Card/types.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Card/Card.svelte`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Card/index.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Card/Card.stories.svelte`
**Reference prototype:** `/home/ilia/Downloads/High-End Portfolio Design System(1)/src/app/components/Card.tsx`
**Step 1: Create types.ts**
```typescript
export type CardBackground = 'ochre' | 'slate' | 'white';
```
**Step 2: Write failing test**
Create `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Card/Card.test.ts`:
```typescript
import { render } from '@testing-library/svelte';
import { describe, expect, it } from 'vitest';
import Card from './Card.svelte';
describe('Card', () => {
it('renders with default ochre background', () => {
const { container } = render(Card);
expect(container.querySelector('div')?.className).toContain('bg-ochre-clay');
});
it('renders with slate background', () => {
const { container } = render(Card, { props: { background: 'slate' } });
expect(container.querySelector('div')?.className).toContain('bg-slate-indigo');
});
it('renders with white background', () => {
const { container } = render(Card, { props: { background: 'white' } });
expect(container.querySelector('div')?.className).toContain('bg-white');
});
});
```
**Step 3: Run test to confirm failure**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Card/Card.test.ts
```
Expected: FAIL
**Step 4: Implement Card.svelte**
```svelte
<!--
Component: Card
Brutalist content container. 3px border, 8px hard shadow, zero radius.
Backgrounds: ochre (default), slate (dark), white.
-->
<script lang="ts">
import { cn } from '$shared/lib/cn';
import type { Snippet } from 'svelte';
import type { CardBackground } from './types';
interface Props {
/** Background color preset @default 'ochre' */
background?: CardBackground;
/** Remove default padding @default false */
noPadding?: boolean;
children?: Snippet;
class?: string;
}
let { background = 'ochre', noPadding = false, children, class: className }: Props = $props();
const backgroundStyles: Record<CardBackground, string> = {
ochre: 'bg-ochre-clay text-carbon-black',
slate: 'bg-slate-indigo text-ochre-clay',
white: 'bg-white text-carbon-black',
};
const classes = $derived(cn(
'brutal-border brutal-shadow',
backgroundStyles[background],
!noPadding && 'p-6 md:p-8',
className,
));
</script>
<div class={classes}>
{#if children}
{@render children()}
{/if}
</div>
```
**Step 5: Create index.ts**
```typescript
export { default as Card } from './Card.svelte';
export type { CardBackground } from './types';
```
**Step 6: Run test to confirm pass**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Card/Card.test.ts
```
Expected: PASS (3 tests)
**Step 7: Update shared/ui/index.ts** — append:
```typescript
export * from './Card';
```
**Step 8: Write Card.stories.svelte**
```svelte
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import Card from './Card.svelte';
const { Story } = defineMeta({
title: 'Shared/Card',
component: Card,
tags: ['autodocs'],
parameters: {
layout: 'padded',
docs: { description: { component: 'Brutalist card. 3px border, hard shadow, three background presets.' } },
},
argTypes: {
background: { control: 'select', options: ['ochre', 'slate', 'white'] },
noPadding: { control: 'boolean' },
},
});
</script>
<Story name="Ochre (default)" args={{ background: 'ochre' }}>
{#snippet template(args)}
<Card {...args}>Card content goes here</Card>
{/snippet}
</Story>
<Story name="Slate" args={{ background: 'slate' }}>
{#snippet template(args)}
<Card {...args}>Card content goes here</Card>
{/snippet}
</Story>
<Story name="White" args={{ background: 'white' }}>
{#snippet template(args)}
<Card {...args}>Card content goes here</Card>
{/snippet}
</Story>
<Story name="No Padding" args={{ noPadding: true }}>
{#snippet template(args)}
<Card {...args}>
<div class="p-4 brutal-border-bottom">Header</div>
<div class="p-4">Body</div>
</Card>
{/snippet}
</Story>
```
**Step 9: Commit**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add src/shared/ui/Card/ src/shared/ui/index.ts
git commit -m "feat(shared/ui): add Card component with background variants and Storybook story"
```
---
## Task 7: Implement `Section` and `Container` components
**Files:**
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Section/types.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Section/Section.svelte`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Section/Container.svelte`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Section/index.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Section/Section.stories.svelte`
**Reference prototype:** `/home/ilia/Downloads/High-End Portfolio Design System(1)/src/app/components/Section.tsx`
**Step 1: Create types.ts**
```typescript
export type SectionBackground = 'ochre' | 'slate' | 'white';
export type ContainerSize = 'default' | 'wide' | 'ultra-wide';
```
**Step 2: Write failing test**
Create `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Section/Section.test.ts`:
```typescript
import { render } from '@testing-library/svelte';
import { describe, expect, it } from 'vitest';
import Section from './Section.svelte';
describe('Section', () => {
it('renders a <section> element', () => {
const { container } = render(Section);
expect(container.querySelector('section')).toBeTruthy();
});
it('applies ochre background by default', () => {
const { container } = render(Section);
expect(container.querySelector('section')?.className).toContain('bg-ochre-clay');
});
it('applies brutal border when bordered=true', () => {
const { container } = render(Section, { props: { bordered: true } });
const cls = container.querySelector('section')?.className ?? '';
expect(cls).toContain('brutal-border-top');
expect(cls).toContain('brutal-border-bottom');
});
});
```
**Step 3: Run test — expect FAIL**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Section/Section.test.ts
```
**Step 4: Implement Section.svelte**
```svelte
<!--
Component: Section
Full-width page section. Controls background and optional top/bottom brutal borders.
-->
<script lang="ts">
import { cn } from '$shared/lib/cn';
import type { Snippet } from 'svelte';
import type { SectionBackground } from './types';
interface Props {
background?: SectionBackground;
/** Add brutal border on top and bottom @default false */
bordered?: boolean;
children?: Snippet;
class?: string;
}
let { background = 'ochre', bordered = false, children, class: className }: Props = $props();
const backgroundStyles: Record<SectionBackground, string> = {
ochre: 'bg-ochre-clay text-carbon-black',
slate: 'bg-slate-indigo text-ochre-clay',
white: 'bg-white text-carbon-black',
};
const classes = $derived(cn(
backgroundStyles[background],
bordered && 'brutal-border-top brutal-border-bottom',
className,
));
</script>
<section class={classes}>
{#if children}
{@render children()}
{/if}
</section>
```
**Step 5: Implement Container.svelte**
```svelte
<!--
Component: Container
Centered max-width wrapper. Three size presets for responsive layouts up to 4K.
-->
<script lang="ts">
import { cn } from '$shared/lib/cn';
import type { Snippet } from 'svelte';
import type { ContainerSize } from './types';
interface Props {
/** Max-width preset @default 'default' */
size?: ContainerSize;
children?: Snippet;
class?: string;
}
let { size = 'default', children, class: className }: Props = $props();
const sizeStyles: Record<ContainerSize, string> = {
'default': 'max-w-7xl',
'wide': 'max-w-[1920px]',
'ultra-wide': 'max-w-[2560px]',
};
const classes = $derived(cn('mx-auto px-6 md:px-12 lg:px-16', sizeStyles[size], className));
</script>
<div class={classes}>
{#if children}
{@render children()}
{/if}
</div>
```
**Step 6: Create index.ts**
```typescript
export { default as Section } from './Section.svelte';
export { default as Container } from './Container.svelte';
export type { SectionBackground, ContainerSize } from './types';
```
**Step 7: Run test — expect PASS**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Section/Section.test.ts
```
**Step 8: Update shared/ui/index.ts** — append:
```typescript
export * from './Section';
```
**Step 9: Write Section.stories.svelte**
```svelte
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import Container from './Container.svelte';
import Section from './Section.svelte';
const { Story } = defineMeta({
title: 'Shared/Section',
component: Section,
tags: ['autodocs'],
parameters: { layout: 'fullscreen' },
});
</script>
<Story name="Ochre (default)">
{#snippet template()}
<Section>
<Container>
<p class="py-16">Section content</p>
</Container>
</Section>
{/snippet}
</Story>
<Story name="Slate">
{#snippet template()}
<Section background="slate">
<Container>
<p class="py-16">Section content on slate</p>
</Container>
</Section>
{/snippet}
</Story>
<Story name="Bordered">
{#snippet template()}
<Section bordered>
<Container>
<p class="py-16">Section with brutal top and bottom borders</p>
</Container>
</Section>
{/snippet}
</Story>
```
**Step 10: Commit**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add src/shared/ui/Section/ src/shared/ui/index.ts
git commit -m "feat(shared/ui): add Section and Container components with layout presets"
```
---
## Task 8: Implement `Input` and `Textarea` components
**Files:**
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Input/types.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Input/Input.svelte`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Input/Textarea.svelte`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Input/index.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Input/Input.stories.svelte`
**Reference prototype:** `/home/ilia/Downloads/High-End Portfolio Design System(1)/src/app/components/Input.tsx`
**Step 1: Create types.ts**
```typescript
// No custom types needed beyond HTML attributes; file reserved for future extension.
export type InputSize = 'sm' | 'md' | 'lg';
```
**Step 2: Write failing test**
Create `/home/ilia/Documents/Projects/allmywork/src/shared/ui/Input/Input.test.ts`:
```typescript
import { render } from '@testing-library/svelte';
import { describe, expect, it } from 'vitest';
import Input from './Input.svelte';
describe('Input', () => {
it('renders an <input> element', () => {
const { container } = render(Input);
expect(container.querySelector('input')).toBeTruthy();
});
it('renders label when label prop is set', () => {
const { getByText } = render(Input, { props: { label: 'Email' } });
expect(getByText('Email')).toBeTruthy();
});
it('renders error message when error prop is set', () => {
const { getByText } = render(Input, { props: { error: 'Required' } });
expect(getByText('Required')).toBeTruthy();
});
});
```
**Step 3: Run test — expect FAIL**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Input/Input.test.ts
```
**Step 4: Implement Input.svelte**
```svelte
<!--
Component: Input
Brutalist text input. 3px border, white background, burnt-oxide focus ring.
Optional label (uppercase) and inline error message.
-->
<script lang="ts">
import { cn } from '$shared/lib/cn';
import type { HTMLInputAttributes } from 'svelte/elements';
interface Props extends HTMLInputAttributes {
/** Uppercase label rendered above the input */
label?: string;
/** Inline error message rendered below the input */
error?: string;
class?: string;
}
let { label, error, class: className, ...rest }: Props = $props();
const inputClasses = $derived(cn(
'brutal-border bg-white px-4 py-3 text-carbon-black w-full',
'focus:outline-none focus:ring-2 focus:ring-burnt-oxide focus:ring-offset-2 focus:ring-offset-ochre-clay',
'transition-all duration-150',
'disabled:opacity-50 disabled:cursor-not-allowed',
className,
));
</script>
<div class="flex flex-col gap-2">
{#if label}
<label class="text-carbon-black">{label}</label>
{/if}
<input class={inputClasses} {...rest} />
{#if error}
<span class="text-sm text-burnt-oxide">{error}</span>
{/if}
</div>
```
**Step 5: Implement Textarea.svelte**
```svelte
<!--
Component: Textarea
Brutalist multi-line input. Same styling as Input but <textarea>.
Non-resizable by default (matches prototype).
-->
<script lang="ts">
import { cn } from '$shared/lib/cn';
import type { HTMLTextareaAttributes } from 'svelte/elements';
interface Props extends HTMLTextareaAttributes {
label?: string;
error?: string;
rows?: number;
class?: string;
}
let { label, error, rows = 4, class: className, ...rest }: Props = $props();
const textareaClasses = $derived(cn(
'brutal-border bg-white px-4 py-3 text-carbon-black w-full resize-none',
'focus:outline-none focus:ring-2 focus:ring-burnt-oxide focus:ring-offset-2 focus:ring-offset-ochre-clay',
'transition-all duration-150',
'disabled:opacity-50 disabled:cursor-not-allowed',
className,
));
</script>
<div class="flex flex-col gap-2">
{#if label}
<label class="text-carbon-black">{label}</label>
{/if}
<textarea {rows} class={textareaClasses} {...rest}></textarea>
{#if error}
<span class="text-sm text-burnt-oxide">{error}</span>
{/if}
</div>
```
**Step 6: Create index.ts**
```typescript
export { default as Input } from './Input.svelte';
export { default as Textarea } from './Textarea.svelte';
export type { InputSize } from './types';
```
**Step 7: Run test — expect PASS**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Input/Input.test.ts
```
**Step 8: Update shared/ui/index.ts** — append:
```typescript
export * from './Input';
```
**Step 9: Write Input.stories.svelte**
```svelte
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import Input from './Input.svelte';
import Textarea from './Textarea.svelte';
const { Story } = defineMeta({
title: 'Shared/Input',
component: Input,
tags: ['autodocs'],
parameters: {
layout: 'padded',
docs: { description: { component: 'Brutalist text input with optional label and error state.' } },
},
});
</script>
<Story name="Default">
{#snippet template()}
<Input placeholder="Enter text..." />
{/snippet}
</Story>
<Story name="With Label">
{#snippet template()}
<Input label="Email address" type="email" placeholder="you@example.com" />
{/snippet}
</Story>
<Story name="With Error">
{#snippet template()}
<Input label="Email address" type="email" value="bad-email" error="Please enter a valid email address." />
{/snippet}
</Story>
<Story name="Disabled">
{#snippet template()}
<Input label="Read-only field" disabled value="Cannot edit this" />
{/snippet}
</Story>
<Story name="Textarea">
{#snippet template()}
<Textarea label="Message" placeholder="Write your message..." rows={5} />
{/snippet}
</Story>
<Story name="Textarea with Error">
{#snippet template()}
<Textarea label="Message" error="Message is required." />
{/snippet}
</Story>
```
**Step 10: Commit**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add src/shared/ui/Input/ src/shared/ui/index.ts
git commit -m "feat(shared/ui): add Input and Textarea components with label and error states"
```
---
## Task 9: Implement `TechStackBrick` component
**Files:**
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/TechStackBrick/TechStackBrick.svelte`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/TechStackBrick/TechStackGrid.svelte`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/TechStackBrick/index.ts`
- Create: `/home/ilia/Documents/Projects/allmywork/src/shared/ui/TechStackBrick/TechStackBrick.stories.svelte`
**Reference prototype:** `/home/ilia/Downloads/High-End Portfolio Design System(1)/src/app/components/TechStackBrick.tsx`
**Step 1: Write failing test**
Create `/home/ilia/Documents/Projects/allmywork/src/shared/ui/TechStackBrick/TechStackBrick.test.ts`:
```typescript
import { render } from '@testing-library/svelte';
import { describe, expect, it } from 'vitest';
import TechStackBrick from './TechStackBrick.svelte';
describe('TechStackBrick', () => {
it('renders the skill name', () => {
const { getByText } = render(TechStackBrick, { props: { name: 'SvelteKit' } });
expect(getByText('SvelteKit')).toBeTruthy();
});
it('uppercases the skill name via CSS class', () => {
const { container } = render(TechStackBrick, { props: { name: 'React' } });
expect(container.querySelector('span')?.className).toContain('uppercase');
});
});
```
**Step 2: Run test — expect FAIL**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/TechStackBrick/TechStackBrick.test.ts
```
**Step 3: Implement TechStackBrick.svelte**
```svelte
<!--
Component: TechStackBrick
Single skill pill. Brutal border, white bg, hard shadow that collapses on hover (lifted press feel).
-->
<script lang="ts">
import { cn } from '$shared/lib/cn';
interface Props {
name: string;
class?: string;
}
let { name, class: className }: Props = $props();
const classes = $derived(cn(
'brutal-border brutal-shadow bg-white px-4 py-3 text-center',
'transition-all duration-200',
'hover:shadow-none hover:translate-x-[2px] hover:translate-y-[2px]',
className,
));
</script>
<div class={classes}>
<span class="text-sm uppercase tracking-wide">{name}</span>
</div>
```
**Step 4: Implement TechStackGrid.svelte**
```svelte
<!--
Component: TechStackGrid
Responsive grid of TechStackBrick items. Adjusts columns from 2 to 6 across breakpoints.
-->
<script lang="ts">
import { cn } from '$shared/lib/cn';
import TechStackBrick from './TechStackBrick.svelte';
interface Props {
skills: string[];
class?: string;
}
let { skills, class: className }: Props = $props();
const classes = $derived(cn(
'grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4',
className,
));
</script>
<div class={classes}>
{#each skills as skill (skill)}
<TechStackBrick name={skill} />
{/each}
</div>
```
**Step 5: Create index.ts**
```typescript
export { default as TechStackBrick } from './TechStackBrick.svelte';
export { default as TechStackGrid } from './TechStackGrid.svelte';
```
**Step 6: Run test — expect PASS**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/TechStackBrick/TechStackBrick.test.ts
```
**Step 7: Update shared/ui/index.ts** — append:
```typescript
export * from './TechStackBrick';
```
**Step 8: Write TechStackBrick.stories.svelte**
```svelte
<script module>
import { defineMeta } from '@storybook/addon-svelte-csf';
import TechStackBrick from './TechStackBrick.svelte';
import TechStackGrid from './TechStackGrid.svelte';
const { Story } = defineMeta({
title: 'Shared/TechStackBrick',
component: TechStackBrick,
tags: ['autodocs'],
parameters: {
layout: 'padded',
docs: { description: { component: 'Skill badge for the capabilities section. Hover removes shadow for a tactile press.' } },
},
});
</script>
<Story name="Single Brick">
{#snippet template()}
<TechStackBrick name="SvelteKit" />
{/snippet}
</Story>
<Story name="Grid">
{#snippet template()}
<TechStackGrid skills={['SvelteKit', 'TypeScript', 'Tailwind', 'Bun', 'Figma', 'Storybook', 'Vitest', 'Playwright']} />
{/snippet}
</Story>
```
**Step 9: Commit**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add src/shared/ui/TechStackBrick/ src/shared/ui/index.ts
git commit -m "feat(shared/ui): add TechStackBrick and TechStackGrid components"
```
---
## Task 10: Run all tests and validate Storybook
This is a verification gate — no new code is written, only validation.
**Step 1: Run full unit test suite**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run test
```
Expected: All tests pass. Note any failures and fix them in the relevant component task before continuing.
**Step 2: Type-check**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run check
```
Expected: Zero errors. If `svelte-check` reports errors in `.stories.svelte` files related to `defineMeta`, confirm `@storybook/addon-svelte-csf` types are installed:
```bash
bun add -D @storybook/addon-svelte-csf
```
**Step 3: Lint all files**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run lint
```
Expected: Zero errors. Fix any Biome lint warnings (unused imports, etc.) before continuing.
**Step 4: Start Storybook and verify all stories render**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run storybook
```
Visit http://localhost:6006. Confirm all stories appear in the sidebar:
- Shared/Badge — 5 stories
- Shared/Button — 6 stories
- Shared/Card — 4 stories
- Shared/Section — 3 stories
- Shared/Input — 6 stories
- Shared/TechStackBrick — 2 stories
**Step 5: Commit any fixes**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add -A
git commit -m "fix(shared/ui): resolve type errors and lint warnings from verification pass"
```
---
## Task 11: Update HomePage to use the design system
Replace the placeholder `HomePage.svelte` with a layout that demonstrates the design system is wired up and functional. This is not the final portfolio content — it's a foundation smoke test.
**Files:**
- Modify: `/home/ilia/Documents/Projects/allmywork/src/pages/home/ui/HomePage.svelte`
**Step 1: Write HomePage.svelte**
```svelte
<!--
Page: HomePage
Smoke test page that renders key shared/ui components to verify the design system.
To be replaced with real portfolio content in a future plan.
-->
<script lang="ts">
import { Badge } from '$shared/ui/Badge';
import { Button } from '$shared/ui/Button';
import { Card } from '$shared/ui/Card';
import { Container, Section } from '$shared/ui/Section';
import { TechStackGrid } from '$shared/ui/TechStackBrick';
</script>
<Section>
<Container>
<div class="py-20 space-y-12">
<!-- Heading -->
<div>
<h1>allmy.work</h1>
<div class="brutal-border-top pt-4 mt-4">
<p class="text-sm uppercase tracking-wider opacity-60">Portfolio Foundation — Design System Active</p>
</div>
</div>
<!-- Cards row -->
<div class="grid md:grid-cols-3 gap-6">
<Card background="slate" noPadding class="p-6">
<p class="text-sm opacity-60 mb-2">Stack</p>
<p>SvelteKit + Bun</p>
</Card>
<Card background="slate" noPadding class="p-6">
<p class="text-sm opacity-60 mb-2">Styling</p>
<p>Tailwind CSS v4</p>
</Card>
<Card background="slate" noPadding class="p-6">
<p class="text-sm opacity-60 mb-2">Components</p>
<p>Shared UI ready</p>
</Card>
</div>
<!-- Badges -->
<div class="flex gap-3 flex-wrap">
<Badge variant="default">SvelteKit</Badge>
<Badge variant="primary">Brutalist</Badge>
<Badge variant="secondary">FSD</Badge>
<Badge variant="outline">Open Source</Badge>
</div>
<!-- Buttons -->
<div class="flex gap-4 flex-wrap">
<Button variant="primary">View Work</Button>
<Button variant="secondary">About</Button>
<Button variant="outline">Contact</Button>
<Button variant="ghost">Learn More</Button>
</div>
<!-- Tech stack -->
<Card background="white">
<h4 class="mb-6">Technologies</h4>
<TechStackGrid skills={['SvelteKit', 'TypeScript', 'Tailwind', 'Bun', 'Storybook', 'Vitest']} />
</Card>
</div>
</Container>
</Section>
```
**Step 2: Start dev server and verify**
```bash
cd /home/ilia/Documents/Projects/allmywork
bun run dev
```
Visit http://localhost:5173. Confirm:
- Ochre-clay background renders
- Fraunces font loads for the h1
- Badge, Button, Card, TechStackGrid components render correctly
- No console errors
**Step 3: Commit**
```bash
cd /home/ilia/Documents/Projects/allmywork
git add src/pages/home/ui/HomePage.svelte
git commit -m "feat(pages/home): wire up design system components in HomePage smoke test"
```
---
## Completion Checklist
Before declaring this plan complete, verify every item:
- [ ] `CLAUDE.md` includes code style section
- [ ] Storybook starts without errors
- [ ] `bun run test` — all tests pass
- [ ] `bun run check` — zero TypeScript errors
- [ ] `bun run lint` — zero Biome errors
- [ ] `bun run dev` — dev server compiles and page renders with ochre background and Fraunces font
- [ ] Storybook shows stories for: Badge, Button, Card, Section, Input, TechStackBrick
- [ ] All components use Svelte 5 runes (`$props`, `$derived`) — no `export let`
- [ ] All components import via FSD aliases (`$shared/...`)
- [ ] All commits follow `prefix(scope): description` convention
---
## Reference Paths
| Resource | Path |
|---|---|
| Figma prototype root | `/home/ilia/Downloads/High-End Portfolio Design System(1)/` |
| Prototype theme.css | `/home/ilia/Downloads/High-End Portfolio Design System(1)/src/styles/theme.css` |
| Prototype components | `/home/ilia/Downloads/High-End Portfolio Design System(1)/src/app/components/` |
| glyphdiff Button (reference) | `/home/ilia/Documents/Projects/glyphdiff/src/shared/ui/Button/Button.svelte` |
| glyphdiff stories (reference) | `/home/ilia/Documents/Projects/glyphdiff/src/shared/ui/Button/Button.stories.svelte` |
| Project root | `/home/ilia/Documents/Projects/allmywork/` |
| Shared UI | `/home/ilia/Documents/Projects/allmywork/src/shared/ui/` |
| App CSS | `/home/ilia/Documents/Projects/allmywork/src/app/styles/app.css` |
| Svelte config | `/home/ilia/Documents/Projects/allmywork/svelte.config.js` |
| FSD aliases | `$shared`, `$pages`, `$features`, `$entities`, `$widgets` |