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

57 KiB
Raw Permalink Blame History

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):

---

## 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

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:

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

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:

"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"

If missing, add them.

Step 5: Run Storybook to confirm it starts

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

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:

@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

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

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

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:

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

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

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

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:

export { cn } from './cn';

Step 7: Commit

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

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:

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

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

<!--
    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

export { default as Badge } from './Badge.svelte';
export type { BadgeVariant } from './types';

Step 6: Run test to confirm it passes

cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Badge/Badge.test.ts

Expected: PASS

Step 7: Write Badge.stories.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

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

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:

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

cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Button/Button.test.ts

Expected: FAIL

Step 4: Implement Button.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

export { default as Button } from './Button.svelte';
export type { ButtonVariant, ButtonSize } from './types';

Step 6: Run test to confirm pass

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:

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

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:

cd /home/ilia/Documents/Projects/allmywork
bun run dev &
# visit http://localhost:5173 — page should load
kill %1

Step 10: Write Button.stories.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

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

export type CardBackground = 'ochre' | 'slate' | 'white';

Step 2: Write failing test

Create /home/ilia/Documents/Projects/allmywork/src/shared/ui/Card/Card.test.ts:

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

cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Card/Card.test.ts

Expected: FAIL

Step 4: Implement Card.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

export { default as Card } from './Card.svelte';
export type { CardBackground } from './types';

Step 6: Run test to confirm pass

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:

export * from './Card';

Step 8: Write Card.stories.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

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

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:

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

cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Section/Section.test.ts

Step 4: Implement Section.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

<!--
    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

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

cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Section/Section.test.ts

Step 8: Update shared/ui/index.ts — append:

export * from './Section';

Step 9: Write Section.stories.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

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

// 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:

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

cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Input/Input.test.ts

Step 4: Implement Input.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

<!--
    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

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

cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/Input/Input.test.ts

Step 8: Update shared/ui/index.ts — append:

export * from './Input';

Step 9: Write Input.stories.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

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:

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

cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/TechStackBrick/TechStackBrick.test.ts

Step 3: Implement TechStackBrick.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

<!--
    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

export { default as TechStackBrick } from './TechStackBrick.svelte';
export { default as TechStackGrid } from './TechStackGrid.svelte';

Step 6: Run test — expect PASS

cd /home/ilia/Documents/Projects/allmywork
bun run test src/shared/ui/TechStackBrick/TechStackBrick.test.ts

Step 7: Update shared/ui/index.ts — append:

export * from './TechStackBrick';

Step 8: Write TechStackBrick.stories.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

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

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

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:

bun add -D @storybook/addon-svelte-csf

Step 3: Lint all files

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

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

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

<!--
    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

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

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