# Fase 2a — Vetrina pubblica · Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Vetrina pubblica multi-pagina (Home, Eventi, Regolamento, Albo d'Oro, Community), **prerendered**, con contenuti reali da **moduli statici committati**, sul tema pergamena+civiltà. Nessuna dipendenza da DB/migrazione.

**Architecture:** Contenuti curati come moduli TypeScript in `src/lib/content/` (+ regolamento da markdown reso con `marked`). Route pubbliche con `export const prerender = true`. Componenti presentazionali riusabili in `src/lib/components/`. I dati per ora sono statici; quando arriveranno engine/migrazione, le pagine dinamiche leggeranno dal DB riusando gli stessi componenti.

**Tech Stack:** SvelteKit (Svelte 5 runes) · Tailwind v4 (token in `src/app.css`) · `marked` (markdown→HTML) · Vitest.

**Riferimenti:** spec [`../specs/2026-06-08-fase-2-vetrina-anagrafiche-design.md`](../specs/2026-06-08-fase-2-vetrina-anagrafiche-design.md) · sorgenti dati in `data/sorgenti/` · mockup Home in `.superpowers/brainstorm/*/home-mockup.html`.

> Nota UI: la **rifinitura visiva** delle pagine usa la skill `frontend-design` (Task 9). I task 4–8 costruiscono struttura + contenuti on-theme; la polish finale arriva dopo che tutto monta.

---

## Task 1 — Moduli contenuto: eventi + community (TDD sulla forma dati)

**Files:**
- Create: `src/lib/content/events.ts`, `src/lib/content/community.ts`
- Test: `src/lib/content/content.test.ts`

- [ ] **Step 1: Test che fallisce** — `src/lib/content/content.test.ts`

```ts
import { describe, it, expect } from 'vitest';
import { eventFormats } from './events';
import { communityLinks } from './community';

describe('content/events', () => {
  it('ha 6 format, ognuno con slug/nome/periodo/formato e slug unici', () => {
    expect(eventFormats.length).toBe(6);
    for (const e of eventFormats) {
      expect(e.slug).toBeTruthy();
      expect(e.nome).toBeTruthy();
      expect(e.periodo).toBeTruthy();
      expect(e.formato).toBeTruthy();
    }
    const slugs = eventFormats.map((e) => e.slug);
    expect(new Set(slugs).size).toBe(slugs.length);
  });
});

describe('content/community', () => {
  it('ha link non vuoti con label e url http', () => {
    expect(communityLinks.length).toBeGreaterThan(0);
    for (const l of communityLinks) {
      expect(l.label).toBeTruthy();
      expect(l.url).toMatch(/^https?:\/\//);
    }
  });
});
```

- [ ] **Step 2: Esegui e verifica il fallimento** — Run: `pnpm test` → FAIL (`Cannot find module './events'`).

- [ ] **Step 3: Implementa `src/lib/content/events.ts`** (descrizioni dai `data/sorgenti/`)

```ts
export type EventFormat = {
  slug: string;
  nome: string;
  periodo: string;
  formato: string;
};

export const eventFormats: EventFormat[] = [
  { slug: 'bolgia-estiva', nome: 'Bolgia Estiva', periodo: 'maggio–luglio', formato: 'Campionato a gironi con ranking, promozioni e retrocessioni.' },
  { slug: 'bolgia-autunnale', nome: 'Bolgia Autunnale', periodo: 'settembre–novembre', formato: 'Campionato a gironi con ranking; eredita le serie dall’estiva.' },
  { slug: 'torneo-generale', nome: 'Torneo Generale', periodo: 'gennaio–maggio', formato: 'La manifestazione di inizio anno: partite a 3-4 giocatori, fasi a eliminazione.' },
  { slug: 'duello', nome: 'Duello', periodo: 'luglio/agosto', formato: 'Playoff 1v1 a eliminazione diretta.' },
  { slug: 'palio', nome: 'Palio', periodo: 'dicembre', formato: 'Torneo a squadre di fine anno, partite a 3 e 4 giocatori.' },
  { slug: 'supercoppa', nome: 'Supercoppa', periodo: 'Natale', formato: 'Sfida commentata sul forum tra i migliori dell’anno.' }
];
```

- [ ] **Step 4: Implementa `src/lib/content/community.ts`**

```ts
export type CommunityLink = { label: string; url: string; descr?: string };

export const communityLinks: CommunityLink[] = [
  { label: 'Tana dei Goblin', url: 'https://www.goblins.net', descr: 'La community ludica italiana.' },
  { label: 'Board Trello (storico)', url: 'https://trello.com/b/JnCOl3xW', descr: 'La bacheca attuale dei tornei.' }
];
```

> **DATA INPUT (chiedere a Paolo):** gli URL esatti del **canale Telegram notizie** e della **chat Telegram**. Appena disponibili, aggiungerli a `communityLinks`. Finché non ci sono, NON inserire link Telegram inventati.

- [ ] **Step 5: Esegui i test** — Run: `pnpm test` → PASS.

- [ ] **Step 6: Commit**

```bash
git add src/lib/content/events.ts src/lib/content/community.ts src/lib/content/content.test.ts
git commit -m "feat(fase2a): moduli contenuto eventi + community (TDD)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```

---

## Task 2 — Albo d'Oro: dati + test

**Files:**
- Create: `src/lib/content/albo.ts`
- Modify: `src/lib/content/content.test.ts`

- [ ] **Step 1: Aggiungi il test che fallisce** (in `content.test.ts`)

```ts
import { alboDoro } from './albo';

describe('content/albo', () => {
  it('contiene le 5 competizioni con voci non vuote', () => {
    const nomi = alboDoro.map((c) => c.competizione);
    for (const atteso of ['Torneo Generale', 'Bolgia', 'Duello', 'Supercoppa', 'Palio']) {
      expect(nomi).toContain(atteso);
    }
    for (const c of alboDoro) {
      expect(c.voci.length).toBeGreaterThan(0);
      for (const v of c.voci) expect(v.vincitore).toBeTruthy();
    }
  });
});
```

- [ ] **Step 2: Esegui** — Run: `pnpm test` → FAIL (`Cannot find module './albo'`).

- [ ] **Step 3: Implementa `src/lib/content/albo.ts`** — definisci i tipi e **trascrivi i vincitori dalla fonte committata** `data/sorgenti/trello-tta-goblin.md` (sezione "Lista 3 — ALBO D'ORO"). Struttura:

```ts
export type AlboEntry = { edizione: string; vincitore: string; note?: string };
export type AlboCompetizione = { competizione: string; voci: AlboEntry[] };

export const alboDoro: AlboCompetizione[] = [
  {
    competizione: 'Bolgia',
    voci: [
      { edizione: 'AUT25', vincitore: 'Sellux' },
      { edizione: 'EST25', vincitore: 'RudyRunner666' }
      // … trascrivere TUTTE le edizioni EST20→AUT25 da data/sorgenti/trello-tta-goblin.md
    ]
  },
  {
    competizione: 'Torneo Generale',
    voci: [
      { edizione: '2024', vincitore: 'Romendil', note: '99 partecipanti' }
      // … 2011→2024 dalla fonte
    ]
  }
  // … Duello (DUE20→DUE25), Supercoppa (CUP20→CUP25), Palio — dalla fonte
];
```

Le voci devono riflettere fedelmente `data/sorgenti/trello-tta-goblin.md`. Ordinare ogni competizione dalla più recente alla più vecchia.

- [ ] **Step 4: Esegui** — Run: `pnpm test` → PASS.

- [ ] **Step 5: Commit**

```bash
git add src/lib/content/albo.ts src/lib/content/content.test.ts
git commit -m "feat(fase2a): dati albo d'oro + test

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```

---

## Task 3 — Regolamento: markdown → HTML

**Files:**
- Create: `src/lib/content/regolamento.md` (copia di `data/sorgenti/regolamento-v1.5.md`), `src/lib/content/regolamento.ts`

- [ ] **Step 1: Installa marked** — Run: `pnpm add marked`

- [ ] **Step 2: Copia il regolamento dentro src** (così è importabile da SvelteKit)

```bash
cp "data/sorgenti/regolamento-v1.5.md" "src/lib/content/regolamento.md"
```

- [ ] **Step 3: Implementa `src/lib/content/regolamento.ts`**

```ts
import { marked } from 'marked';
import raw from './regolamento.md?raw';

// Contenuto fidato (nostro), nessun input utente: ok renderlo come HTML.
export const regolamentoHtml: string = marked.parse(raw, { async: false }) as string;
```

- [ ] **Step 4: Verifica build** — Run: `pnpm build` → success (conferma che l'import `?raw` + marked funziona nel bundle).

- [ ] **Step 5: Commit**

```bash
git add src/lib/content/regolamento.md src/lib/content/regolamento.ts package.json pnpm-lock.yaml
git commit -m "feat(fase2a): regolamento da markdown (marked)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```

---

## Task 4 — Componenti presentazionali riusabili

**Files:**
- Create: `src/lib/components/Section.svelte`, `src/lib/components/Card.svelte`, `src/lib/components/DataTable.svelte`

(Componenti visivi: verifica con `pnpm check` + `pnpm build`; la resa si valida a schermo nei task pagina.)

- [ ] **Step 1: `Section.svelte`** — titolo serif + slot

```svelte
<script lang="ts">
  import type { Snippet } from 'svelte';
  let { title, children }: { title: string; children: Snippet } = $props();
</script>
<section class="mb-10">
  <h2 class="text-2xl mb-4" style="border-bottom:1px solid var(--color-sepia);padding-bottom:6px">{title}</h2>
  {@render children()}
</section>
```

- [ ] **Step 2: `Card.svelte`**

```svelte
<script lang="ts">
  import type { Snippet } from 'svelte';
  let { title, children }: { title: string; children: Snippet } = $props();
</script>
<div class="rounded-xl p-4" style="background:var(--color-carta);border:1px solid var(--color-sepia)">
  <h3 class="text-lg mb-1" style="color:var(--color-verderame)">{title}</h3>
  <div class="text-sm" style="color:var(--color-sepia)">{@render children()}</div>
</div>
```

- [ ] **Step 3: `DataTable.svelte`** — tabella generica (riusabile poi per le classifiche)

```svelte
<script lang="ts">
  let { columns, rows }: { columns: string[]; rows: (string)[][] } = $props();
</script>
<table class="w-full text-sm border-collapse">
  <thead>
    <tr>
      {#each columns as c}
        <th class="text-left p-2" style="border-bottom:2px solid var(--color-bronzo);color:var(--color-inchiostro)">{c}</th>
      {/each}
    </tr>
  </thead>
  <tbody>
    {#each rows as r}
      <tr>
        {#each r as cell}
          <td class="p-2" style="border-bottom:1px solid var(--color-sepia)">{cell}</td>
        {/each}
      </tr>
    {/each}
  </tbody>
</table>
```

- [ ] **Step 4: Verifica** — Run: `pnpm check` → 0 errori; `pnpm build` → success.

- [ ] **Step 5: Commit**

```bash
git add src/lib/components/Section.svelte src/lib/components/Card.svelte src/lib/components/DataTable.svelte
git commit -m "feat(fase2a): componenti riusabili Section/Card/DataTable

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```

---

## Task 5 — Home page (prerendered)

**Files:**
- Create: `src/routes/+page.ts` (prerender flag)
- Modify: `src/routes/+page.svelte`

- [ ] **Step 1: `src/routes/+page.ts`**

```ts
export const prerender = true;
```

- [ ] **Step 2: `src/routes/+page.svelte`** — hero + "I nostri tornei" (da `eventFormats`) + "Albo d'Oro — ultimi vincitori" (prime voci da `alboDoro`) + strip community. Struttura come da mockup approvato.

```svelte
<script lang="ts">
  import Section from '$lib/components/Section.svelte';
  import Card from '$lib/components/Card.svelte';
  import { eventFormats } from '$lib/content/events';
  import { alboDoro } from '$lib/content/albo';
  const ultimi = alboDoro.map((c) => ({ competizione: c.competizione, v: c.voci[0] })).filter((x) => x.v);
</script>

<div class="text-center py-10">
  <h1 class="text-4xl">Tornei di Through the Ages</h1>
  <p class="text-lg" style="color:var(--color-bronzo)">la community Tana dei Goblin</p>
  <p class="max-w-xl mx-auto mt-3 mb-5" style="color:var(--color-sepia)">Campionati, duelli e tornei online sull’app ufficiale CGE. Classifiche, albo d’oro e calendario in un posto solo.</p>
  <a href="/eventi" class="px-4 py-2 rounded text-white inline-block" style="background:var(--color-bronzo)">Esplora gli eventi</a>
  <a href="/regolamento" class="px-4 py-2 rounded inline-block ml-2" style="border:1px solid var(--color-bronzo);color:var(--color-bronzo)">Regolamento</a>
</div>

<Section title="I nostri tornei">
  <div class="grid gap-3" style="grid-template-columns:repeat(auto-fill,minmax(220px,1fr))">
    {#each eventFormats as e}
      <Card title={e.nome}>{e.formato}</Card>
    {/each}
  </div>
</Section>

<Section title="Albo d'Oro — ultimi vincitori">
  <div class="flex flex-wrap gap-3">
    {#each ultimi as u}
      <div class="rounded-lg px-3 py-2" style="background:var(--color-carta);border:1px solid var(--color-sepia)">
        <span style="color:var(--color-oro)">★</span> {u.competizione} {u.v.edizione} — <strong>{u.v.vincitore}</strong>
      </div>
    {/each}
  </div>
  <p class="mt-2 text-sm"><a href="/albo" style="color:var(--color-verderame)">→ Albo d'Oro completo</a></p>
</Section>
```

- [ ] **Step 3: Verifica** — Run: `pnpm build` → success (la home prerenderizza). Avvia `timeout 8 pnpm dev` e/o apri http://localhost:5173 per controllo visivo.

- [ ] **Step 4: Commit**

```bash
git add src/routes/+page.ts src/routes/+page.svelte
git commit -m "feat(fase2a): home page (prerendered)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>"
```

---

## Task 6 — Pagina Eventi (prerendered)

**Files:**
- Create: `src/routes/eventi/+page.ts`, `src/routes/eventi/+page.svelte`

- [ ] **Step 1: `+page.ts`** → `export const prerender = true;`

- [ ] **Step 2: `+page.svelte`**

```svelte
<script lang="ts">
  import Card from '$lib/components/Card.svelte';
  import { eventFormats } from '$lib/content/events';
</script>
<h1 class="text-3xl mb-6">Eventi</h1>
<div class="grid gap-4" style="grid-template-columns:repeat(auto-fill,minmax(260px,1fr))">
  {#each eventFormats as e}
    <Card title={e.nome}>
      <div class="mb-1" style="color:var(--color-bronzo)">{e.periodo}</div>
      {e.formato}
    </Card>
  {/each}
</div>
```

- [ ] **Step 3: Verifica** — `pnpm build` → success.
- [ ] **Step 4: Commit** — `git add src/routes/eventi && git commit -m "feat(fase2a): pagina eventi" ...` (con trailer Co-Authored-By).

---

## Task 7 — Pagina Albo d'Oro (prerendered, DataTable)

**Files:**
- Create: `src/routes/albo/+page.ts`, `src/routes/albo/+page.svelte`

- [ ] **Step 1: `+page.ts`** → `export const prerender = true;`

- [ ] **Step 2: `+page.svelte`** — una sezione per competizione, ogni voce in DataTable

```svelte
<script lang="ts">
  import Section from '$lib/components/Section.svelte';
  import DataTable from '$lib/components/DataTable.svelte';
  import { alboDoro } from '$lib/content/albo';
</script>
<h1 class="text-3xl mb-6">Albo d'Oro</h1>
{#each alboDoro as c}
  <Section title={c.competizione}>
    <DataTable columns={['Edizione', 'Vincitore', 'Note']}
      rows={c.voci.map((v) => [v.edizione, v.vincitore, v.note ?? ''])} />
  </Section>
{/each}
```

- [ ] **Step 3: Verifica** — `pnpm build` → success; controllo visivo della tabella.
- [ ] **Step 4: Commit** (con trailer).

---

## Task 8 — Pagine Regolamento + Community (prerendered)

**Files:**
- Create: `src/routes/regolamento/+page.ts`, `src/routes/regolamento/+page.svelte`, `src/routes/community/+page.ts`, `src/routes/community/+page.svelte`

- [ ] **Step 1:** entrambi i `+page.ts` → `export const prerender = true;`

- [ ] **Step 2: `regolamento/+page.svelte`** (contenuto fidato → `{@html}`)

```svelte
<script lang="ts">
  import { regolamentoHtml } from '$lib/content/regolamento';
</script>
<article class="prose-regolamento">
  {@html regolamentoHtml}
</article>
```

(Stile minimo per il blocco: aggiungere in `src/app.css` regole base per `.prose-regolamento h1/h2/h3/p/ul` con i token tema — titoli serif, spaziatura leggibile.)

- [ ] **Step 3: `community/+page.svelte`**

```svelte
<script lang="ts">
  import { communityLinks } from '$lib/content/community';
</script>
<h1 class="text-3xl mb-6">Community</h1>
<ul class="space-y-3">
  {#each communityLinks as l}
    <li>
      <a href={l.url} target="_blank" rel="noopener" style="color:var(--color-verderame)">{l.label}</a>
      {#if l.descr}<span style="color:var(--color-sepia)"> — {l.descr}</span>{/if}
    </li>
  {/each}
</ul>
```

- [ ] **Step 4: Verifica** — `pnpm build` → success; controllo visivo regolamento + community.
- [ ] **Step 5: Commit** (con trailer).

---

## Task 9 — Nav coerente + prerender check + polish visiva

**Files:**
- Modify: `src/lib/components/Header.svelte`, `src/app.css` (stili `.prose-regolamento`)

- [ ] **Step 1: Allinea la nav** in `Header.svelte` alle pagine reali: `Eventi (/eventi)`, `Albo d'Oro (/albo)`, `Regolamento (/regolamento)`, `Community (/community)`, `Accedi (/login)`. Rimuovi/contrassegna "Classifiche" come *prossimamente* (oppure togli il link finché non c'è l'engine).

- [ ] **Step 2: Verifica completa** — `pnpm check` (0 errori), `pnpm test` (verde), `pnpm build` (success). Avvia `pnpm dev` e controlla che tutte le route pubbliche (`/`, `/eventi`, `/albo`, `/regolamento`, `/community`) carichino e che la nav funzioni.

- [ ] **Step 3: Polish visiva** — attiva la skill **`frontend-design`** e rifinisci le 5 pagine + i componenti (spaziatura, gerarchia, responsive, micro-dettagli) restando dentro il sistema pergamena+civiltà. Mantieni i contenuti e la struttura approvati; non introdurre dipendenze pesanti. Ri-verifica `pnpm check`/`build`.

- [ ] **Step 4: Commit** (con trailer).

---

## Definition of Done (Fase 2a)
- [ ] `/`, `/eventi`, `/albo`, `/regolamento`, `/community` caricano e sono **prerendered**.
- [ ] Contenuti reali dai moduli (`events`, `albo`, `community`) e dal regolamento markdown.
- [ ] Nav coerente; tema pergamena+civiltà applicato; responsive.
- [ ] `pnpm check` 0/0 · `pnpm test` verde · `pnpm build` success.
- [ ] Nessuna dipendenza da DB/migrazione introdotta.

## Self-review (note)
- Copertura spec 2a: moduli contenuto (T1-T3), componenti (T4), 5 route prerendered (T5-T8), nav+polish (T9). ✓
- Out of scope (corretto): classifiche/dashboard/profilo/claim/migrazione (→ piano 2b e fasi successive).
- DATA INPUT aperto: URL Telegram per `community.ts` (da Paolo) — non blocca il resto.
