# UFC Fight-Week Activation Spec · Hyder Amil · June 20, 2026

Status: Ready to fire when Hyder responds with hero/Pick'em/slab picks.

This spec defines three product surfaces that activate for the week of June 14-20, 2026 to celebrate Hyder Amil's fight. All three are gated by activation windows so they appear and disappear cleanly without manual intervention.

---

## Context

**Fighter**: Hyder Amil (UFC featherweight, fight date June 20, 2026)

**Catalog state** (verified live May 11 night):
- 20 Hyder Amil cards in production catalog
- Set: `Ufc Cards 2026 Topps Chrome UFC Autograph`
- Card number format: `BAV-HA` (likely his RC autograph parallels)
- Rarities present: Refractor, Geometric Refractor (+ others in the 20 results)
- Live API contract: `GET /api/cards/search?game=ufc&q=hyder` returns `{ data: [...] }`
- Some cards have images, some don't — verify hero card has an image before activation

**Pending from Hyder** (gates the build):
1. Hero card pick (which of his 20 cards is the featured home card)
2. Pick'em card pick (which card the price prediction tracks)
3. Slab focus (which 1-2 cards Ben should focus the limited slabs on)

When Hyder responds, this spec becomes immediately fireable to CC.

---

## Activation windows

| Surface | Window | Behavior outside window |
|---------|--------|-------------------------|
| Home featured card | June 14 00:00 PT → June 21 11:59 PT | Hidden, no DOM impact |
| UFC Pick'em | June 14 00:00 PT → 30 days post-fight (July 20) | Hidden during pre-window, locked during fight window, results revealed post-window |
| Countdown banner | June 17 00:00 PT → June 20 21:00 PT (fight start) | Hidden |

Activation check uses a single shared utility:

```ts
// client/src/lib/fight-week.ts
export const FIGHT_WEEK = {
  fighter: 'Hyder Amil',
  fightDateUTC: '2026-06-21T03:00:00Z', // June 20 9pm ET / 6pm PT
  homeFeaturedStart: '2026-06-14T07:00:00Z',
  homeFeaturedEnd: '2026-06-21T19:00:00Z',
  pickemStart: '2026-06-14T07:00:00Z',
  pickemRevealEnd: '2026-07-21T07:00:00Z',
  countdownStart: '2026-06-17T07:00:00Z',
  countdownEnd: '2026-06-21T04:00:00Z',
} as const;

export function isFightWeekHomeActive(now = Date.now()): boolean { /* ... */ }
export function isPickemActive(now = Date.now()): boolean { /* ... */ }
export function isCountdownActive(now = Date.now()): boolean { /* ... */ }
```

Rollback: edit any of the date strings to a far-future date, deploy. Surface immediately hides.

---

## Deliverable 1 · Home Featured Card Widget

### File path
`client/src/components/home/hyder-featured-card.tsx`

### Placement
Below the hero search pill, above the game switcher chips (PR #54). Only renders when `isFightWeekHomeActive() === true`.

### Data fetch

```ts
const { data: card } = useQuery({
  queryKey: ['hyder-featured-card'],
  queryFn: async () => {
    const res = await fetch(`/api/cards/search?game=ufc&q=${encodeURIComponent('<HYDER_HERO_CARD_QUERY>')}`);
    const json = await res.json();
    return (json.data || []).find(c => c.id === '<HYDER_HERO_CARD_ID>') || null;
  },
  staleTime: 5 * 60 * 1000,
  enabled: isFightWeekHomeActive(),
});
```

`<HYDER_HERO_CARD_QUERY>` and `<HYDER_HERO_CARD_ID>` get filled in once Hyder picks the hero. Hardcoded in this file — not in a database — to keep the activation deterministic and reviewable in version control.

### UI

A horizontal card row with:
- **Left (40% width)**: card image (use the `images.large` field, fallback to placeholder if empty)
- **Right (60% width)**:
  - Eyebrow: `FIGHT WEEK · JUNE 20`
  - Headline: card name (e.g., `Hyder Amil [Refractor] #BAV-HA`)
  - Sub: set name + rarity
  - CTA pill: `Track this card → ` (links to `/card/:cardId`)
  - Below CTA: small text `Hyder fights at UFC Fight Night · 6 days` (auto-updates)

Style: matches UFC theming (PR #14) — UFC red accent on eyebrow, otherwise navy/charcoal card chrome.

### Acceptance criteria
- Renders only inside activation window
- Image fallback works if card image is empty
- CTA navigates to existing card detail route
- "X days" auto-decrements daily
- Hidden completely (no empty container) outside window
- No layout shift when toggling between active/inactive

---

## Deliverable 2 · UFC Pick'em

### File paths
- API: `server/routes/pickem-hyder.ts` (or extend existing pickem service)
- Schema: add to `shared/schema.ts` as new table `pickem_predictions_hyder` (or reuse generic `pickem_predictions` if already generalized)
- UI: `client/src/pages/pickem-hyder.tsx`
- Home entry point: link in the Featured Card widget ("Make your prediction →")

### Schema

```ts
export const pickemPredictions = pgTable('pickem_predictions', {
  id: serial('id').primaryKey(),
  userId: text('user_id').notNull(),
  pickemKey: text('pickem_key').notNull(), // 'hyder-amil-june-20-2026'
  cardId: text('card_id').notNull(),
  predictionDirection: text('prediction_direction').notNull(), // 'up' | 'down'
  basePriceUsd: numeric('base_price_usd').notNull(),
  predictionLockedAt: timestamp('prediction_locked_at').defaultNow(),
  resolvedAt: timestamp('resolved_at'),
  finalPriceUsd: numeric('final_price_usd'),
  outcome: text('outcome'), // 'correct' | 'incorrect' | 'tied' | null
});
```

Migration: `migrations/0040_pickem_predictions.sql`. Single user can have one prediction per pickemKey (unique constraint on `user_id, pickem_key`).

### Mechanics

1. **Window opens June 14**: user can pick "up" or "down" on the Hyder Pick'em card's 30-day-post-fight price.
2. **Lock at fight start (June 20 9pm ET)**: no more predictions accepted.
3. **Card price snapshot at lock**: write `basePriceUsd` to all predictions.
4. **Resolution at +30 days (July 20)**: cron job pulls latest `cardmarket.prices.averageSellPrice` for the Pick'em card, writes `finalPriceUsd` and `outcome` to each prediction.
5. **Reveal screen**: user sees their pick, the base price, the final price, the outcome, and a ranking (top 10% / top 25% / etc.) of all users who picked correctly.

### API

```
POST /api/pickem/hyder/predict
  body: { direction: 'up' | 'down' }
  response: { id, basePriceUsd, lockedAt }
  401 if not signed in
  409 if already predicted

GET /api/pickem/hyder/my-prediction
  response: { prediction | null, basePriceUsd, lockedAt, resolvedAt, outcome, finalPriceUsd }

GET /api/pickem/hyder/stats  (post-resolution only)
  response: { totalPredictions, correctPct, upPct, downPct, currentPrice }
```

### UI states

| State | Renders |
|-------|---------|
| Outside activation window | Nothing |
| Pre-fight, no prediction | Card image + "Will this card be worth more or less 30 days after the fight?" + two big buttons (Up / Down) + sub-copy explaining the rules |
| Pre-fight, already predicted | Card image + "You picked: UP. Locks at fight start (June 20)." + small "Change pick" link (re-enabled until lock) |
| Fight in progress / between lock and resolution | "Predictions locked. Final price reveals July 20." + countdown to July 20 |
| Post-resolution, won | Big "You called it." + base/final prices + ranking |
| Post-resolution, lost | Restrained "Not this time." + base/final prices + outcome distribution chart |
| Post-resolution, never predicted | "Pick'em closed. Final result: ___" |

Pick'em celebration animation explicitly deferred to v1.0.1 — for fight week, the result screen is restrained text only.

### Acceptance criteria
- Single prediction per user, locked at fight start
- Cron job resolves on July 20 without manual intervention
- Idempotent resolution (running cron twice doesn't double-update)
- Anonymous users see "Sign in to predict" CTA, not 401 error
- Post-fight reveal works even if user signs in after July 20
- Pick'em is visible from the home featured card widget when both are active

---

## Deliverable 3 · Countdown Banner

### File path
`client/src/components/home/fight-countdown-banner.tsx`

### Placement
Sticky just below the main app header, above the home hero. Only renders during countdown window (June 17 → June 20).

### UI

Single horizontal strip:
- Left: UFC red accent dot + "FIGHT WEEK"
- Center: "Hyder Amil fights in **3 days, 4 hours**" (auto-updating)
- Right: "View featured card →" (scrolls to or navigates to featured card)
- Dismissable: small `×` on right edge, stores dismissal in localStorage. Re-appears each day.

Height: 44px on desktop, 56px on mobile (taller for thumb tap-target).

### Acceptance criteria
- Live countdown updates every minute (not every second — no perf cost)
- Dismissal persists for 24h, then re-appears
- Banner doesn't push hero content down by jumping — uses CSS sticky or top-of-page reserve
- Disappears completely at fight start (no DOM at all)
- Mobile: tap-target hits cleanly without bumping into nav

---

## Build order (when fired)

1. **Shared utility first**: `client/src/lib/fight-week.ts` with all date constants and `is*Active()` helpers. Test in isolation.
2. **Countdown banner**: smallest surface, lowest risk. Ship to confirm the activation pattern works.
3. **Home featured card**: depends on countdown but adds richer data path (card fetch). Image fallback verification critical here.
4. **Pick'em schema + API**: backend work — migration, endpoints, cron job.
5. **Pick'em UI**: connects to APIs. Most complex of the three. Lock state transitions are the highest-risk area.

Total estimated CC effort: 6-10 hours across the four phases. Build phases 1-3 first as one PR (visual + activation). Pick'em as a separate PR because of schema and cron complexity.

---

## Open questions for Hyder

When his list arrives, confirm:

1. **Hero card**: which card ID / name from the 20 in catalog?
2. **Pick'em card**: same or different from hero? Different is more interesting (deeper-cut card with more price-move potential).
3. **Slab focus**: which 1-2 for Ben's limited run? Recommend 1 high-end (autograph parallel) + 1 accessible (base refractor).
4. **Banner copy preference**: "Hyder Amil fights in X days" vs "Fight week" vs custom? His call.
5. **Post-fight reveal copy**: any tagline he wants for the Pick'em result screen?

---

## What stays the same regardless of his picks

- Activation window dates (June 14-20 / June 17-20 / +30d resolution)
- All file paths
- All API contracts
- All schema
- All UI structure
- All copy except the card names

This means **the spec is buildable now**. CC can scaffold every file, every schema, every API — and just leave the card ID / card name as placeholders for Hyder's picks.

If you want to overlap Hyder's response window with CC build, fire a Phase 1 prompt now ("build everything except plug in Hyder's three card IDs") and the moment Hyder responds, fire a 5-minute Phase 2 prompt ("fill in these three card IDs in these three files") and ship.

---

## Rollback / kill switch

If anything goes wrong during fight week:

1. **Hide all three surfaces immediately**: edit `client/src/lib/fight-week.ts`, change all date strings to a far-future date (e.g., `'2030-01-01T00:00:00Z'`). Deploy. All three surfaces disappear within 30 seconds.

2. **Pick'em data integrity**: predictions stay in DB. If we hide the surfaces but resolution cron still runs, predictions resolve correctly when re-enabled. Safe to disable UI while keeping backend.

3. **Cron job pause**: comment out cron registration in `server/cron.ts`. Resolution will not fire. Re-enable post-fix.

---

## Activation checklist (run morning of June 14)

- [ ] Hyder's three card IDs filled into spec placeholders
- [ ] Each referenced card verified in `/api/cards/search?game=ufc` (returns image, not empty)
- [ ] Migration 0040 applied to production
- [ ] Cron job registered and verified scheduled for July 20 00:00 PT
- [ ] Activation window dates confirmed
- [ ] Featured card image renders on staging
- [ ] Pick'em UP/DOWN buttons work signed-in
- [ ] Pick'em returns 401 (not crash) for anonymous users
- [ ] Countdown banner renders and updates
- [ ] Mobile testing on iPhone (SE, 14, 15 Pro Max) — all three surfaces

---

*Spec generated 2026-05-11 night · Ready to fire CC the moment Hyder responds*
