Architecture
Fortune Kit follows a layered architecture separating concerns cleanly.
Package Structure
fortune-kit/
├── packages/
│ ├── tokens/ # Design tokens (colors, spacing, motion)
│ ├── headless/ # Game logic (wheel, quiz, slots engines)
│ └── components/ # Vue UI components
├── apps/
│ ├── docs/ # VitePress documentation
│ └── storybook/ # Component playground & API contractDependency Graph
@fortune-kit/components
│
├── @fortune-kit/tokens
│
└── @fortune-kit/headless
│
└── vue (peer)Design Decisions
Why Monorepo?
- Atomic changes — Update tokens + components in one PR
- Consistent tooling — Shared TypeScript, ESLint, Prettier configs
- Simplified versioning — All packages stay in sync
Why Headless Engines?
Separating game logic from UI provides:
- Testability — Unit test wheel spin probability without mocking DOM
- Flexibility — Use
useWheel()with Canvas, SVG, or WebGL - Reusability — Same quiz logic for mobile and desktop with different UI
Why CSS Variables?
Tokens export to CSS custom properties because:
- Runtime theming without JS
- DevTools inspection
- Works with any styling solution (Tailwind, CSS Modules, vanilla)
- SSR-friendly
css
.my-component {
/* Use tokens anywhere */
color: var(--fk-color-win-DEFAULT);
transition: all var(--fk-duration-normal) var(--fk-ease-easeOut);
}File Conventions
Component Structure
packages/components/src/
├── results/
│ ├── FkResultWin.vue
│ ├── FkResultLose.vue
│ └── FkResultInfo.vue
├── leaderboard/
│ └── FkLeaderboard.vue
└── index.ts # Public exportsNaming
- Components:
Fkprefix, PascalCase (FkResultWin) - Composables:
useprefix, camelCase (useWheel) - Tokens: Flat keys with semantic names (
colors.win.DEFAULT)
Performance Considerations
- No global CSS — Components use scoped styles
- Lazy composables —
useWheel()doesn't allocate until called - RAF animations — Smooth 60fps updates for wheels, counters
- Virtual scrolling ready — Leaderboard designed for 1000+ items