← Mike Higgins Home
Custom Code Experiment •

Toki Pona

Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TOKI PONA — Nimi Flashcards</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=VT323&display=swap" rel="stylesheet">
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }

:root {
  --bg: #0a0a0a;
  --fg: #e8e8e8;
  --fg-dim: #888;
  --fg-bright: #fff;
  --border: #333;
  --correct: #4ade80;
  --wrong: #f87171;
  --mono: 'Share Tech Mono', monospace;
  --display: 'VT323', monospace;
}

html, body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--mono);
  height: 100%;
  width: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden;
}

#app {
  width: 100%;
  height: 100vh;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px;
  box-sizing: border-box;
  overflow: hidden;
}

.header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 1px solid var(--border);
  padding-bottom: 10px;
  flex-wrap: wrap;
  gap: 8px;
}

.title {
  font-family: var(--display);
  font-size: 36px;
  color: var(--fg-bright);
  letter-spacing: 3px;
  line-height: 1;
}

.subtitle {
  font-size: 11px;
  color: var(--fg-dim);
  letter-spacing: 1px;
  margin-top: 2px;
}

.stats {
  display: flex;
  gap: 20px;
  font-size: 11px;
  color: var(--fg-dim);
  text-align: right;
}

.stat-val {
  display: block;
  color: var(--fg-bright);
  font-size: 18px;
  font-family: var(--display);
  line-height: 1;
  margin-bottom: 2px;
}

.mode-bar {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}

.mode-btn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--fg-dim);
  font-family: var(--mono);
  font-size: 10px;
  padding: 4px 10px;
  cursor: pointer;
  letter-spacing: 1px;
  text-transform: uppercase;
  transition: all 0.1s;
}

.mode-btn.active {
  background: var(--fg-bright);
  border-color: var(--fg-bright);
  color: var(--bg);
}

.mode-btn:hover:not(.active) {
  border-color: var(--fg);
  color: var(--fg);
}

.top-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

.streak-row {
  font-size: 11px;
  color: var(--fg-dim);
  letter-spacing: 1px;
}

.streak-val { color: var(--correct); }

.category-row {
  display: flex;
  gap: 5px;
  flex-wrap: wrap;
}

.cat-btn {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--fg-dim);
  font-family: var(--mono);
  font-size: 10px;
  padding: 3px 8px;
  cursor: pointer;
  letter-spacing: 1px;
  transition: all 0.1s;
}

.cat-btn.active {
  background: #1e1e1e;
  border-color: var(--fg-dim);
  color: var(--fg);
}

.cat-btn:hover:not(.active) { border-color: #555; color: #aaa; }

.progress-bar-wrap {
  width: 100%;
  height: 2px;
  background: var(--border);
}

.progress-bar-fill {
  height: 2px;
  background: var(--fg-bright);
  transition: width 0.3s;
}

.card-area {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  flex: 1;
  min-height: 0;
}

.card-counter {
  font-size: 11px;
  color: var(--fg-dim);
  align-self: flex-end;
  letter-spacing: 1px;
}

/* 3D flip card */
.card-scene {
  width: 100%;
  flex: 1;
  min-height: 120px;
  perspective: 900px;
  cursor: pointer;
  user-select: none;
}

.card-scene.no-flip { cursor: default; }

.flashcard {
  width: 100%;
  height: 100%;
  position: relative;
  transform-style: preserve-3d;
  transition: transform 0.45s cubic-bezier(0.4, 0, 0.2, 1);
}

.flashcard.flipped { transform: rotateY(180deg); }

.card-face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 28px 24px;
  gap: 10px;
  border: 1px solid var(--fg-dim);
  background: var(--bg);
  transition: border-color 0.15s;
}

.card-face.back {
  transform: rotateY(180deg);
  border-color: var(--fg-bright);
}

.card-scene:not(.no-flip):hover .card-face.front { border-color: var(--fg-bright); }

/* quiz-mode card: no faces, plain display */
.quiz-card {
  width: 100%;
  flex: 1;
  min-height: 120px;
  border: 1px solid var(--fg-dim);
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 28px 24px;
  gap: 10px;
  position: relative;
  display: none;
}

.card-label {
  font-size: 10px;
  color: var(--fg-dim);
  letter-spacing: 2px;
  text-transform: uppercase;
  position: absolute;
  top: 10px;
  left: 12px;
}

.card-word {
  font-family: var(--display);
  font-size: 64px;
  color: var(--fg-bright);
  letter-spacing: 3px;
  line-height: 1;
  text-align: center;
}

.card-pos {
  font-size: 10px;
  color: var(--fg-dim);
  letter-spacing: 1px;
}

.card-meaning {
  font-size: 17px;
  color: var(--fg);
  text-align: center;
  max-width: 400px;
  line-height: 1.5;
}

.card-example {
  font-size: 11px;
  color: var(--fg-dim);
  text-align: center;
  font-style: italic;
  padding-top: 8px;
  border-top: 1px solid var(--border);
  width: 100%;
}

.tap-hint {
  position: absolute;
  bottom: 10px;
  right: 12px;
  font-size: 10px;
  color: var(--fg-dim);
  letter-spacing: 1px;
}

.action-row {
  display: flex;
  gap: 8px;
  width: 100%;
}

.action-btn {
  flex: 1;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--fg-dim);
  font-family: var(--mono);
  font-size: 11px;
  padding: 10px 4px;
  cursor: pointer;
  letter-spacing: 1px;
  text-transform: uppercase;
  transition: all 0.1s;
  display: none;
}

.action-btn:hover { border-color: var(--fg); color: var(--fg); }
.action-btn.correct-btn:hover { border-color: var(--correct); color: var(--correct); }
.action-btn.skip-btn:hover { border-color: var(--wrong); color: var(--wrong); }

.next-btn {
  width: 100%;
  background: transparent;
  border: 1px solid var(--border);
  color: var(--fg-dim);
  font-family: var(--mono);
  font-size: 11px;
  padding: 10px;
  cursor: pointer;
  letter-spacing: 1px;
  text-transform: uppercase;
  transition: all 0.1s;
}

.next-btn:hover { border-color: var(--fg-bright); color: var(--fg-bright); }

.quiz-options {
  display: none;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
  width: 100%;
}

.quiz-opt {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--fg);
  font-family: var(--mono);
  font-size: 12px;
  padding: 12px 10px;
  cursor: pointer;
  text-align: left;
  letter-spacing: 0.5px;
  transition: all 0.1s;
  line-height: 1.4;
}

.quiz-opt:hover:not(:disabled) { border-color: var(--fg); }
.quiz-opt.correct-ans { border-color: var(--correct); color: var(--correct); }
.quiz-opt.wrong-ans { border-color: var(--wrong); color: var(--wrong); }
.quiz-opt:disabled { cursor: default; }

.result-flash {
  font-family: var(--display);
  font-size: 26px;
  letter-spacing: 4px;
  height: 32px;
  text-align: center;
  width: 100%;
}

.result-flash.c { color: var(--correct); }
.result-flash.w { color: var(--wrong); }

.done-screen {
  display: none;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 20px;
  padding: 40px 20px;
  text-align: center;
  border: 1px solid var(--border);
}

.done-label {
  font-size: 11px;
  color: var(--fg-dim);
  letter-spacing: 3px;
}

.done-score {
  font-family: var(--display);
  font-size: 80px;
  color: var(--fg-bright);
  line-height: 1;
}

.done-msg {
  font-size: 12px;
  color: var(--fg-dim);
  letter-spacing: 1px;
  max-width: 340px;
  line-height: 1.8;
}

.restart-btn {
  background: var(--fg-bright);
  border: 1px solid var(--fg-bright);
  color: var(--bg);
  font-family: var(--mono);
  font-size: 11px;
  padding: 12px 28px;
  cursor: pointer;
  letter-spacing: 2px;
  text-transform: uppercase;
  transition: all 0.15s;
}

.restart-btn:hover {
  background: transparent;
  color: var(--fg-bright);
}

.footer {
  font-size: 10px;
  color: #444;
  letter-spacing: 1px;
  border-top: 1px solid var(--border);
  padding-top: 8px;
  text-align: center;
}
</style>
</head>
<body>
<div id="app">

  <div class="header">
    <div>
      <div class="title">TOKI PONA</div>
      <div class="subtitle">nimi li pona &bull; words are good</div>
    </div>
    <div class="stats">
      <div><span class="stat-val" id="stat-correct">0</span>correct</div>
      <div><span class="stat-val" id="stat-seen">0</span>seen</div>
      <div><span class="stat-val" id="stat-left">—</span>left</div>
    </div>
  </div>

  <div class="top-row">
    <div class="mode-bar">
      <button class="mode-btn active" id="btn-flash" onclick="setMode('flash')">FLASH</button>
      <button class="mode-btn" id="btn-quiz" onclick="setMode('quiz')">QUIZ</button>
      <button class="mode-btn" id="btn-rev" onclick="setMode('rev')">EN→TP</button>
    </div>
    <div class="streak-row">streak: <span class="streak-val" id="streak-val">0</span></div>
  </div>

  <div class="category-row" id="cat-row"></div>

  <div class="progress-bar-wrap">
    <div class="progress-bar-fill" id="prog" style="width:0%"></div>
  </div>

  <div class="card-area" id="card-area">
    <div class="card-counter" id="card-counter"></div>

    <!-- Flash / Rev mode: 3D flip card -->
    <div class="card-scene" id="card-scene" onclick="flipCard()">
      <div class="flashcard" id="flashcard">
        <div class="card-face front" id="card-front">
          <span class="card-label" id="front-label">TOKI PONA</span>
          <div class="card-word" id="front-word">—</div>
          <div class="card-pos" id="front-pos"></div>
          <span class="tap-hint">[ tap to flip ]</span>
        </div>
        <div class="card-face back" id="card-back">
          <span class="card-label" id="back-label">MEANING</span>
          <div class="card-word" id="back-word" style="font-size:26px"></div>
          <div class="card-pos" id="back-pos"></div>
          <div class="card-meaning" id="back-meaning"></div>
          <div class="card-example" id="back-example"></div>
        </div>
      </div>
    </div>

    <!-- Quiz mode: plain card, no flip, no answer -->
    <div class="quiz-card" id="quiz-card">
      <span class="card-label">TOKI PONA</span>
      <div class="card-word" id="quiz-word">—</div>
      <div class="card-pos" id="quiz-pos"></div>
    </div>

    <div class="quiz-options" id="quiz-opts"></div>
    <div class="result-flash" id="result-flash"></div>

    <div class="action-row" id="action-row">
      <button class="action-btn correct-btn" onclick="markCard(true)">✓ GOT IT</button>
      <button class="action-btn skip-btn" onclick="markCard(false)">✗ MISSED</button>
    </div>

    <button class="next-btn" id="next-btn" onclick="nextCard()">NEXT CARD →</button>
  </div>

  <div class="done-screen" id="done-screen">
    <div class="done-label">ROUND COMPLETE</div>
    <div class="done-score" id="done-score"></div>
    <div class="done-msg" id="done-msg"></div>
    <button class="restart-btn" onclick="restart()">o musi sin! &bull; PLAY AGAIN</button>
  </div>

  <div class="footer">120 nimi pu &bull; toki pona by sonja lang &bull; flashcard game</div>

</div>

<script>
const WORDS = [
  {w:"a",pos:"interj",en:"ah, ha, oh — emotional emphasis",cat:"particle",ex:"a a a! (laughter)"},
  {w:"akesi",pos:"n",en:"reptile, amphibian, non-cute animal",cat:"nature",ex:"akesi li tawa lon telo."},
  {w:"ala",pos:"mod",en:"no, not, zero, none",cat:"particle",ex:"mi ala sona e ni."},
  {w:"alasa",pos:"vt",en:"to hunt, forage, seek",cat:"action",ex:"mi alasa e moku."},
  {w:"ale",pos:"n/mod",en:"all, every, everything, 100",cat:"particle",ex:"ale li pona."},
  {w:"anpa",pos:"mod",en:"down, below, under, humble",cat:"position",ex:"soweli li lon anpa."},
  {w:"ante",pos:"mod",en:"different, other, changed",cat:"quality",ex:"nimi li ante."},
  {w:"anu",pos:"conj",en:"or (marks a question or choice)",cat:"particle",ex:"sina pona anu ike?"},
  {w:"awen",pos:"v",en:"to stay, to keep, to remain, to protect",cat:"action",ex:"o awen pona."},
  {w:"e",pos:"part",en:"marks the direct object",cat:"particle",ex:"mi moku e kili."},
  {w:"en",pos:"conj",en:"and (between subjects)",cat:"particle",ex:"mi en sina li toki."},
  {w:"esun",pos:"n",en:"market, shop, trade, business",cat:"place",ex:"mi tawa esun."},
  {w:"ijo",pos:"n",en:"thing, object, matter, phenomenon",cat:"basic",ex:"ijo ni li seme?"},
  {w:"ike",pos:"mod",en:"bad, negative, complex, harmful",cat:"quality",ex:"tenpo ike li kama."},
  {w:"ilo",pos:"n",en:"tool, instrument, device",cat:"object",ex:"ilo ni li pona."},
  {w:"insa",pos:"n",en:"inside, contents, center, stomach",cat:"position",ex:"insa pi tomo li lete."},
  {w:"jaki",pos:"mod",en:"disgusting, toxic, unclean, gross",cat:"quality",ex:"telo ni li jaki."},
  {w:"jan",pos:"n",en:"person, people, human being",cat:"basic",ex:"jan li wile moku."},
  {w:"jelo",pos:"mod",en:"yellow, yellowish",cat:"color",ex:"kili jelo li pona."},
  {w:"jo",pos:"vt",en:"to have, to contain, to carry",cat:"action",ex:"mi jo e lipu."},
  {w:"kala",pos:"n",en:"fish, sea creature",cat:"nature",ex:"kala li tawa lon telo."},
  {w:"kalama",pos:"n/v",en:"sound, noise; to make sound",cat:"sense",ex:"kalama musi li pona."},
  {w:"kama",pos:"v",en:"to come, to arrive, to happen",cat:"action",ex:"kama pona! (welcome)"},
  {w:"kasi",pos:"n",en:"plant, herb, leaf, vegetation",cat:"nature",ex:"kasi li laso."},
  {w:"ken",pos:"v/mod",en:"to be able to, can, possible",cat:"particle",ex:"mi ken toki."},
  {w:"kepeken",pos:"prep",en:"to use, by means of, with",cat:"particle",ex:"mi toki kepeken ilo."},
  {w:"kili",pos:"n",en:"fruit, vegetable, mushroom",cat:"food",ex:"kili li moku pona."},
  {w:"kiwen",pos:"n/mod",en:"hard object, stone, metal, solid",cat:"object",ex:"kiwen li pakala e ilo."},
  {w:"ko",pos:"n",en:"semi-solid, paste, clay, powder",cat:"object",ex:"ko li jaki."},
  {w:"kon",pos:"n",en:"air, atmosphere, spirit, essence",cat:"nature",ex:"kon li lete."},
  {w:"kule",pos:"mod",en:"coloured, colorful; a color",cat:"color",ex:"kule ni li seme?"},
  {w:"kulupu",pos:"n",en:"group, community, society, company",cat:"social",ex:"kulupu ni li pona."},
  {w:"kute",pos:"v/n",en:"to hear, to listen; ear",cat:"sense",ex:"mi kute e kalama."},
  {w:"la",pos:"part",en:"context separator (if / when)",cat:"particle",ex:"tenpo pimeja la mi lape."},
  {w:"lape",pos:"v/mod",en:"to sleep, to rest; sleeping",cat:"action",ex:"mi wile lape."},
  {w:"laso",pos:"mod",en:"blue, green",cat:"color",ex:"telo li laso."},
  {w:"lawa",pos:"n/v",en:"head, mind, leader; to lead, control",cat:"body",ex:"lawa mi li pakala."},
  {w:"len",pos:"n",en:"clothing, fabric, cover",cat:"object",ex:"len li lete ala e mi."},
  {w:"lete",pos:"mod",en:"cold, cool, raw, uncooked",cat:"quality",ex:"telo lete li pona."},
  {w:"li",pos:"part",en:"between subject and verb / adjective",cat:"particle",ex:"jan li tawa."},
  {w:"lili",pos:"mod",en:"small, few, little, less",cat:"quality",ex:"soweli lili li pona."},
  {w:"linja",pos:"n",en:"long flexible object, line, rope, hair",cat:"object",ex:"linja li lon sinpin."},
  {w:"lipu",pos:"n",en:"flat object, paper, book, document",cat:"object",ex:"mi lukin e lipu."},
  {w:"loje",pos:"mod",en:"red, reddish",cat:"color",ex:"telo loje li tawa."},
  {w:"lon",pos:"prep/v",en:"at, in, on; to exist, to be present",cat:"particle",ex:"mi lon tomo."},
  {w:"luka",pos:"n",en:"hand, arm; five",cat:"body",ex:"luka mi li seli."},
  {w:"lukin",pos:"v/n",en:"to look, to see, to seek; eye",cat:"sense",ex:"mi lukin e suno."},
  {w:"lupa",pos:"n",en:"hole, door, window, portal",cat:"object",ex:"lupa li lon tomo."},
  {w:"ma",pos:"n",en:"earth, land, territory, country",cat:"place",ex:"ma ni li suli."},
  {w:"mama",pos:"n",en:"parent, ancestor, creator, caretaker",cat:"social",ex:"mama mi li pona."},
  {w:"mani",pos:"n",en:"money, wealth, large domesticated animal",cat:"object",ex:"mani li jo ala e pona."},
  {w:"meli",pos:"n",en:"woman, female, feminine person",cat:"social",ex:"meli li toki."},
  {w:"mi",pos:"pro",en:"I, me, we, us",cat:"particle",ex:"mi pona."},
  {w:"mije",pos:"n",en:"man, male, masculine person",cat:"social",ex:"mije li sona."},
  {w:"moku",pos:"v/n",en:"to eat, to drink; food, meal",cat:"food",ex:"mi moku e kili."},
  {w:"moli",pos:"v/mod",en:"to die, to kill; dead, dying",cat:"quality",ex:"kasi li moli."},
  {w:"monsi",pos:"n",en:"back, behind, rear",cat:"position",ex:"jan li lon monsi mi."},
  {w:"mu",pos:"interj",en:"animal sound — moo, woof, meow, etc.",cat:"particle",ex:"soweli li mu."},
  {w:"mun",pos:"n",en:"moon, night sky object, star",cat:"nature",ex:"mun li suli."},
  {w:"musi",pos:"mod/v",en:"fun, amusing, artistic; to play",cat:"quality",ex:"musi li pona tawa mi."},
  {w:"mute",pos:"mod/n",en:"many, very, much; quantity",cat:"particle",ex:"jan mute li kama."},
  {w:"nanpa",pos:"n/part",en:"number, digit; ordinal marker",cat:"particle",ex:"nanpa wan — number one."},
  {w:"nasa",pos:"mod",en:"unusual, strange, silly, drunk",cat:"quality",ex:"jan nasa li kalama."},
  {w:"nasin",pos:"n",en:"way, method, path, road, custom",cat:"place",ex:"nasin li suli."},
  {w:"nena",pos:"n",en:"bump, hill, mountain, nose, button",cat:"nature",ex:"nena li suli mute."},
  {w:"ni",pos:"mod",en:"this, that",cat:"particle",ex:"ijo ni li seme?"},
  {w:"nimi",pos:"n",en:"word, name",cat:"basic",ex:"nimi mi li seme?"},
  {w:"noka",pos:"n",en:"foot, leg, bottom",cat:"body",ex:"noka mi li wawa."},
  {w:"o",pos:"part",en:"address or command marker",cat:"particle",ex:"o kama! (come!)"},
  {w:"oko",pos:"n",en:"eye (synonym of lukin)",cat:"body",ex:"oko mi li loje."},
  {w:"olin",pos:"vt",en:"to love, to have compassion for",cat:"emotion",ex:"mi olin e sina."},
  {w:"ona",pos:"pro",en:"they, them, it",cat:"particle",ex:"ona li pona."},
  {w:"open",pos:"v/mod",en:"to open, start, turn on; beginning",cat:"action",ex:"o open e lupa."},
  {w:"pakala",pos:"v/n",en:"to ruin, break, damage; error",cat:"action",ex:"ilo li pakala."},
  {w:"pali",pos:"v",en:"to do, to make, to work, to create",cat:"action",ex:"mi pali e tomo."},
  {w:"palisa",pos:"n",en:"long hard object, stick, rod",cat:"object",ex:"palisa li lon ma."},
  {w:"pan",pos:"n",en:"grain, bread, pasta, cereal",cat:"food",ex:"pan li moku pona."},
  {w:"pana",pos:"vt",en:"to give, to send, to emit",cat:"action",ex:"o pana e ijo pona."},
  {w:"pi",pos:"part",en:"regroups modifiers: X pi Y Z = X of (Y Z)",cat:"particle",ex:"tomo pi telo nasa."},
  {w:"pilin",pos:"n/v",en:"heart, feeling; to feel, to think",cat:"emotion",ex:"pilin mi li pona."},
  {w:"pimeja",pos:"mod",en:"black, dark, unlit",cat:"color",ex:"pimeja li ike ala."},
  {w:"pini",pos:"mod/v",en:"past, finished, ended; to finish",cat:"action",ex:"pali mi li pini."},
  {w:"pipi",pos:"n",en:"bug, insect, spider",cat:"nature",ex:"pipi lili li lon kasi."},
  {w:"poka",pos:"n/prep",en:"side, hip; next to, nearby",cat:"position",ex:"jan li lon poka mi."},
  {w:"poki",pos:"n",en:"container, box, cup, bowl",cat:"object",ex:"poki li jo e telo."},
  {w:"pona",pos:"mod/n",en:"good, simple, positive, nice; goodness",cat:"quality",ex:"pona! (great / thanks!)"},
  {w:"pu",pos:"mod",en:"interacting with the official Toki Pona book",cat:"particle",ex:"mi pu."},
  {w:"sama",pos:"mod/prep",en:"same, similar; like, as",cat:"quality",ex:"ona li sama mi."},
  {w:"seli",pos:"mod/n",en:"hot, warm, cooked; heat, fire",cat:"quality",ex:"ilo seli li pona."},
  {w:"selo",pos:"n",en:"outer layer, skin, bark, shell",cat:"body",ex:"selo kasi li laso."},
  {w:"seme",pos:"q",en:"what, which (question word)",cat:"particle",ex:"sina jo e seme?"},
  {w:"sewi",pos:"n/mod",en:"above, high, divine, sacred",cat:"position",ex:"sewi li suno."},
  {w:"sijelo",pos:"n",en:"body, physical form, torso",cat:"body",ex:"sijelo mi li pona."},
  {w:"sike",pos:"n/mod",en:"circle, ball, cycle; round",cat:"object",ex:"sike li lon sewi."},
  {w:"sin",pos:"mod",en:"new, fresh, another, additional",cat:"quality",ex:"ijo sin li kama."},
  {w:"sina",pos:"pro",en:"you (singular and plural)",cat:"particle",ex:"sina pona!"},
  {w:"sinpin",pos:"n",en:"face, wall, front, chest",cat:"position",ex:"sinpin tomo li walo."},
  {w:"sitelen",pos:"n/v",en:"image, picture, writing; to draw",cat:"action",ex:"mi sitelen e kasi."},
  {w:"sona",pos:"v/n",en:"to know, to understand; knowledge",cat:"emotion",ex:"mi sona e toki pona."},
  {w:"soweli",pos:"n",en:"animal, land mammal, loveable creature",cat:"nature",ex:"soweli li musi."},
  {w:"suli",pos:"mod",en:"big, great, important, long",cat:"quality",ex:"tomo suli li lon ma."},
  {w:"suno",pos:"n",en:"sun, light, brightness, glow",cat:"nature",ex:"suno li wawa."},
  {w:"supa",pos:"n",en:"flat surface, furniture, floor",cat:"object",ex:"mi lape lon supa."},
  {w:"suwi",pos:"mod",en:"sweet, cute, adorable, innocent",cat:"quality",ex:"soweli li suwi mute."},
  {w:"tan",pos:"prep",en:"from, because of, by",cat:"particle",ex:"mi kama tan ma ante."},
  {w:"taso",pos:"conj/mod",en:"but, however; only, just",cat:"particle",ex:"mi taso li lon."},
  {w:"tawa",pos:"prep/v",en:"to, toward, for; to move, to go",cat:"action",ex:"mi tawa tomo."},
  {w:"telo",pos:"n",en:"water, liquid, fluid",cat:"nature",ex:"mi moku e telo."},
  {w:"tenpo",pos:"n",en:"time, period, situation",cat:"basic",ex:"tenpo ni li pona."},
  {w:"toki",pos:"v/n",en:"to speak, to say; language, speech",cat:"action",ex:"toki! (hello!)"},
  {w:"tomo",pos:"n",en:"indoor space, house, building",cat:"place",ex:"tomo mi li lili."},
  {w:"tu",pos:"mod/n",en:"two; to split, divide",cat:"particle",ex:"jan tu li tawa."},
  {w:"unpa",pos:"v/mod",en:"to have sex with; sexual",cat:"action",ex:"—"},
  {w:"uta",pos:"n",en:"mouth, lips",cat:"body",ex:"uta mi li suwi."},
  {w:"utala",pos:"v/n",en:"to fight, to battle; conflict",cat:"action",ex:"utala ala, pona tawa!"},
  {w:"walo",pos:"mod",en:"white, light-coloured, pale",cat:"color",ex:"len walo li pona."},
  {w:"wan",pos:"mod/n",en:"one; unique; to unite",cat:"particle",ex:"jan wan li lon."},
  {w:"waso",pos:"n",en:"bird, flying creature",cat:"nature",ex:"waso li musi."},
  {w:"wawa",pos:"mod",en:"strong, powerful, energetic, confident",cat:"quality",ex:"ona li wawa."},
  {w:"weka",pos:"mod/v",en:"away, absent, remote; to remove",cat:"action",ex:"ona li weka."},
  {w:"wile",pos:"v/mod",en:"to want, to need, must",cat:"particle",ex:"mi wile moku."},
];

const CATS = ["all","basic","particle","action","quality","nature","body","social","food","color","position","emotion","sense","place","object"];

let mode = 'flash', deck = [], idx = 0, correct = 0, seen = 0, streak = 0, revealed = false, activeCat = 'all', quizAnswered = false;

function shuffle(arr) {
  let a = [...arr];
  for (let i = a.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [a[i], a[j]] = [a[j], a[i]];
  }
  return a;
}

function buildDeck() {
  let pool = activeCat === 'all' ? WORDS : WORDS.filter(w => w.cat === activeCat);
  deck = shuffle(pool);
  idx = 0; correct = 0; seen = 0; streak = 0;
  updateStats();
}

function setMode(m) {
  mode = m;
  ['flash','quiz','rev'].forEach(x => document.getElementById('btn-' + x).classList.toggle('active', x === m));
  buildDeck();
  showCard();
}

function setCategory(cat) {
  activeCat = cat;
  document.querySelectorAll('.cat-btn').forEach(b => b.classList.toggle('active', b.dataset.cat === cat));
  buildDeck();
  showCard();
}

function buildCatRow() {
  const row = document.getElementById('cat-row');
  CATS.forEach(c => {
    const b = document.createElement('button');
    b.className = 'cat-btn' + (c === activeCat ? ' active' : '');
    b.dataset.cat = c;
    b.textContent = c;
    b.onclick = () => setCategory(c);
    row.appendChild(b);
  });
}

function updateStats() {
  document.getElementById('stat-correct').textContent = correct;
  document.getElementById('stat-seen').textContent = seen;
  document.getElementById('stat-left').textContent = deck.length - idx;
  document.getElementById('streak-val').textContent = streak;
  const pct = deck.length ? (idx / deck.length * 100) : 0;
  document.getElementById('prog').style.width = pct + '%';
  document.getElementById('card-counter').textContent = deck.length ? (idx + 1) + ' / ' + deck.length : '';
}

function showCard() {
  document.getElementById('done-screen').style.display = 'none';
  document.getElementById('card-area').style.display = 'flex';

  if (!deck.length || idx >= deck.length) { showDone(); return; }

  const card = deck[idx];
  revealed = false;
  quizAnswered = false;

  const scene = document.getElementById('card-scene');
  const fc = document.getElementById('flashcard');
  const quizCard = document.getElementById('quiz-card');
  const actionRow = document.getElementById('action-row');
  const nextBtn = document.getElementById('next-btn');
  const quizOpts = document.getElementById('quiz-opts');
  const rf = document.getElementById('result-flash');

  rf.textContent = '';
  rf.className = 'result-flash';

  // reset flip without animation
  fc.style.transition = 'none';
  fc.classList.remove('flipped');
  requestAnimationFrame(() => { fc.style.transition = ''; });

  if (mode === 'quiz') {
    scene.style.display = 'none';
    quizCard.style.display = 'flex';
    actionRow.style.display = 'none';
    nextBtn.style.display = 'none';
    quizOpts.style.display = 'grid';

    document.getElementById('quiz-word').textContent = card.w;
    document.getElementById('quiz-word').style.fontSize = '64px';
    document.getElementById('quiz-pos').textContent = '[' + card.pos + '] ' + card.cat;

    buildQuiz(card);

  } else {
    scene.style.display = 'block';
    quizCard.style.display = 'none';
    quizOpts.style.display = 'none';
    scene.classList.remove('no-flip');

    // action row hidden until flipped
    actionRow.querySelectorAll('.action-btn').forEach(b => b.style.display = 'none');
    nextBtn.style.display = 'none';

    if (mode === 'rev') {
      document.getElementById('front-label').textContent = 'ENGLISH → TOKI PONA';
      document.getElementById('front-word').textContent = card.en;
      document.getElementById('front-word').style.fontSize = '22px';
      document.getElementById('front-pos').textContent = '[' + card.pos + '] ' + card.cat;
      document.getElementById('back-label').textContent = 'TOKI PONA';
      document.getElementById('back-word').textContent = card.w;
      document.getElementById('back-word').style.fontSize = '64px';
      document.getElementById('back-pos').textContent = '';
      document.getElementById('back-meaning').textContent = '';
      document.getElementById('back-example').textContent = 'e.g. ' + card.ex;
    } else {
      document.getElementById('front-label').textContent = 'TOKI PONA';
      document.getElementById('front-word').textContent = card.w;
      document.getElementById('front-word').style.fontSize = '64px';
      document.getElementById('front-pos').textContent = '[' + card.pos + '] ' + card.cat;
      document.getElementById('back-label').textContent = 'MEANING';
      document.getElementById('back-word').textContent = card.en;
      document.getElementById('back-word').style.fontSize = '22px';
      document.getElementById('back-pos').textContent = '[' + card.pos + '] ' + card.cat;
      document.getElementById('back-meaning').textContent = '';
      document.getElementById('back-example').textContent = 'e.g. ' + card.ex;
    }
  }

  updateStats();
}

function flipCard() {
  if (mode === 'quiz') return;
  const fc = document.getElementById('flashcard');
  const actionRow = document.getElementById('action-row');
  const nextBtn = document.getElementById('next-btn');
  if (fc.classList.contains('flipped')) {
    fc.classList.remove('flipped');
    revealed = false;
    actionRow.querySelectorAll('.action-btn').forEach(b => b.style.display = 'none');
    nextBtn.style.display = 'none';
  } else {
    fc.classList.add('flipped');
    revealed = true;
    setTimeout(() => {
      actionRow.querySelectorAll('.action-btn').forEach(b => b.style.display = 'block');
      nextBtn.style.display = 'block';
    }, 300);
  }
}

function markCard(got) {
  if (got) { correct++; streak++; } else { streak = 0; }
  seen++;
  idx++;
  updateStats();
  showCard();
}

function nextCard() {
  idx++;
  updateStats();
  showCard();
}

function buildQuiz(card) {
  const pool = WORDS.filter(w => w.w !== card.w);
  const wrong = shuffle(pool).slice(0, 3);
  const opts = shuffle([card, ...wrong]);
  const qOpts = document.getElementById('quiz-opts');
  qOpts.innerHTML = '';
  opts.forEach(o => {
    const b = document.createElement('button');
    b.className = 'quiz-opt';
    b.textContent = o.en;
    b.onclick = () => checkQuiz(b, o.w === card.w);
    qOpts.appendChild(b);
  });
}

function checkQuiz(btn, isCorrect) {
  if (quizAnswered) return;
  quizAnswered = true;
  const rf = document.getElementById('result-flash');
  const btns = document.getElementById('quiz-opts').querySelectorAll('.quiz-opt');
  btns.forEach(b => b.disabled = true);
  if (isCorrect) {
    btn.classList.add('correct-ans');
    rf.textContent = 'PONA!';
    rf.className = 'result-flash c';
    correct++; streak++;
  } else {
    btn.classList.add('wrong-ans');
    rf.textContent = 'IKE...';
    rf.className = 'result-flash w';
    btns.forEach(b => { if (!b.classList.contains('wrong-ans')) b.classList.add('correct-ans'); });
    streak = 0;
  }
  seen++;
  updateStats();
  setTimeout(() => { idx++; showCard(); }, 1500);
}

function showDone() {
  document.getElementById('card-area').style.display = 'none';
  const ds = document.getElementById('done-screen');
  ds.style.display = 'flex';
  const pct = deck.length ? Math.round(correct / deck.length * 100) : 0;
  document.getElementById('done-score').textContent = pct + '%';
  const msgs = [
    [90, 'wawa! sina sona e toki pona!\nexcellent mastery!'],
    [70, 'pona! sina pali pona!\ngood work, keep it up!'],
    [50, 'awen o! you\'re getting there.\nkeep practicing!'],
    [0,  'o awen musi!\nkeep playing — it gets easier!'],
  ];
  document.getElementById('done-msg').textContent = msgs.find(m => pct >= m[0])[1];
}

function restart() {
  buildDeck();
  showCard();
}

buildCatRow();
buildDeck();
showCard();
</script>
</body>
</html>