# Architecture

Croupier is a framework-agnostic swipe deck component. This document explains how it works internally.

## Overview

Croupier has two layers within a single source file (`src/croupier.js`):

```
┌─────────────────────────────────────────────┐
│  USER CODE                                    │
│  items, renderCard, actions, hooks, theme     │
└──────────────────┬──────────────────────────┘
                   │
┌──────────────────▼──────────────────────────┐
│  CROUPIER CORE                               │
│  ─ Stack Manager   DOM preservation · depth   │
│  ─ Gesture Engine  Pointer Events · WAAPI   │
│  ─ Keyboard        keymap · focus management │
│  ─ Render Pipeline 2 modes · async guard    │
│  ─ State Machine   5 states                  │
│  ─ Event Bus       7 events                  │
│  ─ Undo Manager    history · reciclagem     │
└──────────────────┬──────────────────────────┘
                   │
┌──────────────────▼──────────────────────────┐
│  UI SHELL (built-in)                         │
│  Header · Stats bar · Toast · Empty · Hints  │
└─────────────────────────────────────────────┘
```

## Data flow

```
items (user array)
  │
  ▼
state.items (cloned on reset)
  │
  ├── state.index → points to current card
  ├── undoManager → stack of actions (for undo)
  └── state.stats → counters per action
        │
        ▼
    render() → DOM (4 visible cards)
        │
        ├── dispatch(action) → doAction() → animateAndAdvance() → index++ → render()
        ├── undo() → index-- → render()
        └── reset() → index=0 → render()
```

State is the single source of truth. Every mutation goes through `doAction()` (records history + stats) or `undo()` (reverts). The DOM is always derived from state.

## Lifecycle of a swipe

```
1. User presses → (or drags right)
2. dispatch('accept') or onPointerUp → detects action
3. doAction('accept')
   ├── undoManager.push({ action, item, atIndex })
   ├── state.stats.accept++
   └── showCenterToast('READ', 'accept')
4. animateAndAdvance(card, 'accept')
   ├── card.style.transform = 'translateX(1200px) rotate(45deg)'
   └── setTimeout(450ms) → state.index++ → render()
5. render()
   ├── Preserves surviving cards (updates data-cr-depth)
   ├── Builds new card if needed
   └── CSS transition slides cards to new depths
```

## Why single source file?

The decision to keep everything in one file is practical:

1. **Zero dependencies** — works without npm, server, or CDN
2. **Easy embed** — copy one file, use as script tag or import
3. **32 KB** — smaller than most frameworks alone
4. **Simple maintenance** — everything is in one place

If the project grows beyond what a single file can handle, the natural split is:

- `croupier.js` — core logic
- `croupier.css` — styles
- `plugins/` — optional extensions

But for the current scope, a single source file is the right choice.

## Why no framework?

A framework would add more weight than the entire component. Croupier:

- Doesn't need declarative reactivity — state is simple enough for manual mutation
- Doesn't need routing — it's a single screen
- Doesn't need complex state management — state has ~10 fields
- Doesn't need virtual DOM — 4 cards max, rebuild is trivial

Adding React, Vue, or Svelte would multiply complexity without benefit.

## See also

- [Design decisions](design-decisions.md)
- [Animation system](animation-system.md)
- [API Reference](../reference/api.md)
