Untitled

Anonymous
html
01/29/2026 4:32 AM
69.3 KB
14
Indexable
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>History Study – Flashcards + Practice Test</title>
  <style>
    :root{
      --bg:#0b1020; --panel:#121a33; --panel2:#0f1630;
      --text:#e9eeff; --muted:#aeb8e6; --line:#22305f;
      --accent:#7aa2ff; --good:#3ddc97; --bad:#ff6b7a; --warn:#ffd166;
      --shadow: 0 12px 30px rgba(0,0,0,.35);
      --radius: 18px;
      --mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
      --sans: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
    }
    *{box-sizing:border-box}
    body{
      margin:0; font-family:var(--sans);
      background: radial-gradient(1200px 800px at 20% -10%, rgba(122,162,255,.22), transparent 55%),
                  radial-gradient(900px 700px at 100% 0%, rgba(61,220,151,.16), transparent 50%),
                  var(--bg);
      color:var(--text);
      min-height:100vh;
    }
    a{color:inherit}
    .wrap{max-width:1100px;margin:0 auto;padding:22px 16px 40px}
    header{
      display:flex; gap:14px; align-items:flex-end; justify-content:space-between; flex-wrap:wrap;
      padding:14px 2px 18px;
    }
    .brand h1{
      margin:0; font-size:26px; letter-spacing:.2px;
    }
    .brand p{margin:6px 0 0;color:var(--muted); font-size:13px; max-width:700px}
    .tabs{
      display:flex; gap:10px; align-items:center; flex-wrap:wrap;
      background:rgba(18,26,51,.55); border:1px solid var(--line);
      padding:8px; border-radius:999px;
      box-shadow: var(--shadow);
      backdrop-filter: blur(8px);
    }
    .tab{
      border:1px solid transparent;
      background:transparent; color:var(--text);
      padding:9px 12px; border-radius:999px;
      cursor:pointer; font-weight:650; font-size:13px;
      transition: .15s ease;
    }
    .tab:hover{border-color:rgba(122,162,255,.35)}
    .tab.active{background:rgba(122,162,255,.18); border-color:rgba(122,162,255,.45)}
    .grid{
      display:grid; grid-template-columns: 1.3fr .7fr; gap:16px;
    }
    @media (max-width: 940px){
      .grid{grid-template-columns:1fr}
    }
    .card{
      background: linear-gradient(180deg, rgba(18,26,51,.86), rgba(15,22,48,.86));
      border:1px solid var(--line);
      border-radius: var(--radius);
      box-shadow: var(--shadow);
      overflow:hidden;
    }
    .card .head{
      padding:14px 16px;
      border-bottom:1px solid var(--line);
      display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap;
    }
    .card .head h2{margin:0;font-size:15px}
    .card .head .sub{color:var(--muted);font-size:12px}
    .body{padding:16px}
    .row{display:flex; gap:10px; align-items:center; flex-wrap:wrap}
    .pill{
      font-family: var(--mono);
      font-size:12px; color:var(--muted);
      padding:6px 10px; border-radius:999px;
      border:1px solid rgba(34,48,95,.9);
      background: rgba(10,16,40,.55);
    }
    .btn{
      cursor:pointer;
      border:1px solid rgba(122,162,255,.35);
      background: rgba(122,162,255,.12);
      color:var(--text);
      padding:10px 12px;
      border-radius: 12px;
      font-weight:700;
      font-size:13px;
      transition:.15s ease;
      user-select:none;
    }
    .btn:hover{transform: translateY(-1px); border-color: rgba(122,162,255,.55)}
    .btn:active{transform: translateY(0px)}
    .btn.secondary{
      border-color: rgba(174,184,230,.22);
      background: rgba(174,184,230,.08);
    }
    .btn.danger{
      border-color: rgba(255,107,122,.35);
      background: rgba(255,107,122,.12);
    }
    .btn.good{
      border-color: rgba(61,220,151,.35);
      background: rgba(61,220,151,.12);
    }
    .btn.small{padding:7px 10px; border-radius: 10px; font-size:12px}
    .inputs{
      display:grid; grid-template-columns: 1fr 1fr; gap:10px;
      margin-top:10px;
    }
    @media (max-width: 520px){
      .inputs{grid-template-columns:1fr}
    }
    input[type="text"], select, textarea{
      width:100%;
      padding:11px 12px;
      border-radius: 12px;
      border:1px solid rgba(34,48,95,.9);
      background: rgba(10,16,40,.55);
      color:var(--text);
      outline:none;
    }
    textarea{min-height:110px; resize:vertical; font-family:var(--sans)}
    label{font-size:12px; color:var(--muted); display:block; margin:0 0 6px 2px}
    .hr{height:1px;background:var(--line);margin:14px 0}
    .progress{
      height:10px; border-radius:999px;
      background: rgba(34,48,95,.5);
      overflow:hidden; border:1px solid rgba(34,48,95,.9);
    }
    .bar{height:100%; width:0%; background: rgba(122,162,255,.75)}
    /* Flashcard */
    .flashcard{
      display:grid;
      gap:12px;
    }
    .fc-stage{
      position:relative;
      background: rgba(10,16,40,.45);
      border:1px solid rgba(34,48,95,.9);
      border-radius: var(--radius);
      min-height: 230px;
      padding:18px 18px 16px;
      box-shadow: inset 0 0 0 1px rgba(255,255,255,.03);
    }
    .fc-badge{
      position:absolute; top:14px; right:14px;
      font-size:12px; color:var(--muted);
      padding:6px 10px; border-radius:999px;
      border:1px solid rgba(34,48,95,.9);
      background: rgba(10,16,40,.55);
      font-family: var(--mono);
    }
    .fc-front{
      font-size:20px; font-weight:850; letter-spacing:.2px; margin:10px 0 6px;
      line-height:1.18;
    }
    .fc-back{
      margin:12px 0 0;
      color:var(--text);
      font-size:14px;
      line-height:1.45;
      display:none;
      white-space:pre-wrap;
    }
    .fc-hint{
      margin-top:10px;
      color: var(--muted);
      font-size:12px;
    }
    .fc-controls{display:flex; gap:10px; flex-wrap:wrap; align-items:center}
    .known{
      display:flex; gap:10px; align-items:center; flex-wrap:wrap;
      padding:10px 12px;
      border-radius: 14px;
      border:1px solid rgba(34,48,95,.9);
      background: rgba(10,16,40,.4);
    }
    .toggle{
      display:flex; align-items:center; gap:10px;
    }
    .toggle input{transform: scale(1.1)}
    .tag{
      font-size:12px; padding:5px 10px; border-radius:999px;
      border:1px solid rgba(34,48,95,.9);
      background: rgba(10,16,40,.55);
      color:var(--muted);
    }
    .tag.good{border-color: rgba(61,220,151,.35); background: rgba(61,220,151,.10); color: rgba(199,255,231,.95)}
    .tag.bad{border-color: rgba(255,107,122,.35); background: rgba(255,107,122,.10); color: rgba(255,220,225,.98)}
    /* Test */
    .q{
      padding:14px 14px;
      border-radius: 16px;
      border:1px solid rgba(34,48,95,.9);
      background: rgba(10,16,40,.45);
      margin-bottom:12px;
    }
    .q .meta{display:flex; justify-content:space-between; gap:10px; flex-wrap:wrap; color:var(--muted); font-size:12px}
    .q .prompt{margin:10px 0 12px; font-size:14px; line-height:1.35}
    .choices{display:grid; gap:10px}
    .choice{
      cursor:pointer;
      padding:10px 12px;
      border-radius: 12px;
      border:1px solid rgba(34,48,95,.9);
      background: rgba(10,16,40,.35);
      transition:.12s ease;
      user-select:none;
    }
    .choice:hover{border-color: rgba(122,162,255,.55)}
    .choice.selected{border-color: rgba(122,162,255,.75); background: rgba(122,162,255,.14)}
    .choice.correct{border-color: rgba(61,220,151,.75); background: rgba(61,220,151,.14)}
    .choice.wrong{border-color: rgba(255,107,122,.75); background: rgba(255,107,122,.14)}
    .result{
      padding:12px 14px;
      border-radius: 14px;
      border:1px solid rgba(34,48,95,.9);
      background: rgba(10,16,40,.45);
      display:none;
    }
    .result strong{font-size:14px}
    .muted{color:var(--muted)}
    .footer-note{margin-top:12px; font-size:12px; color:var(--muted)}
    .split{display:flex; gap:10px; flex-wrap:wrap; align-items:center; justify-content:space-between}
    .kpi{
      display:grid; grid-template-columns: 1fr 1fr; gap:10px;
    }
    .kpi .box{
      padding:12px; border-radius: 16px;
      border:1px solid rgba(34,48,95,.9);
      background: rgba(10,16,40,.40);
    }
    .kpi .box .big{font-size:18px; font-weight:900}
    .kpi .box .small{font-size:12px; color:var(--muted)}
    .hide{display:none !important}
    .mini{font-size:12px;color:var(--muted)}
  </style>
</head>

<body>
  <div class="wrap">
    <header>
      <div class="brand">
        <h1>History Study</h1>
        <p>Flashcards + Practice Test (auto-saves progress in your browser). Use Search + filters to focus your studying.</p>
      </div>
      <div class="tabs" role="tablist" aria-label="Modes">
        <button class="tab active" data-view="flashcards" role="tab" aria-selected="true">Flashcards</button>
        <button class="tab" data-view="test" role="tab" aria-selected="false">Practice Test</button>
        <button class="tab" data-view="manage" role="tab" aria-selected="false">Manage Cards</button>
      </div>
    </header>

    <div class="grid">
      <!-- Main -->
      <section class="card" id="mainCard">
        <div class="head">
          <div>
            <h2 id="mainTitle">Flashcards</h2>
            <div class="sub" id="mainSub">Flip, shuffle, and track what you know.</div>
          </div>
          <div class="row">
            <span class="pill" id="deckInfo">Deck: 0 cards</span>
            <span class="pill" id="modeInfo">Mode: Flashcards</span>
          </div>
        </div>
        <div class="body">
          <!-- Flashcards View -->
          <div id="view-flashcards">
            <div class="flashcard">
              <div class="fc-stage" id="fcStage">
                <div class="fc-badge" id="fcBadge">Card 0 / 0</div>
                <div class="fc-front" id="fcFront">—</div>
                <div class="fc-back" id="fcBack">—</div>
                <div class="fc-hint" id="fcHint">Tip: press <span class="pill">Space</span> to flip, <span class="pill">←</span>/<span class="pill">→</span> to move.</div>
              </div>

              <div class="fc-controls">
                <button class="btn secondary" id="prevBtn">← Prev</button>
                <button class="btn" id="flipBtn">Flip</button>
                <button class="btn secondary" id="nextBtn">Next →</button>
                <button class="btn secondary" id="shuffleBtn">Shuffle</button>
                <button class="btn danger" id="resetProgressBtn">Reset Progress</button>
              </div>

              <div class="known">
                <div class="toggle">
                  <input type="checkbox" id="knownToggle" />
                  <label for="knownToggle" style="margin:0;color:var(--text);font-weight:750">Mark as “Known”</label>
                </div>
                <span class="tag" id="categoryTag">Category: —</span>
                <span class="tag" id="knownTag">Status: —</span>
              </div>

              <div>
                <div class="split">
                  <div class="mini">Known progress</div>
                  <div class="mini"><span id="knownCount">0</span>/<span id="totalCount">0</span> known</div>
                </div>
                <div class="progress" aria-label="Progress">
                  <div class="bar" id="progressBar"></div>
                </div>
              </div>
            </div>
          </div>

          <!-- Test View -->
          <div id="view-test" class="hide">
            <div class="row" style="margin-bottom:12px">
              <button class="btn" id="startTestBtn">Start New Test</button>
              <button class="btn secondary" id="gradeTestBtn">Grade</button>
              <button class="btn secondary" id="newSetBtn">New Questions</button>
            </div>

            <div class="inputs">
              <div>
                <label for="testCount"># of Questions</label>
                <select id="testCount">
                  <option>5</option>
                  <option selected>10</option>
                  <option>15</option>
                  <option>20</option>
                  <option>25</option>
                </select>
              </div>
              <div>
                <label for="testType">Question Type</label>
                <select id="testType">
                  <option value="mcq" selected>Multiple Choice (term → definition)</option>
                  <option value="tf">True / False (facts)</option>
                  <option value="mixed">Mixed</option>
                </select>
              </div>
            </div>

            <div class="hr"></div>

            <div id="testArea"></div>

            <div class="result" id="testResult">
              <div class="split">
                <strong id="scoreLine">Score: —</strong>
                <span class="pill" id="scoreDetail">—</span>
              </div>
              <div class="footer-note" id="reviewNote">Review missed questions below (they’ll be highlighted).</div>
            </div>
          </div>

          <!-- Manage View -->
          <div id="view-manage" class="hide">
            <div class="inputs">
              <div>
                <label for="searchInput">Search (term/definition)</label>
                <input id="searchInput" type="text" placeholder="Search… e.g., ‘Odyssey’ or ‘World War’" />
              </div>
              <div>
                <label for="categoryFilter">Category</label>
                <select id="categoryFilter">
                  <option value="all" selected>All</option>
                </select>
              </div>
            </div>

            <div class="inputs">
              <div>
                <label for="knownFilter">Known filter</label>
                <select id="knownFilter">
                  <option value="all" selected>All</option>
                  <option value="known">Known only</option>
                  <option value="unknown">Unknown only</option>
                </select>
              </div>
              <div class="row" style="align-items:flex-end">
                <button class="btn secondary" id="exportBtn">Export JSON</button>
                <button class="btn secondary" id="importBtn">Import JSON</button>
                <input id="importFile" type="file" accept="application/json" class="hide" />
              </div>
            </div>

            <div class="hr"></div>

            <div class="kpi">
              <div class="box">
                <div class="big" id="kpiTotal">0</div>
                <div class="small">Total cards</div>
              </div>
              <div class="box">
                <div class="big" id="kpiKnown">0</div>
                <div class="small">Known cards</div>
              </div>
            </div>

            <div class="hr"></div>

            <div>
              <label for="addCategory">Add / Edit Card</label>
              <div class="inputs">
                <div>
                  <label for="addCategory">Category</label>
                  <input id="addCategory" type="text" placeholder="e.g., U.S. History" />
                </div>
                <div>
                  <label for="addTerm">Term</label>
                  <input id="addTerm" type="text" placeholder="e.g., War of 1812" />
                </div>
              </div>
              <div style="margin-top:10px">
                <label for="addDef">Definition</label>
                <textarea id="addDef" placeholder="Definition…"></textarea>
              </div>
              <div class="row" style="margin-top:10px">
                <button class="btn good" id="saveCardBtn">Save Card</button>
                <button class="btn secondary" id="clearFormBtn">Clear</button>
                <button class="btn danger" id="deleteCardBtn">Delete (by exact term)</button>
              </div>
              <div class="footer-note">Tip: To edit an existing card, type the exact Term and hit Save.</div>
            </div>

            <div class="hr"></div>

            <div id="listArea"></div>
          </div>
        </div>
      </section>

      <!-- Sidebar -->
      <aside class="card">
        <div class="head">
          <div>
            <h2>Study Settings</h2>
            <div class="sub">Customize what shows up.</div>
          </div>
        </div>
        <div class="body">
          <div>
            <label for="studyCategory">Study category</label>
            <select id="studyCategory">
              <option value="all" selected>All</option>
            </select>
          </div>

          <div style="margin-top:10px">
            <label for="studyScope">Study scope</label>
            <select id="studyScope">
              <option value="all" selected>All cards</option>
              <option value="unknown">Unknown only</option>
              <option value="known">Known only</option>
            </select>
          </div>

          <div class="hr"></div>

          <div class="row">
            <button class="btn secondary small" id="jumpRandomBtn">Random Card</button>
            <button class="btn secondary small" id="showAnswerBtn">Show Answer</button>
          </div>

          <div class="hr"></div>

          <div class="mini">
            Keyboard:
            <div class="footer-note">
              <span class="pill">Space</span> flip &nbsp;
              <span class="pill">←</span>/<span class="pill">→</span> prev/next &nbsp;
              <span class="pill">K</span> toggle known
            </div>
          </div>

          <div class="hr"></div>

          <div class="mini">
            Data is stored locally (your browser). Use Export/Import in “Manage Cards” to move it to another device.
          </div>
        </div>
      </aside>
    </div>
  </div>

  <script>
    /**************
     * DEFAULT DECK
     **************/
    const DEFAULT_CARDS = [
      // Terms/Definitions
      { category:"U.S. History", term:"War of 1812", definition:"A conflict fought between the United States and Great Britain from 1812 to 1815 over trade restrictions, British impressment of American sailors, and U.S. expansion." },
      { category:"Ancient History", term:"The Odyssey", definition:"An ancient Greek epic poem attributed to Homer that follows the hero Odysseus on his long journey home after the Trojan War." },
      { category:"Soviet History", term:"Glasnost", definition:"A Soviet policy introduced by Mikhail Gorbachev in the 1980s that promoted openness, transparency, and freedom of information in government." },
      { category:"U.S. History", term:"George Washington", definition:"The first president of the United States and commander of the Continental Army during the American Revolutionary War." },
      { category:"Latin American / European", term:"Falklands War", definition:"A brief war fought in 1982 between Argentina and the United Kingdom over control of the Falkland Islands in the South Atlantic." },
      { category:"European History", term:"Bohemia", definition:"A historical region in Central Europe, now part of the Czech Republic, that played an important role in European political and cultural history." },
      { category:"U.S. History", term:"Apollo Program", definition:"A U.S. space program run by NASA that successfully landed the first humans on the Moon in 1969." },
      { category:"Economics", term:"Milton Friedman", definition:"An influential American economist who promoted free-market capitalism and limited government intervention in the economy." },
      { category:"European History", term:"Volodymyr Zelenskyy", definition:"The president of Ukraine, elected in 2019, who has led the country during the Russo-Ukrainian War." },
      { category:"Canadian History", term:"Quiet Revolution", definition:"A period of rapid social, political, and economic change in Quebec during the 1960s that reduced the influence of the Catholic Church." },
      { category:"European History", term:"Battle of Borodino", definition:"A major battle fought in 1812 between Napoleon’s army and Russian forces during Napoleon’s invasion of Russia." },
      { category:"Asian History", term:"Mao Zedong", definition:"The communist leader who founded the People’s Republic of China in 1949 and ruled until his death in 1976." },
      { category:"U.S. History", term:"Five Civilized Tribes", definition:"A term used to describe five Native American tribes (Cherokee, Chickasaw, Choctaw, Creek, and Seminole) that adopted aspects of European-American culture." },
      { category:"Ancient History", term:"Cyrus the Great", definition:"The founder of the Persian Empire, known for his military conquests and policies of religious tolerance." },
      { category:"European / Asian History", term:"Marco Polo", definition:"A Venetian explorer who traveled to Asia in the 13th century and helped increase European knowledge of China." },
      { category:"U.S. History", term:"Iran Hostage Crisis", definition:"A 1979–1981 event in which Iranian militants held 52 American diplomats hostage in Tehran." },
      { category:"European History", term:"René Descartes", definition:"A French philosopher and mathematician often considered the father of modern philosophy, famous for the phrase “I think, therefore I am.”" },
      { category:"U.S. History", term:"Zoot Suit Riots", definition:"A series of violent clashes in Los Angeles in 1943 between U.S. servicemen and Mexican American youths." },
      { category:"European History", term:"Brexit", definition:"The United Kingdom’s withdrawal from the European Union, formally completed in 2020." },
      { category:"Asian History", term:"Lee Kuan Yew", definition:"The first prime minister of Singapore, credited with transforming the country into a major global economic power." },
      { category:"European History", term:"Archduke Franz Ferdinand", definition:"The heir to the Austro-Hungarian throne whose assassination in 1914 sparked World War I." },
      { category:"Oceania History", term:"Musket Wars", definition:"A series of conflicts in New Zealand between Māori tribes during the early 19th century, intensified by the introduction of firearms." },
      { category:"European History", term:"Estado Novo", definition:"An authoritarian political regime that ruled Portugal from the 1930s to the 1970s under António Salazar." },
      { category:"European History", term:"French Revolution", definition:"A major political and social upheaval in France from 1789 to 1799 that led to the overthrow of the monarchy." },
      { category:"World History", term:"Great Depression", definition:"A worldwide economic downturn beginning in 1929 that caused massive unemployment and poverty." },
      { category:"Latin American History", term:"Juan Ponce de León", definition:"A Spanish explorer who explored Florida and is often associated with the legend of the Fountain of Youth." },
      { category:"World History", term:"NATO", definition:"The North Atlantic Treaty Organization, a military alliance formed in 1949 for collective defense." },
      { category:"U.S. History", term:"Watergate Scandal", definition:"A political scandal in the 1970s that led to the resignation of U.S. President Richard Nixon." },
      { category:"Ancient History", term:"Peloponnesian War", definition:"A long conflict between Athens and Sparta fought from 431 to 404 BCE in ancient Greece." },
      { category:"World Religions", term:"Buddhism", definition:"A major world religion founded by Siddhartha Gautama (the Buddha) that teaches the path to enlightenment through ethical living and meditation." },

      // Fact statements (for True/False pool)
      { category:"African History (Fact)", term:"FACT: Johannesburg gold discovery", definition:"The discovery of gold in the Witwatersrand Basin in South Africa sparked the growth of Johannesburg.", fact:true },
      { category:"African History (Fact)", term:"FACT: Menelik II Ethiopia", definition:"The Ethiopian Empire was extended to its greatest extent under Menelik II.", fact:true },
      { category:"Asian History (Fact)", term:"FACT: Hammurabi code", definition:"While ruling Babylonia, Hammurabi established the earliest and most complete written legal code.", fact:true },
      { category:"Asian History (Fact)", term:"FACT: Indira Gandhi", definition:"Indira Gandhi was the first female prime minister of India.", fact:true },
      { category:"Asian History (Fact)", term:"FACT: Bongbong Marcos", definition:"The current president of the Philippines, Bongbong Marcos, is the son of the country’s former dictator, Ferdinand Marcos.", fact:true },
      { category:"Asian History (Fact)", term:"FACT: Silk Road", definition:"An ancient trade network between China and the West was defined by the trading of silk.", fact:true },
      { category:"Asian History (Fact)", term:"FACT: Baghdad Abbasids", definition:"The city of Baghdad served as the capital of the Abbasid Caliphate.", fact:true },
      { category:"Ancient History (Fact)", term:"FACT: Sparta training", definition:"Sparta was a powerful city-state in Greece where boys began military training at age seven.", fact:true },
      { category:"Ancient History (Fact)", term:"FACT: Alexander succeeds Philip", definition:"In 336 BCE, Alexander the Great succeeded his father, Philip II, as King of Macedon.", fact:true },
      { category:"European History (Fact)", term:"FACT: Constantine converts", definition:"Constantine the Great was the first Roman emperor to convert to Christianity.", fact:true },
      { category:"European History (Fact)", term:"FACT: Henry VIII break", definition:"Henry VIII broke from the Catholic Church to annul his marriage to Catherine of Aragon in order to marry Anne Boleyn.", fact:true },
      { category:"European History (Fact)", term:"FACT: Battle of the Bulge", definition:"During World War II, the final major German offensive in the West occurred at the Battle of the Bulge, fought mostly in Belgium and Luxembourg in December 1944.", fact:true },
      { category:"European History (Fact)", term:"FACT: Russo-Ukrainian War", definition:"The Russo-Ukrainian War has been the deadliest war in Europe since World War II.", fact:true },
      { category:"European History (Fact)", term:"FACT: Leonardo works", definition:"Leonardo da Vinci created notable Italian Renaissance artworks such as Mona Lisa and The Last Supper.", fact:true },
      { category:"European History (Fact)", term:"FACT: Churchill leads UK", definition:"During World War II, the United Kingdom was led by Prime Minister Winston Churchill.", fact:true },
      { category:"European History (Fact)", term:"FACT: Frederick the Great", definition:"Frederick the Great’s nearly 50-year reign in the 18th century led to the emergence of Prussia as a major European power.", fact:true },
      { category:"European History (Fact)", term:"FACT: Martin Luther theses", definition:"Martin Luther sparked the Protestant Reformation after criticizing the sale of indulgences in his Ninety-five Theses.", fact:true },
      { category:"Latin American History (Fact)", term:"FACT: Juan Perón", definition:"Juan Perón was the president of Argentina from 1946 until his overthrow in 1955 and again from 1973 until his death in 1974.", fact:true },
      { category:"Latin American History (Fact)", term:"FACT: Haiti revolt", definition:"Haiti was the site of a successful slave revolt against French colonial rule.", fact:true },
      { category:"U.S. History (Fact)", term:"FACT: Cuba & Spanish-American War", definition:"Cuba’s struggle for independence sparked the Spanish-American War.", fact:true },
      { category:"U.S. History (Fact)", term:"FACT: Jefferson authored DOI", definition:"The Declaration of Independence was principally authored by Thomas Jefferson.", fact:true },
      { category:"U.S. History (Fact)", term:"FACT: Obama slogans", definition:"Barack Obama ran with the primary slogans “Hope” and “Forward” during his two presidential campaigns.", fact:true },
      { category:"U.S. History (Fact)", term:"FACT: Hamilton musical", definition:"Lin-Manuel Miranda’s 2015 musical, Hamilton, covers the life of Alexander Hamilton during the American Revolution; Hamilton was also the first Secretary of the Treasury.", fact:true },
    ];

    /*****************
     * STORAGE + STATE
     *****************/
    const STORAGE_KEY = "history_study_deck_v1";
    const PROGRESS_KEY = "history_study_known_v1";

    let cards = [];
    let knownSet = new Set();

    // study filters
    let studyCategory = "all";
    let studyScope = "all"; // all/known/unknown
    let filteredIndexes = [];
    let currentPos = 0;
    let showingBack = false;

    // test state
    let testQuestions = []; // {type, term, correct, choices[], selectedIndex, cardIndex}
    let testGraded = false;

    /************
     * UTILITIES
     ************/
    const $ = (id) => document.getElementById(id);
    const uniq = (arr) => Array.from(new Set(arr));
    const clamp = (n, a, b) => Math.max(a, Math.min(b, n));

    function saveDeck(){
      localStorage.setItem(STORAGE_KEY, JSON.stringify(cards));
    }
    function saveProgress(){
      localStorage.setItem(PROGRESS_KEY, JSON.stringify(Array.from(knownSet)));
    }
    function loadAll(){
      const stored = localStorage.getItem(STORAGE_KEY);
      cards = stored ? JSON.parse(stored) : structuredClone(DEFAULT_CARDS);

      const storedKnown = localStorage.getItem(PROGRESS_KEY);
      knownSet = new Set(storedKnown ? JSON.parse(storedKnown) : []);
    }

    function cardKey(card){
      // stable key: category + term (term should be unique in a typical study set)
      return `${card.category}::${card.term}`;
    }

    function isKnown(card){
      return knownSet.has(cardKey(card));
    }

    function setKnown(card, val){
      const k = cardKey(card);
      if(val) knownSet.add(k);
      else knownSet.delete(k);
      saveProgress();
    }

    function getCategories(){
      return uniq(cards.map(c => c.category)).sort((a,b)=>a.localeCompare(b));
    }

    function computeFiltered(){
      filteredIndexes = cards
        .map((c, idx) => ({c, idx}))
        .filter(({c}) => (studyCategory === "all" ? true : c.category === studyCategory))
        .filter(({c}) => {
          if(studyScope === "all") return true;
          return studyScope === "known" ? isKnown(c) : !isKnown(c);
        })
        .map(x => x.idx);

      if(filteredIndexes.length === 0){
        currentPos = 0;
      } else {
        currentPos = clamp(currentPos, 0, filteredIndexes.length - 1);
      }
    }

    function currentCard(){
      if(filteredIndexes.length === 0) return null;
      return cards[ filteredIndexes[currentPos] ];
    }

    function updateTopBadges(){
      $("deckInfo").textContent = `Deck: ${cards.length} cards`;
      const mode = document.querySelector(".tab.active")?.textContent?.trim() ?? "—";
      $("modeInfo").textContent = `Mode: ${mode}`;
    }

    /************
     * VIEW TABS
     ************/
    const views = ["flashcards","test","manage"];

    function setView(view){
      for(const v of views){
        $("view-" + v).classList.toggle("hide", v !== view);
      }
      document.querySelectorAll(".tab").forEach(btn => {
        const isActive = btn.dataset.view === view;
        btn.classList.toggle("active", isActive);
        btn.setAttribute("aria-selected", isActive ? "true" : "false");
      });

      if(view === "flashcards"){
        $("mainTitle").textContent = "Flashcards";
        $("mainSub").textContent = "Flip, shuffle, and track what you know.";
        renderFlashcard();
      } else if(view === "test"){
        $("mainTitle").textContent = "Practice Test";
        $("mainSub").textContent = "Multiple choice and/or True/False from your deck.";
        renderTestShell();
      } else {
        $("mainTitle").textContent = "Manage Cards";
        $("mainSub").textContent = "Search, filter, edit, export, and import.";
        renderManage();
      }
      updateTopBadges();
    }

    /****************
     * FLASHCARDS UI
     ****************/
    function renderFlashcard(){
      computeFiltered();
      const total = filteredIndexes.length;
      const card = currentCard();

      // reset display
      showingBack = false;
      $("fcBack").style.display = "none";
      $("fcFront").style.display = "block";

      $("totalCount").textContent = String(total);

      if(!card){
        $("fcFront").textContent = "No cards match your filters.";
        $("fcBack").textContent = "";
        $("fcBadge").textContent = `Card 0 / 0`;
        $("knownToggle").checked = false;
        $("categoryTag").textContent = "Category: —";
        $("knownTag").textContent = "Status: —";
        updateProgressBar();
        return;
      }

      $("fcFront").textContent = card.term;
      $("fcBack").textContent = card.definition;
      $("fcBadge").textContent = `Card ${currentPos + 1} / ${total}`;

      $("categoryTag").textContent = `Category: ${card.category}`;
      const k = isKnown(card);
      $("knownToggle").checked = k;
      $("knownTag").textContent = `Status: ${k ? "Known" : "Unknown"}`;
      $("knownTag").className = "tag " + (k ? "good" : "bad");

      updateProgressBar();
    }

    function flipCard(forceShowBack = null){
      const card = currentCard();
      if(!card) return;

      if(forceShowBack === null){
        showingBack = !showingBack;
      } else {
        showingBack = !!forceShowBack;
      }

      $("fcBack").style.display = showingBack ? "block" : "none";
      $("fcFront").style.display = showingBack ? "none" : "block";
    }

    function nextCard(){
      if(filteredIndexes.length === 0) return;
      currentPos = (currentPos + 1) % filteredIndexes.length;
      renderFlashcard();
    }

    function prevCard(){
      if(filteredIndexes.length === 0) return;
      currentPos = (currentPos - 1 + filteredIndexes.length) % filteredIndexes.length;
      renderFlashcard();
    }

    function shuffleFiltered(){
      // Fisher-Yates shuffle on filteredIndexes
      for(let i = filteredIndexes.length - 1; i > 0; i--){
        const j = Math.floor(Math.random() * (i + 1));
        [filteredIndexes[i], filteredIndexes[j]] = [filteredIndexes[j], filteredIndexes[i]];
      }
      currentPos = 0;
      renderFlashcard();
    }

    function updateProgressBar(){
      const total = filteredIndexes.length;
      if(total === 0){
        $("knownCount").textContent = "0";
        $("progressBar").style.width = "0%";
        return;
      }
      const known = filteredIndexes.reduce((acc, idx) => acc + (isKnown(cards[idx]) ? 1 : 0), 0);
      $("knownCount").textContent = String(known);
      $("progressBar").style.width = `${Math.round((known/total)*100)}%`;
    }

    /***************
     * PRACTICE TEST
     ***************/
    function renderTestShell(){
      $("testArea").innerHTML = "";
      $("testResult").style.display = "none";
      testQuestions = [];
      testGraded = false;
    }

    function getDeckForTest(){
      // use same filters as flashcards (studyCategory + studyScope)
      computeFiltered();
      return filteredIndexes.map(i => ({ card: cards[i], idx: i }));
    }

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

    function buildMCQQuestions(deckItems, count){
      // Only non-fact cards or include all? We'll allow all cards, but MCQ works best with "term -> definition"
      const pool = deckItems.filter(x => !!x.card.term && !!x.card.definition);
      const picked = pickRandom(pool, count);
      const allDefs = pool.map(x => x.card.definition);

      return picked.map(({card, idx}, qIndex) => {
        const correct = card.definition;
        // create 3 distractors
        const distractors = pickRandom(allDefs.filter(d => d !== correct), 3);
        const choices = pickRandom([correct, ...distractors], 4);

        return {
          type: "mcq",
          prompt: `What is the best definition of: "${card.term}"?`,
          choices,
          correctIndex: choices.indexOf(correct),
          selectedIndex: null,
          cardIndex: idx,
          label: `Q${qIndex+1}`
        };
      });
    }

    function buildTFQuestions(deckItems, count){
      // Prefer explicit fact cards, otherwise use definitions as "statements".
      const factPool = deckItems.filter(x => x.card.fact === true);
      const fallbackPool = deckItems;

      const pool = (factPool.length >= 4) ? factPool : fallbackPool;
      const picked = pickRandom(pool, count);

      return picked.map(({card, idx}, qIndex) => {
        // Make half false by swapping with another random statement (or negating)
        const makeFalse = Math.random() < 0.5;
        let statement = card.definition;
        let answer = true;

        if(makeFalse && pool.length > 1){
          const other = pickRandom(pool.filter(x => x.idx !== idx), 1)[0];
          // false statement: pair term context with mismatched statement
          // If it's an explicit fact, we can just use another fact statement.
          statement = other.card.definition;
          answer = false;
        }

        return {
          type: "tf",
          prompt: statement,
          choices: ["True", "False"],
          correctIndex: answer ? 0 : 1,
          selectedIndex: null,
          cardIndex: idx,
          label: `Q${qIndex+1}`
        };
      });
    }

    function startTest(){
      const count = parseInt($("testCount").value, 10);
      const kind = $("testType").value;

      const deckItems = getDeckForTest();
      $("testArea").innerHTML = "";
      $("testResult").style.display = "none";
      testGraded = false;

      if(deckItems.length === 0){
        $("testArea").innerHTML = `<div class="q"><div class="prompt">No cards match your filters. Change Study Settings.</div></div>`;
        testQuestions = [];
        return;
      }

      let qs = [];
      if(kind === "mcq"){
        qs = buildMCQQuestions(deckItems, count);
      } else if(kind === "tf"){
        qs = buildTFQuestions(deckItems, count);
      } else {
        // mixed
        const half = Math.floor(count/2);
        const mcq = buildMCQQuestions(deckItems, count - half);
        const tf = buildTFQuestions(deckItems, half);
        qs = pickRandom([...mcq, ...tf], count);
        // relabel
        qs.forEach((q,i)=> q.label = `Q${i+1}`);
      }

      testQuestions = qs;
      renderTestQuestions();
    }

    function renderTestQuestions(){
      const container = $("testArea");
      container.innerHTML = "";

      testQuestions.forEach((q, i) => {
        const el = document.createElement("div");
        el.className = "q";
        el.dataset.qindex = String(i);

        const meta = document.createElement("div");
        meta.className = "meta";
        meta.innerHTML = `<span>${q.label} • ${q.type.toUpperCase()}</span><span class="muted">Card #${q.cardIndex+1}</span>`;

        const prompt = document.createElement("div");
        prompt.className = "prompt";
        prompt.textContent = q.prompt;

        const choices = document.createElement("div");
        choices.className = "choices";

        q.choices.forEach((c, ci) => {
          const ch = document.createElement("div");
          ch.className = "choice";
          ch.textContent = c;
          ch.addEventListener("click", () => {
            if(testGraded) return;
            q.selectedIndex = ci;
            // update classes
            choices.querySelectorAll(".choice").forEach((node, idx) => {
              node.classList.toggle("selected", idx === ci);
            });
          });
          choices.appendChild(ch);
        });

        el.appendChild(meta);
        el.appendChild(prompt);
        el.appendChild(choices);
        container.appendChild(el);
      });
    }

    function gradeTest(){
      if(testQuestions.length === 0) return;

      testGraded = true;

      let correct = 0;

      testQuestions.forEach((q, i) => {
        const qEl = document.querySelector(`.q[data-qindex="${i}"]`);
        const nodes = Array.from(qEl.querySelectorAll(".choice"));

        nodes.forEach((node, idx) => {
          node.classList.remove("correct","wrong");
          if(idx === q.correctIndex) node.classList.add("correct");
          if(q.selectedIndex !== null && idx === q.selectedIndex && q.selectedIndex !== q.correctIndex){
            node.classList.add("wrong");
          }
        });

        if(q.selectedIndex === q.correctIndex) correct++;
      });

      const total = testQuestions.length;
      const pct = Math.round((correct/total)*100);
      $("scoreLine").textContent = `Score: ${correct} / ${total} (${pct}%)`;
      $("scoreDetail").textContent = pct >= 80 ? "Nice work" : pct >= 60 ? "Keep going" : "More review needed";
      $("testResult").style.display = "block";
      $("testResult").scrollIntoView({behavior:"smooth", block:"start"});
    }

    /*****************
     * MANAGE / SEARCH
     *****************/
    function populateCategorySelects(){
      const cats = getCategories();

      // sidebar selects
      const sc = $("studyCategory");
      const cf = $("categoryFilter");

      // preserve current selections where possible
      const prevSC = sc.value || "all";
      const prevCF = cf.value || "all";

      sc.innerHTML = `<option value="all">All</option>` + cats.map(c => `<option value="${escapeHtml(c)}">${escapeHtml(c)}</option>`).join("");
      cf.innerHTML = `<option value="all">All</option>` + cats.map(c => `<option value="${escapeHtml(c)}">${escapeHtml(c)}</option>`).join("");

      sc.value = cats.includes(prevSC) ? prevSC : "all";
      cf.value = cats.includes(prevCF) ? prevCF : "all";
    }

    function renderManage(){
      populateCategorySelects();

      // KPI
      $("kpiTotal").textContent = String(cards.length);
      const knownCount = cards.reduce((a,c)=>a+(isKnown(c)?1:0),0);
      $("kpiKnown").textContent = String(knownCount);

      // list
      const q = ($("searchInput").value || "").trim().toLowerCase();
      const cat = $("categoryFilter").value;
      const kf = $("knownFilter").value;

      const items = cards
        .map((c, idx) => ({c, idx}))
        .filter(({c}) => cat === "all" ? true : c.category === cat)
        .filter(({c}) => {
          if(kf === "all") return true;
          return kf === "known" ? isKnown(c) : !isKnown(c);
        })
        .filter(({c}) => {
          if(!q) return true;
          return (c.term + " " + c.definition).toLowerCase().includes(q);
        });

      const list = $("listArea");
      list.innerHTML = "";

      if(items.length === 0){
        list.innerHTML = `<div class="q"><div class="prompt">No matches.</div></div>`;
        return;
      }

      items.slice(0, 80).forEach(({c, idx}) => {
        const box = document.createElement("div");
        box.className = "q";

        const meta = document.createElement("div");
        meta.className = "meta";
        meta.innerHTML = `<span>${escapeHtml(c.category)}</span><span class="muted">${isKnown(c) ? "Known" : "Unknown"}</span>`;

        const prompt = document.createElement("div");
        prompt.className = "prompt";
        prompt.innerHTML = `<strong>${escapeHtml(c.term)}</strong><div class="muted" style="margin-top:6px;white-space:pre-wrap">${escapeHtml(c.definition)}</div>`;

        const actions = document.createElement("div");
        actions.className = "row";
        actions.style.marginTop = "10px";
        actions.innerHTML = `
          <button class="btn secondary small">Study This</button>
          <button class="btn secondary small">${isKnown(c) ? "Mark Unknown" : "Mark Known"}</button>
        `;

        actions.children[0].addEventListener("click", () => {
          // set filters to this category and jump to this card in flashcards
          studyCategory = c.category;
          $("studyCategory").value = studyCategory;
          studyScope = "all";
          $("studyScope").value = studyScope;

          computeFiltered();
          const pos = filteredIndexes.indexOf(idx);
          currentPos = pos >= 0 ? pos : 0;

          setView("flashcards");
          renderFlashcard();
        });

        actions.children[1].addEventListener("click", () => {
          setKnown(c, !isKnown(c));
          renderManage();
          renderFlashcard();
        });

        box.appendChild(meta);
        box.appendChild(prompt);
        box.appendChild(actions);
        list.appendChild(box);
      });

      if(items.length > 80){
        const more = document.createElement("div");
        more.className = "footer-note";
        more.textContent = `Showing first 80 of ${items.length} matches. Use search/filter to narrow.`;
        list.appendChild(more);
      }
    }

    function saveCardFromForm(){
      const cat = $("addCategory").value.trim();
      const term = $("addTerm").value.trim();
      const def = $("addDef").value.trim();

      if(!cat || !term || !def){
        alert("Please fill Category, Term, and Definition.");
        return;
      }

      const existingIndex = cards.findIndex(c => c.term === term);
      if(existingIndex >= 0){
        // keep known key consistent (category might change)
        const old = cards[existingIndex];
        const wasKnown = isKnown(old);

        cards[existingIndex] = { category: cat, term, definition: def };
        // migrate known key if needed
        if(wasKnown){
          knownSet.delete(cardKey(old));
          knownSet.add(cardKey(cards[existingIndex]));
          saveProgress();
        }
      } else {
        cards.push({ category: cat, term, definition: def });
      }

      saveDeck();
      populateCategorySelects();
      renderManage();
      renderFlashcard();
      clearForm();
    }

    function deleteCardByTerm(){
      const term = $("addTerm").value.trim();
      if(!term){
        alert("Type the exact Term to delete.");
        return;
      }
      const idx = cards.findIndex(c => c.term === term);
      if(idx < 0){
        alert("No card found with that exact Term.");
        return;
      }
      const c = cards[idx];
      knownSet.delete(cardKey(c));
      cards.splice(idx, 1);
      saveDeck(); saveProgress();
      populateCategorySelects();
      renderManage();
      renderFlashcard();
      clearForm();
    }

    function clearForm(){
      $("addCategory").value = "";
      $("addTerm").value = "";
      $("addDef").value = "";
    }

    function exportJSON(){
      const payload = {
        version: 1,
        exportedAt: new Date().toISOString(),
        cards,
        knownKeys: Array.from(knownSet),
      };
      const blob = new Blob([JSON.stringify(payload, null, 2)], {type:"application/json"});
      const url = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = url;
      a.download = "history-study-deck.json";
      a.click();
      URL.revokeObjectURL(url);
    }

    function importJSONFile(file){
      const reader = new FileReader();
      reader.onload = () => {
        try{
          const data = JSON.parse(reader.result);
          if(!data || !Array.isArray(data.cards)) throw new Error("Invalid JSON format.");
          cards = data.cards;
          knownSet = new Set(Array.isArray(data.knownKeys) ? data.knownKeys : []);
          saveDeck(); saveProgress();
          populateCategorySelects();
          renderManage();
          renderFlashcard();
          alert("Imported successfully!");
        } catch(e){
          alert("Import failed: " + e.message);
        }
      };
      reader.readAsText(file);
    }

    function escapeHtml(str){
      return String(str)
        .replaceAll("&","&amp;")
        .replaceAll("<","&lt;")
        .replaceAll(">","&gt;")
        .replaceAll('"',"&quot;")
        .replaceAll("'","&#039;");
    }

    /************
     * WIRING UP
     ************/
    function bindEvents(){
      // tabs
      document.querySelectorAll(".tab").forEach(btn => {
        btn.addEventListener("click", () => setView(btn.dataset.view));
      });

      // flashcards controls
      $("flipBtn").addEventListener("click", () => flipCard());
      $("prevBtn").addEventListener("click", prevCard);
      $("nextBtn").addEventListener("click", nextCard);
      $("shuffleBtn").addEventListener("click", shuffleFiltered);
      $("resetProgressBtn").addEventListener("click", () => {
        if(confirm("Reset known/unknown progress?")){
          knownSet = new Set();
          saveProgress();
          renderFlashcard();
          renderManage();
        }
      });
      $("knownToggle").addEventListener("change", (e) => {
        const c = currentCard();
        if(!c) return;
        setKnown(c, e.target.checked);
        renderFlashcard();
        renderManage();
      });

      $("fcStage").addEventListener("click", () => flipCard());

      // sidebar settings
      $("studyCategory").addEventListener("change", (e) => {
        studyCategory = e.target.value;
        currentPos = 0;
        renderFlashcard();
      });
      $("studyScope").addEventListener("change", (e) => {
        studyScope = e.target.value;
        currentPos = 0;
        renderFlashcard();
      });
      $("jumpRandomBtn").addEventListener("click", () => {
        computeFiltered();
        if(filteredIndexes.length === 0) return;
        currentPos = Math.floor(Math.random() * filteredIndexes.length);
        renderFlashcard();
      });
      $("showAnswerBtn").addEventListener("click", () => flipCard(true));

      // test controls
      $("startTestBtn").addEventListener("click", startTest);
      $("newSetBtn").addEventListener("click", startTest);
      $("gradeTestBtn").addEventListener("click", gradeTest);

      // manage controls
      $("searchInput").addEventListener("input", renderManage);
      $("categoryFilter").addEventListener("change", renderManage);
      $("knownFilter").addEventListener("change", renderManage);

      $("saveCardBtn").addEventListener("click", saveCardFromForm);
      $("clearFormBtn").addEventListener("click", clearForm);
      $("deleteCardBtn").addEventListener("click", deleteCardByTerm);

      $("exportBtn").addEventListener("click", exportJSON);
      $("importBtn").addEventListener("click", () => $("importFile").click());
      $("importFile").addEventListener("change", (e) => {
        const f = e.target.files?.[0];
        if(f) importJSONFile(f);
        e.target.value = "";
      });

      // keyboard shortcuts
      window.addEventListener("keydown", (e) => {
        const activeView = document.querySelector(".tab.active")?.dataset.view;
        if(activeView !== "flashcards") return;

        if(e.code === "Space"){
          e.preventDefault();
          flipCard();
        } else if(e.code === "ArrowRight"){
          nextCard();
        } else if(e.code === "ArrowLeft"){
          prevCard();
        } else if(e.key.toLowerCase() === "k"){
          const c = currentCard();
          if(!c) return;
          const newVal = !isKnown(c);
          setKnown(c, newVal);
          $("knownToggle").checked = newVal;
          renderFlashcard();
          renderManage();
        }
      });
    }

    /************
     * INIT
     ************/
    (function init(){
      loadAll();
      populateCategorySelects();
      studyCategory = "all";
      studyScope = "all";
      $("studyCategory").value = "all";
      $("studyScope").value = "all";

      updateTopBadges();
      bindEvents();
      setView("flashcards");
      renderManage(); // keep KPI/list ready
    })();
  </script>
</body>
</html>
Editor is loading...
Leave a Comment