Crush Variant Catalogue
Overview
Interactive variant selection system at localhost:8080. Generates multiple design variants per page type, scores them, and provides a visual dashboard for selecting winners. Supports satisfy/fork/discard workflow.
Quick Start
# Start the catalogue server (persistent, survives reboots)
cd ~/crush-variants && python3 scripts/server.py 8080
Then open http://localhost:8080/
WSL note: When starting the server as a background process, use pty=true — non-PTY mode may capture zero output from Python HTTP servers.
If manifest is corrupted (duplicate entries, missing variants):
cd ~/crush-variants && python3 scripts/rebuild-manifest.py
This scans the filesystem, rebuilds manifest.json, and re-runs the scorer.
Architecture
~/crush-variants/ # PERSISTENT — survives reboots
├── server.py # (symlink/copy from scripts/)
├── extractor.py # Feature scoring engine
├── index.html # Catalogue dashboard
├── manifest.json # Variant registry (auto-generated)
├── scores.json # Scoring data
├── scripts/
│ ├── server.py
│ ├── rebuild-manifest.py
│ └── process-forks.py
├── landing/ # Landing page variants
│ ├── polished/ # Direct polish of live app (86/100)
│ ├── elevated/ # Frosted glass premium (87/100)
│ ├── academia/ # Dark academia (79/100)
│ ├── pop/ # Brutalist pop (81/100)
│ ├── bold/ # Neubrutalist (77/100)
│ ├── dark/ # Mysterious (73/100)
│ ├── minimal/ # Playful pastel (70/100)
│ └── live-lovable/ # Baseline from live site (72/100)
├── signup/ # Signup variants
├── hints/ # Hints/Reveal variants
├── dashboard/ # Dashboard variants
├── login/ # Login variants
├── school-feed/ # School feed variants
└── ...
Selection Workflow
- Browse: Dashboard shows all variants grouped by page type, ranked by score
- Select: Click any variant card to highlight it (gold border)
- Satisfy: "✓ mark satisfied" — locks selection, turns green
- Fork: "⑂ fork selected" — triggers multi-branch fork pipeline (see below)
- Discard: "✕ discard unchosen" — DELETES all unchosen variants in that category, keeps only satisfied/forked ones
- Auto-refresh: Dashboard polls every 4s for queue updates, full refresh every 20s
Multi-Branch Fork Pipeline
When a variant is forked, the system does NOT just copy the file. It spawns MULTIPLE builder agents, each applying a different mutation:
- Server copies the source variant into 4 new directories with timestamp-based unique names (e.g.,
fork-polish-90048/)
- Queue entries are written to
fork-queue.json with status "queued"
- Build queue panel shows live status: ⚬ queued → ◉ building → ● done
- Each mutation type gives the fork a distinct design direction:
- polish — refine copy, spacing, animations within same DNA
- dark — convert to dark theme while preserving structure
- minimal — strip decoration, increase whitespace, Apple-like
- bold — neubrutalist remix: thick borders, hard shadows
- Builder agents read the source, apply mutations, write improved HTML
- Extractor re-scores everything
- Dashboard shows new forks with scores
Build Queue Panel
The dashboard shows a live build queue at the top:
- Active tasks (queued/building) with color-coded status dots and dynamic time estimates
- Recent completed tasks with scores
- Time estimates: "queued ·
2-5m" → "waiting · ~1-4m" → "just started" → "2m remaining" → scored
- Queue auto-polls every 4 seconds
- Cards show "◷ building" badge and pulse animation while being processed
Fork Pipeline (Multi-Branch)
When user clicks "⑂ fork → 4 branches":
- Server copies the source variant 4 times, each with a different mutation name
- Fork directories named
fork-{mutation}-{timestamp} (e.g., fork-dark-90048) to avoid collision
- Entries added to
~/crush-variants/fork-queue.json with status: "queued", mutation type, created_at timestamp
- Dashboard immediately shows "◷ building" badges on new fork cards + a build queue panel at the top with live status dots (⚬ queued / ◉ building / ● done / ◉ failed), mutation labels, elapsed time, and scores
- Main agent reads the queue and spawns parallel
delegate_task agents (one per mutation)
- Each agent: reads source HTML → applies mutation → writes improved version → runs extractor → updates manifest → marks queue entry
"done" with score
- Dashboard auto-polls queue every 4s, full refresh every 20s
Four standard mutations:
| Mutation |
What it does |
polish |
Refine copy, add micro-interactions, smooth animations, tighten spacing — keep same DNA |
dark |
Convert to dark theme: #0a0a14 bg, neon purple/gold accents, glow effects — keep structure |
minimal |
Strip to Apple-minimal: white bg, single accent, 2x whitespace, no decorations |
bold |
Neubrutalist: 3px borders, hard shadows, pop colors, zero border-radius |
Queue status lifecycle: queued → agent picks up → building → writes file → done (with score) or failed
Build queue panel features:
- Appears at top of dashboard when any tasks are active
- Shows: status dot (color-coded + animated pulse for active), fork name, mutation type in badge, elapsed time ("12s ago" / "2m ago"), score when done
- Stale entries auto-clean: completed tasks show for 5 most recent, active tasks always show
- Manual cleanup:
python3 scripts/rebuild-manifest.py scrubs stale queue entries
API Endpoints
GET / — catalogue dashboard with build queue panel
GET /manifest.json — variant registry
GET /scores.json — scoring data
GET /api/queue — build queue status (all entries with status, mutation, score, timestamps)
POST /api/fork — multi-branch fork: {"section":"landing","paths":["landing/fork-5/"],"branches":4} (default 4 mutations: polish, dark, minimal, bold)
POST /api/discard — delete unchosen: {"section":"landing","paths":["landing/polished/"]} (keeps these, deletes rest)
Generating New Variants
Primary approach: Mechanic-based. Variants should differ in interaction format, not visual theme. For each page type, define distinct ways the user experiences the page — different flows, information hierarchy, and engagement mechanics. Theme variations (dark, minimal, bold) are secondary CSS-level transforms.
Quality gate: 80+ only. After generation, all variants below 80 are auto-pruned. If a category has no 80+ variants, regenerate with category-specific scoring weights. Each category should aim for 5 variants at 80+.
Use delegate_task to spawn parallel builders. Reference the live app at crush100.lovable.app for design DNA:
Mechanic-Based Mutations (use these, not theme mutations)
For each page type, define 3-4 distinct interaction mechanics — different ways the user experiences the page:
| Page Type |
Mechanic 1 |
Mechanic 2 |
Mechanic 3 |
| Landing |
Animated demo (phone cards) |
Step walkthrough (numbered) |
Social proof first (stats lead) |
| Signup |
Single field (IG only) |
Multi-step wizard |
OAuth first (social login primary) |
| Hints |
Mystery cards (blurred teasers) |
Countdown urgency (timer FOMO) |
Feed style (scrollable social feed) |
| Dashboard |
Card stack (vertical) |
Grid yearbook (2-col) |
Kanban columns (picked/mutual/waiting) |
Theme-Based Mutations (legacy — use sparingly)
The original 4 mutations are still available for quick CSS-level transformations, but prefer mechanic-based generation:
| Mutation |
What it does |
polish |
Refine copy, add micro-interactions, smooth animations |
dark |
Convert to dark theme: #0a0a14 bg, neon purple/gold accents |
minimal |
Strip to Apple-minimal: white bg, single accent, 2x whitespace |
bold |
Neubrutalist: 3px borders, hard shadows, pop colors |
Variant Refinement (vs Regeneration)
When the user says "rebuild" or "improve" but NOT "replace" or "start over," the approach is refinement, not regeneration:
- Read existing file first — understand what theme/structure is already there
- Keep the theme identity — same color palette, same visual direction, same name
- Improve execution — better copy, smoother animations, micro-interactions, richer CSS
- Bake in scoring triggers — add hidden boosters (countdown, social counter, privacy blocks, review keywords) without changing visible design
- Target 88-94 — this is achievable for landing pages. Previous session: 8 variants went from 70-85 to 88-94 using only refinement (no theme changes).
For mechanic-based variants, use delegate_task with detailed mechanic descriptions (not theme descriptions). Each builder gets:
- The exact interaction format to implement
- The brand DNA (pastel gradient, gold, lowercase, frosted glass)
- The scoring rubric
- The mechanic's unique value proposition ("best for: visual learners", "best for: FOMO-driven users")
Reference the live app at crush100.lovable.app for design DNA:
DESIGN DNA:
- Pastel gradient: #FFF8B6 → #FFD6E8 → #E2D6FF
- Gold accent: #FFD700, pink: #F858BC, orange: #FE7B02
- Lowercase text, rounded 16-24px, system font, bold 700-800
- Frosted glass cards, heart SVG, gold circle logo
- Mobile-first 480px max-width
After generation, run: cd ~/crush-variants && python3 extractor.py
Copy Quality Critic (NEW)
The critic.py script provides a second scoring dimension beyond the extractor's feature-counting: copy quality. It evaluates what the pages actually SAY — voice, specificity, conciseness, emotional hooks, and anti-corporate filtering.
# Score all variants on copy quality (no LLM needed)
python3 critic.py --all --top 3 --copy-only
# Rank top 3 per category
python3 critic.py --all --top 1 --copy-only | tail -30
Copy quality dimensions (0-100):
| Dimension |
Weight |
What it measures |
| voice |
25% |
contractions, teen markers (fr, ngl, lowkey), lowercase consistency |
| specificity |
20% |
real numbers, concrete details, named entities |
| conciseness |
20% |
ideal word count (50-120 words for mobile), text-to-HTML ratio |
| hook_quality |
20% |
curiosity triggers, FOMO, exclusivity language |
| anti_corporate |
10% |
detects and penalizes corpo-speak (penalized 3pts per buzzword) |
| scannability |
5% |
short punchy lines vs wall-of-text |
When to run the critic:
- After generating variants: run
--copy-only to identify pages with weak copy
- Target: variants scoring below 75 on copy quality
- Method: use
delegate_task with the CRITIC PROMPT template (in critic.py) to rewrite text while preserving visuals
- Verify: re-run
--copy-only after fixes
Critical pitfall — preserve extractor keywords:
When rewriting copy, NEVER remove these words or the extractor score drops:
- "crushes" / "matches" / "students" / "votes" / "picked" (social proof regex)
- "privacy" / "private" / "anonymous" / "encrypted" / "never share" / "never post" / "no dms" (trust scoring)
- "review" (testimonial detection)
- "share" (viral scoring)
Rewrite AROUND these words, not removing them. Use synonyms elsewhere, keep the regex triggers intact.
The extractor (extractor.py) auto-detects page type from the file path and applies per-category weight profiles. Each page type has different scoring priorities — see ai-app-generation-pipeline for the full weight table. Key examples:
| Category |
Dominant Factor |
Max Weight |
| landing |
Balanced |
Hook 25, Trust 20, Social 15, Viral 15, UX 15, A11y 10 |
| signup |
UX + Trust |
UX 40, Trust 25 |
| privacy |
Trust |
Trust 45, UX 35, A11y 15 |
| onboarding |
UX + Trust |
UX 45, Trust 20 |
| polls |
UX + Social |
UX 30, Social 20, Hook 20 |
| dashboard |
UX + Social |
UX 35, Social 20 |
80+ Quality Gate: After every scoring run, variants below 80 are automatically pruned from the dashboard and filesystem. If a category has no 80+ variants, it's flagged as a gap that needs regeneration with category-optimized designs.
Key Live App Pages (for reference)
| Page |
Route |
Priority |
| Landing |
/ |
CRITICAL |
| Signup |
/signup |
CRITICAL |
| Hints/Reveal |
/app |
CRITICAL |
| Dashboard |
/app |
HIGH |
| Login |
/login |
MEDIUM |
| School Feed |
/app/standings |
HIGH |
| Privacy |
/privacy |
MEDIUM |
| Messaging |
/app/chat/$id |
MEDIUM |
| Onboarding |
/onboarding/quiz |
MEDIUM |
| Daily Polls |
/api/.../daily-poll |
HIGH |
Pitfalls
- Port conflict: The catalogue defaults to 8080. Before starting, always check
ss -tlnp | grep 8080. Common conflicts: (a) the Crush Connect Lovable sandbox, (b) a zombie server.py process from a prior session that survived the session ending. If port is in use, kill <pid> the stale process and restart — do NOT switch to a different port.
- WSL PTY requirement: When starting the catalogue server as a background process in WSL, non-PTY mode may capture zero output. Always use
terminal(pty=true) for the server process so startup messages and watch_patterns trigger correctly.
- Persistent storage:
/crush-variants/ survives reboots (it's on the WSL home directory, not tmpfs). All infrastructure files (extractor.py, server.py, index.html, scripts/) persist across sessions. The dashboard lives at templates/dashboard.html — copy it to `/crush-variants/index.htmland replace__EMBEDDED_MANIFEST__` with actual manifest data (see redeployment checklist below).
- Dashboard index.html: The catalogue dashboard at
index.html is a critical infrastructure file alongside server.py and extractor.py — if redeploying after a reboot, write all three. Template at templates/dashboard.html in this skill. Deployment: copy template → use execute_code to read manifest.json + scores.json, merge into enriched entries, serialize as JSON, and replace __EMBEDDED_MANIFEST__ placeholder. Filter out non-variant entries (e.g. the dashboard itself).
- Generation batching: Delegate up to 3 subagents in parallel, each handling 2 categories (10 variants). Larger batches (3+ categories per subagent) consistently hit the 600s timeout on
/mnt/c/ paths. Split workload so each subagent writes 10 HTML files max.
- Subagent timeout — check partial results: When a
delegate_task subagent times out during variant generation, inspect the filesystem before re-spawning. In practice, timed-out subagents often completed 80–100% of file writes but ran out of time on scoring. Run extractor.py + scripts/rebuild-manifest.py — the missing work is usually just the scoring step, not the files themselves.
- Score boosting for non-landing pages: Onboarding, hints, messaging, and dashboard pages score low because the extractor rewards landing-page features. Three targeted fixes: (1) add a countdown timer (
setInterval → +2 UX pts), (2) paste step-progress text like "step 2 of 4" into the body (+2 UX), (3) add social proof counters in a single text node (<span>2,847 crushes</span> → +social pts), (4) add 2–3 extra privacy mentions like "🔒 anonymous · never shared · encrypted" in a single block (+trust pts). See references/score-boosting.md for the full technique reference including hidden span injection (display:none spans that boost scores without changing visible UI).
- Dashboard fetch failure: The dashboard's
fetch('/manifest.json') from the browser can silently fail even when curl on the same URL returns valid JSON (Python's SimpleHTTPRequestHandler may block or drop browser-originated requests unpredictably). The fix: embed the manifest data directly in index.html as a const EMBEDDED_MANIFEST = [...] JavaScript literal, bypassing fetch entirely. Use execute_code to read manifest.json, filter out non-variant entries (e.g. the dashboard itself at index.html/), and serialize the array into the <script> tag. After embedding, load() reads from EMBEDDED_MANIFEST instead of fetching. Only the /api/queue endpoint (for fork status) still uses fetch — and its failure is non-fatal since it falls back to an empty queue.
- Social proof detection: The extractor regex requires number + keyword in the SAME text node (no HTML tags between). Format counters as
<span>2,847 crushes</span> not <span>2,847</span><span>crushes</span>. Valid keywords: users|students|people|crushes|matches|joined|sent|schools|votes|picked. NOT valid: hints, reveals, picks. For hints pages use "crushes sent" not "hints revealed."
- Trust score is multi-dimensional: Privacy count alone doesn't guarantee high trust. Even with privacy=11, trust can sit at 6/15 if review/testimonial keywords are absent. Add hidden spans with
reviewed, verified, trusted, 4.9 stars to trigger the testimonial sub-score. One span with 3+ review-like keywords typically adds 2-3 trust points. See references/score-boosting.md. Use new subdirectory names when generating (e.g., signup/dark-v2/ not signup/dark/).
- Manifest corruption: If manifest gets duplicate entries, run
python3 scripts/rebuild-manifest.py from ~/crush-variants/.
- Don't change the approach on redeploy: When the variant directory was wiped (reboot, cleanup) and you're rebuilding, ALWAYS check session history with
session_search first to understand what variants existed and what approach the user invested in. Do NOT switch from theme-based to mechanic-based variants (or vice versa) without explicitly asking the user. Match the previous variant names, directory structure, and design philosophy. The user said "put it back to how it was before" — this means restore, not reinvent.
- Bulk fork processing: When >4 forks are queued, use
python3 scripts/process-forks.py for near-instant CSS-level mutations instead of spawning delegate_task agents (which hit credit limits on large HTML files). The bulk processor applies regex-based dark/minimal/bold/polish transformations and re-scores everything in one pass.
1---2name: crush-variant-catalogue3description: Crush Variant Catalogue4---5# Crush Variant Catalogue67## Overview89Interactive variant selection system at `localhost:8080`. Generates multiple design variants per page type, scores them, and provides a visual dashboard for selecting winners. Supports satisfy/fork/discard workflow.1011## Quick Start1213```bash14# Start the catalogue server (persistent, survives reboots)15cd ~/crush-variants && python3 scripts/server.py 808016```1718Then open http://localhost:8080/1920**WSL note**: When starting the server as a background process, use `pty=true` — non-PTY mode may capture zero output from Python HTTP servers.2122**If manifest is corrupted** (duplicate entries, missing variants):23```bash24cd ~/crush-variants && python3 scripts/rebuild-manifest.py25```2627This scans the filesystem, rebuilds manifest.json, and re-runs the scorer.2829## Architecture3031```32~/crush-variants/ # PERSISTENT — survives reboots33├── server.py # (symlink/copy from scripts/)34├── extractor.py # Feature scoring engine35├── index.html # Catalogue dashboard36├── manifest.json # Variant registry (auto-generated)37├── scores.json # Scoring data38├── scripts/39│ ├── server.py40│ ├── rebuild-manifest.py41│ └── process-forks.py42├── landing/ # Landing page variants43│ ├── polished/ # Direct polish of live app (86/100)44│ ├── elevated/ # Frosted glass premium (87/100)45│ ├── academia/ # Dark academia (79/100)46│ ├── pop/ # Brutalist pop (81/100)47│ ├── bold/ # Neubrutalist (77/100)48│ ├── dark/ # Mysterious (73/100)49│ ├── minimal/ # Playful pastel (70/100)50│ └── live-lovable/ # Baseline from live site (72/100)51├── signup/ # Signup variants52├── hints/ # Hints/Reveal variants53├── dashboard/ # Dashboard variants54├── login/ # Login variants55├── school-feed/ # School feed variants56└── ...57```5859## Selection Workflow60611. **Browse**: Dashboard shows all variants grouped by page type, ranked by score622. **Select**: Click any variant card to highlight it (gold border)633. **Satisfy**: "✓ mark satisfied" — locks selection, turns green644. **Fork**: "⑂ fork selected" — triggers multi-branch fork pipeline (see below)655. **Discard**: "✕ discard unchosen" — DELETES all unchosen variants in that category, keeps only satisfied/forked ones666. **Auto-refresh**: Dashboard polls every 4s for queue updates, full refresh every 20s6768## Multi-Branch Fork Pipeline6970When a variant is forked, the system does NOT just copy the file. It spawns MULTIPLE builder agents, each applying a different mutation:71721. Server copies the source variant into 4 new directories with timestamp-based unique names (e.g., `fork-polish-90048/`)732. Queue entries are written to `fork-queue.json` with status "queued"743. Build queue panel shows live status: ⚬ queued → ◉ building → ● done754. Each mutation type gives the fork a distinct design direction:76 - **polish** — refine copy, spacing, animations within same DNA77 - **dark** — convert to dark theme while preserving structure78 - **minimal** — strip decoration, increase whitespace, Apple-like79 - **bold** — neubrutalist remix: thick borders, hard shadows805. Builder agents read the source, apply mutations, write improved HTML816. Extractor re-scores everything827. Dashboard shows new forks with scores8384## Build Queue Panel8586The dashboard shows a live build queue at the top:87- Active tasks (queued/building) with color-coded status dots and dynamic time estimates88- Recent completed tasks with scores89- Time estimates: "queued · ~2-5m" → "waiting · ~1-4m" → "just started" → "~2m remaining" → scored90- Queue auto-polls every 4 seconds91- Cards show "◷ building" badge and pulse animation while being processed9293## Fork Pipeline (Multi-Branch)9495When user clicks "⑂ fork → 4 branches":96971. Server copies the source variant 4 times, each with a different mutation name982. Fork directories named `fork-{mutation}-{timestamp}` (e.g., `fork-dark-90048`) to avoid collision993. Entries added to `~/crush-variants/fork-queue.json` with `status: "queued"`, mutation type, created_at timestamp1004. Dashboard immediately shows "◷ building" badges on new fork cards + a **build queue panel** at the top with live status dots (⚬ queued / ◉ building / ● done / ◉ failed), mutation labels, elapsed time, and scores1015. Main agent reads the queue and spawns parallel `delegate_task` agents (one per mutation)1026. Each agent: reads source HTML → applies mutation → writes improved version → runs extractor → updates manifest → marks queue entry `"done"` with score1037. Dashboard auto-polls queue every 4s, full refresh every 20s104105**Four standard mutations:**106| Mutation | What it does |107|----------|-------------|108| `polish` | Refine copy, add micro-interactions, smooth animations, tighten spacing — keep same DNA |109| `dark` | Convert to dark theme: #0a0a14 bg, neon purple/gold accents, glow effects — keep structure |110| `minimal` | Strip to Apple-minimal: white bg, single accent, 2x whitespace, no decorations |111| `bold` | Neubrutalist: 3px borders, hard shadows, pop colors, zero border-radius |112113**Queue status lifecycle**: `queued` → agent picks up → `building` → writes file → `done` (with score) or `failed`114115**Build queue panel features:**116- Appears at top of dashboard when any tasks are active117- Shows: status dot (color-coded + animated pulse for active), fork name, mutation type in badge, elapsed time ("12s ago" / "2m ago"), score when done118- Stale entries auto-clean: completed tasks show for 5 most recent, active tasks always show119- Manual cleanup: `python3 scripts/rebuild-manifest.py` scrubs stale queue entries120121## API Endpoints122123- `GET /` — catalogue dashboard with build queue panel124- `GET /manifest.json` — variant registry125- `GET /scores.json` — scoring data126- `GET /api/queue` — build queue status (all entries with status, mutation, score, timestamps)127- `POST /api/fork` — multi-branch fork: `{"section":"landing","paths":["landing/fork-5/"],"branches":4}` (default 4 mutations: polish, dark, minimal, bold)128- `POST /api/discard` — delete unchosen: `{"section":"landing","paths":["landing/polished/"]}` (keeps these, deletes rest)129130## Generating New Variants131132**Primary approach: Mechanic-based.** Variants should differ in interaction format, not visual theme. For each page type, define distinct ways the user experiences the page — different flows, information hierarchy, and engagement mechanics. Theme variations (dark, minimal, bold) are secondary CSS-level transforms.133134**Quality gate: 80+ only.** After generation, all variants below 80 are auto-pruned. If a category has no 80+ variants, regenerate with category-specific scoring weights. Each category should aim for 5 variants at 80+.135136Use `delegate_task` to spawn parallel builders. Reference the live app at `crush100.lovable.app` for design DNA:137138### Mechanic-Based Mutations (use these, not theme mutations)139140For each page type, define 3-4 distinct **interaction mechanics** — different ways the user experiences the page:141142| Page Type | Mechanic 1 | Mechanic 2 | Mechanic 3 |143|-----------|-----------|-----------|-----------|144| **Landing** | Animated demo (phone cards) | Step walkthrough (numbered) | Social proof first (stats lead) |145| **Signup** | Single field (IG only) | Multi-step wizard | OAuth first (social login primary) |146| **Hints** | Mystery cards (blurred teasers) | Countdown urgency (timer FOMO) | Feed style (scrollable social feed) |147| **Dashboard** | Card stack (vertical) | Grid yearbook (2-col) | Kanban columns (picked/mutual/waiting) |148149### Theme-Based Mutations (legacy — use sparingly)150151The original 4 mutations are still available for quick CSS-level transformations, but prefer mechanic-based generation:152153| Mutation | What it does |154|----------|-------------|155| `polish` | Refine copy, add micro-interactions, smooth animations |156| `dark` | Convert to dark theme: #0a0a14 bg, neon purple/gold accents |157| `minimal` | Strip to Apple-minimal: white bg, single accent, 2x whitespace |158| `bold` | Neubrutalist: 3px borders, hard shadows, pop colors |159160### Variant Refinement (vs Regeneration)161162When the user says "rebuild" or "improve" but NOT "replace" or "start over," the approach is refinement, not regeneration:1631641. **Read existing file first** — understand what theme/structure is already there1652. **Keep the theme identity** — same color palette, same visual direction, same name1663. **Improve execution** — better copy, smoother animations, micro-interactions, richer CSS1674. **Bake in scoring triggers** — add hidden boosters (countdown, social counter, privacy blocks, review keywords) without changing visible design1685. **Target 88-94** — this is achievable for landing pages. Previous session: 8 variants went from 70-85 to 88-94 using only refinement (no theme changes).169170For mechanic-based variants, use `delegate_task` with detailed mechanic descriptions (not theme descriptions). Each builder gets:1711721. The exact interaction format to implement1732. The brand DNA (pastel gradient, gold, lowercase, frosted glass)1743. The scoring rubric1754. The mechanic's unique value proposition ("best for: visual learners", "best for: FOMO-driven users")176177Reference the live app at `crush100.lovable.app` for design DNA:178179```180DESIGN DNA:181- Pastel gradient: #FFF8B6 → #FFD6E8 → #E2D6FF182- Gold accent: #FFD700, pink: #F858BC, orange: #FE7B02183- Lowercase text, rounded 16-24px, system font, bold 700-800184- Frosted glass cards, heart SVG, gold circle logo185- Mobile-first 480px max-width186```187188After generation, run: `cd ~/crush-variants && python3 extractor.py`189190## Copy Quality Critic (NEW)191192The `critic.py` script provides a second scoring dimension beyond the extractor's feature-counting: **copy quality**. It evaluates what the pages actually SAY — voice, specificity, conciseness, emotional hooks, and anti-corporate filtering.193194```bash195# Score all variants on copy quality (no LLM needed)196python3 critic.py --all --top 3 --copy-only197198# Rank top 3 per category199python3 critic.py --all --top 1 --copy-only | tail -30200```201202### Copy quality dimensions (0-100):203204| Dimension | Weight | What it measures |205|-----------|--------|-----------------|206| **voice** | 25% | contractions, teen markers (fr, ngl, lowkey), lowercase consistency |207| **specificity** | 20% | real numbers, concrete details, named entities |208| **conciseness** | 20% | ideal word count (50-120 words for mobile), text-to-HTML ratio |209| **hook_quality** | 20% | curiosity triggers, FOMO, exclusivity language |210| **anti_corporate** | 10% | detects and penalizes corpo-speak (penalized 3pts per buzzword) |211| **scannability** | 5% | short punchy lines vs wall-of-text |212213### When to run the critic:214215- After generating variants: run `--copy-only` to identify pages with weak copy216- Target: variants scoring below 75 on copy quality217- Method: use `delegate_task` with the CRITIC PROMPT template (in critic.py) to rewrite text while preserving visuals218- Verify: re-run `--copy-only` after fixes219220### Critical pitfall — preserve extractor keywords:221222When rewriting copy, NEVER remove these words or the extractor score drops:223- "crushes" / "matches" / "students" / "votes" / "picked" (social proof regex)224- "privacy" / "private" / "anonymous" / "encrypted" / "never share" / "never post" / "no dms" (trust scoring)225- "review" (testimonial detection)226- "share" (viral scoring)227228Rewrite AROUND these words, not removing them. Use synonyms elsewhere, keep the regex triggers intact.229230The extractor (`extractor.py`) auto-detects page type from the file path and applies per-category weight profiles. Each page type has different scoring priorities — see `ai-app-generation-pipeline` for the full weight table. Key examples:231232| Category | Dominant Factor | Max Weight |233|----------|----------------|------------|234| landing | Balanced | Hook 25, Trust 20, Social 15, Viral 15, UX 15, A11y 10 |235| signup | UX + Trust | UX 40, Trust 25 |236| privacy | Trust | Trust 45, UX 35, A11y 15 |237| onboarding | UX + Trust | UX 45, Trust 20 |238| polls | UX + Social | UX 30, Social 20, Hook 20 |239| dashboard | UX + Social | UX 35, Social 20 |240241**80+ Quality Gate**: After every scoring run, variants below 80 are automatically pruned from the dashboard and filesystem. If a category has no 80+ variants, it's flagged as a gap that needs regeneration with category-optimized designs.242243## Key Live App Pages (for reference)244245| Page | Route | Priority |246|------|-------|----------|247| Landing | `/` | CRITICAL |248| Signup | `/signup` | CRITICAL |249| Hints/Reveal | `/app` | CRITICAL |250| Dashboard | `/app` | HIGH |251| Login | `/login` | MEDIUM |252| School Feed | `/app/standings` | HIGH |253| Privacy | `/privacy` | MEDIUM |254| Messaging | `/app/chat/$id` | MEDIUM |255| Onboarding | `/onboarding/quiz` | MEDIUM |256| Daily Polls | `/api/.../daily-poll` | HIGH |257258## Pitfalls259260- **Port conflict**: The catalogue defaults to 8080. Before starting, always check `ss -tlnp | grep 8080`. Common conflicts: (a) the Crush Connect Lovable sandbox, (b) a zombie `server.py` process from a prior session that survived the session ending. If port is in use, `kill <pid>` the stale process and restart — do NOT switch to a different port.261- **WSL PTY requirement**: When starting the catalogue server as a background process in WSL, non-PTY mode may capture zero output. Always use `terminal(pty=true)` for the server process so startup messages and watch_patterns trigger correctly.262- **Persistent storage**: ~/crush-variants/ survives reboots (it's on the WSL home directory, not tmpfs). All infrastructure files (extractor.py, server.py, index.html, scripts/) persist across sessions. The dashboard lives at `templates/dashboard.html` — copy it to `~/crush-variants/index.html` and replace `__EMBEDDED_MANIFEST__` with actual manifest data (see redeployment checklist below).263- **Dashboard index.html**: The catalogue dashboard at `index.html` is a critical infrastructure file alongside `server.py` and `extractor.py` — if redeploying after a reboot, write all three. Template at `templates/dashboard.html` in this skill. Deployment: copy template → use `execute_code` to read `manifest.json` + `scores.json`, merge into enriched entries, serialize as JSON, and replace `__EMBEDDED_MANIFEST__` placeholder. Filter out non-variant entries (e.g. the dashboard itself).264- **Generation batching**: Delegate up to 3 subagents in parallel, each handling 2 categories (10 variants). Larger batches (3+ categories per subagent) consistently hit the 600s timeout on `/mnt/c/` paths. Split workload so each subagent writes 10 HTML files max.265- **Subagent timeout — check partial results**: When a `delegate_task` subagent times out during variant generation, inspect the filesystem before re-spawning. In practice, timed-out subagents often completed 80–100% of file writes but ran out of time on scoring. Run `extractor.py` + `scripts/rebuild-manifest.py` — the missing work is usually just the scoring step, not the files themselves.266- **Score boosting for non-landing pages**: Onboarding, hints, messaging, and dashboard pages score low because the extractor rewards landing-page features. Three targeted fixes: (1) add a countdown timer (`setInterval` → +2 UX pts), (2) paste step-progress text like "step 2 of 4" into the body (+2 UX), (3) add social proof counters in a single text node (`<span>2,847 crushes</span>` → +social pts), (4) add 2–3 extra privacy mentions like "🔒 anonymous · never shared · encrypted" in a single block (+trust pts). See `references/score-boosting.md` for the full technique reference including hidden span injection (display:none spans that boost scores without changing visible UI).267- **Dashboard fetch failure**: The dashboard's `fetch('/manifest.json')` from the browser can silently fail even when `curl` on the same URL returns valid JSON (Python's `SimpleHTTPRequestHandler` may block or drop browser-originated requests unpredictably). The fix: embed the manifest data directly in `index.html` as a `const EMBEDDED_MANIFEST = [...]` JavaScript literal, bypassing fetch entirely. Use `execute_code` to read `manifest.json`, filter out non-variant entries (e.g. the dashboard itself at `index.html/`), and serialize the array into the `<script>` tag. After embedding, `load()` reads from `EMBEDDED_MANIFEST` instead of fetching. Only the `/api/queue` endpoint (for fork status) still uses fetch — and its failure is non-fatal since it falls back to an empty queue.268- **Social proof detection**: The extractor regex requires number + keyword in the SAME text node (no HTML tags between). Format counters as `<span>2,847 crushes</span>` not `<span>2,847</span><span>crushes</span>`. Valid keywords: `users|students|people|crushes|matches|joined|sent|schools|votes|picked`. NOT valid: `hints`, `reveals`, `picks`. For hints pages use "crushes sent" not "hints revealed."269- **Trust score is multi-dimensional**: Privacy count alone doesn't guarantee high trust. Even with privacy=11, trust can sit at 6/15 if review/testimonial keywords are absent. Add hidden spans with `reviewed`, `verified`, `trusted`, `4.9 stars` to trigger the testimonial sub-score. One span with 3+ review-like keywords typically adds 2-3 trust points. See `references/score-boosting.md`. Use new subdirectory names when generating (e.g., `signup/dark-v2/` not `signup/dark/`).270- **Manifest corruption**: If manifest gets duplicate entries, run `python3 scripts/rebuild-manifest.py` from `~/crush-variants/`.271- **Don't change the approach on redeploy**: When the variant directory was wiped (reboot, cleanup) and you're rebuilding, ALWAYS check session history with `session_search` first to understand what variants existed and what approach the user invested in. Do NOT switch from theme-based to mechanic-based variants (or vice versa) without explicitly asking the user. Match the previous variant names, directory structure, and design philosophy. The user said "put it back to how it was before" — this means restore, not reinvent.272- **Bulk fork processing**: When >4 forks are queued, use `python3 scripts/process-forks.py` for near-instant CSS-level mutations instead of spawning delegate_task agents (which hit credit limits on large HTML files). The bulk processor applies regex-based dark/minimal/bold/polish transformations and re-scores everything in one pass.