From concept to 650+ words
The prompts (washed & ordered) used to take this project from a vague idea to 650+ words across 9 deployed pages. Hand any of them to a capable LLM to reproduce the corresponding artifact.
Open with who the tool is for and how it teaches. Everything downstream — schema, page splits, example sentences — falls out of this.
Pin the word object before generating any content. Every field must carry weight — no empty placeholders.
en, ipa, pos (vt / vi / vt·vi / n / n·vt / adj / adj·n / adv / idiom), zh, etym (brief, mixed Chinese+English, memory-aid not academic), example (sounds like The Economist — formal British English, real geopolitical/economic content), coll (collocations), dist (one-line Chinese distinction from its synonyms — this is the most important field). Group words into concept objects: { concept, zh, words[] }. For cross-page references, use a skip-entry where example: "skip", filtered out at runtime via a CLEAN_DATA pass.Bias hard toward zero-friction: no npm, no bundler, no server logic. Each HTML file is a working artifact on its own.
useState — no localStorage (must work in artifact sandboxes)<!DOCTYPE html>
<html lang="en"><head>
<meta charset="UTF-8">
<script src="https://cdnjs.cloudflare.com/.../react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/.../react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/.../babel.min.js"></script>
</head><body>
<div id="root"></div>
<script type="text/babel">
const { useState, useMemo } = React;
const DATA = [/* concept groups */];
const CLEAN_DATA = DATA.map(g => ({...g, words: g.words.filter(w => w.example !== "skip")})).filter(g => g.words.length);
function speak(word) { /* SpeechSynthesis, en-GB, rate 0.85 */ }
function App() { /* Header, Stats, Controls, ConceptGroup, WordCard, Quiz */ }
ReactDOM.createRoot(document.getElementById("root")).render(<App/>);
</script>
</body></html>
Six components, each doing one thing:
e.stopPropagation() so it doesn't toggle the card.POS badge colors are fixed across all pages: vt #C41B0E, vi #B8860B, vt/vi #8B4513, n #1B4965, n/vt #2D6A4F, adj #6B21A8, idiom #7C2D12, adv #B8860B.
Each of the 9 pages gets a distinct accent so the user can tell at a glance which "deck" they're in.
1 Core #E3120B red on #FAF7F2
2 Extended #1B4965 navy on #FAF7F2
3 Nouns #2D6A4F green on #F5FAF5
4 Specialist #92400E amber on #FFFCF5
5 Advanced #6B21A8 purple on #FAF5FF
6 Final #0E7C86 teal on #F0FAFB
7 Uncategorisable #475569 slate on #F8FAFC
8 Idioms #7C2D12 maroon on #FDF5F3
9 Headlines #312E81 indigo on #F5F3FF
The seed page. ~30 concept groups × 3–5 words ≈ 144 words. These are the networks that surface in nearly every Economist issue.
dist line is non-negotiable — it's what makes the synonym set teachable.Each page has a thematic spine. Before generating, scan all earlier pages for the English word — if it exists, either skip or insert a skip-entry.
Verification script after each page:
import re
seen = set()
for f in pages:
words = set(re.findall(r'en: "([^"]+)"', open(f).read()))
print(f, "duplicates:", words & seen)
seen |= words
These sit outside the synonym-network model — they're fixed expressions and rhetorical devices. Same schema, but pos: "idiom" and the dist field becomes "when an Economist writer reaches for this".
Once all 9 content pages are verified working, build a pure HTML/CSS landing page (no React) — red Economist-style header, dark stats bar, card grid. Each card: colored top bar matching the page theme, title, description, word count, hover lift.
"2px solid " + (active ? "#E3120B" : "#ccc").