| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Etymology Graph Explorer</title> |
| <link rel="preconnect" href="https://fonts.googleapis.com"> |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| <link href="https://fonts.googleapis.com/css2?family=EB+Garamond:ital,wght@0,400;0,600;1,400&display=swap" rel="stylesheet"> |
| <link rel="stylesheet" href="styles.css?v=60"> |
| <script src="https://unpkg.com/cytoscape@3.28.1/dist/cytoscape.min.js"></script> |
| <script src="https://unpkg.com/dagre@0.8.5/dist/dagre.min.js"></script> |
| <script src="https://unpkg.com/cytoscape-dagre@2.5.0/cytoscape-dagre.js"></script> |
| </head> |
| <body> |
| <main> |
| <header> |
| <h1>Etymology Explorer</h1> |
| <p class="subtitle">Trace the origins of words through time</p> |
| <div class="header-buttons"> |
| |
| <a href="https://github.com/lucharo/etymology-for-all/issues/new/choose" target="_blank" rel="noopener" class="header-btn desktop-only" title="Report an issue"> |
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| <circle cx="12" cy="12" r="10"></circle> |
| <line x1="12" y1="8" x2="12" y2="12"></line> |
| <line x1="12" y1="16" x2="12.01" y2="16"></line> |
| </svg> |
| <span class="btn-label">Issue</span> |
| </a> |
| <button id="about-btn" class="header-btn desktop-only" title="About this project"> |
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| <circle cx="12" cy="12" r="10"></circle> |
| <path d="M12 16v-4"></path> |
| <path d="M12 8h.01"></path> |
| </svg> |
| <span class="btn-label">About</span> |
| </button> |
| <div class="settings-wrapper desktop-only"> |
| <button id="settings-btn" class="header-btn" title="Settings" aria-label="Settings"> |
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| <path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"></path> |
| <circle cx="12" cy="12" r="3"></circle> |
| </svg> |
| </button> |
| <div id="settings-popover" class="settings-popover hidden"> |
| <label class="settings-option" title="Include compound words in random selection and graph display"> |
| <input type="checkbox" id="include-compound" checked> |
| <span>Include compound words</span> |
| </label> |
| <label class="settings-option" title="Color edges by link type (inherited, borrowed, derived, cognate)"> |
| <input type="checkbox" id="show-link-types"> |
| <span>Show link types</span> |
| </label> |
| </div> |
| </div> |
| |
| <div class="mobile-menu-wrapper mobile-only"> |
| <button id="mobile-menu-btn" class="header-btn" title="Menu" aria-label="Menu"> |
| <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| <circle cx="5" cy="12" r="1"></circle> |
| <circle cx="12" cy="12" r="1"></circle> |
| <circle cx="19" cy="12" r="1"></circle> |
| </svg> |
| </button> |
| <div id="mobile-menu" class="mobile-menu hidden"> |
| <button id="mobile-about-btn" class="mobile-menu-item mobile-about-link">About ›</button> |
| <div class="mobile-menu-divider"></div> |
| <label class="mobile-menu-item settings-option" title="Include compound words"> |
| <input type="checkbox" id="mobile-include-compound" checked> |
| <span>Include compound words</span> |
| </label> |
| <label class="mobile-menu-item settings-option" title="Color edges by link type"> |
| <input type="checkbox" id="mobile-show-link-types"> |
| <span>Show link types</span> |
| </label> |
| <div class="mobile-menu-divider"></div> |
| <a href="https://github.com/lucharo/etymology-for-all/issues/new/choose" target="_blank" rel="noopener" class="mobile-menu-item mobile-external-link">Report an issue <span class="external-icon">↗</span></a> |
| </div> |
| </div> |
| </div> |
| </header> |
|
|
| <div class="search-container"> |
| <div class="search-wrapper"> |
| <input |
| type="text" |
| id="word-input" |
| placeholder="Enter a word..." |
| autocomplete="off" |
| autofocus |
| > |
| <div id="suggestions" class="suggestions hidden"></div> |
| </div> |
| <button id="search-btn" title="Search"> |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| <circle cx="11" cy="11" r="8"></circle> |
| <path d="m21 21-4.3-4.3"></path> |
| </svg> |
| </button> |
| <button id="random-btn" title="Random word" aria-label="Random word"> |
| <span style="font-size: 1.3em; line-height: 1;">🎲</span> |
| </button> |
| </div> |
| <p class="search-hint">Click any word in the graph to see its definition</p> |
|
|
| |
| <div id="graph-backdrop" class="graph-backdrop"></div> |
|
|
| |
| <div id="graph-options" class="graph-options hidden"> |
| <div class="view-toggle"> |
| <button id="view-graph" class="view-btn active" title="Graph view"> |
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| <circle cx="12" cy="12" r="3"></circle> |
| <circle cx="19" cy="5" r="2"></circle> |
| <circle cx="5" cy="19" r="2"></circle> |
| <circle cx="5" cy="5" r="2"></circle> |
| <line x1="12" y1="9" x2="12" y2="5"></line> |
| <line x1="6.5" y1="17.5" x2="9.5" y2="14.5"></line> |
| <line x1="17.5" y1="6.5" x2="14.5" y2="9.5"></line> |
| </svg> |
| Graph |
| </button> |
| <button id="view-tree" class="view-btn" title="Tree view"> |
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| <path d="M12 3v18"></path> |
| <path d="M5 8h14"></path> |
| <path d="M5 16h14"></path> |
| </svg> |
| Tree |
| </button> |
| </div> |
| <div class="depth-control"> |
| <span class="depth-label">Depth:</span> |
| <button id="depth-minus" class="depth-btn" title="Decrease depth">−</button> |
| <span id="depth-value" class="depth-value">5</span> |
| <button id="depth-plus" class="depth-btn" title="Increase depth">+</button> |
| </div> |
| </div> |
|
|
| <div id="graph-container"> |
| |
| <button id="expand-btn" class="expand-btn hidden" title="Expand graph (Esc to minimize)"> |
| <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="expand-icon"> |
| <polyline points="15 3 21 3 21 9"></polyline> |
| <polyline points="9 21 3 21 3 15"></polyline> |
| <line x1="21" y1="3" x2="14" y2="10"></line> |
| <line x1="3" y1="21" x2="10" y2="14"></line> |
| </svg> |
| <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="minimize-icon"> |
| <polyline points="4 14 10 14 10 20"></polyline> |
| <polyline points="20 10 14 10 14 4"></polyline> |
| <line x1="14" y1="10" x2="21" y2="3"></line> |
| <line x1="3" y1="21" x2="10" y2="14"></line> |
| </svg> |
| </button> |
|
|
| <div id="loading" class="hidden"> |
| <div class="spinner"></div> |
| <span>Tracing etymology...</span> |
| </div> |
| <div id="empty-state"> |
| <p>Search for a word to see its etymological tree, or press <span style="font-size: 1.5em; vertical-align: middle; line-height: 1;">🎲</span> to get a random word</p> |
| </div> |
| <div id="error-state" class="hidden"> |
| <p id="error-message"></p> |
| <div id="error-actions" class="error-actions"></div> |
| </div> |
| <div id="cy"></div> |
|
|
| |
| <div id="tree-view" class="tree-view hidden"></div> |
|
|
| |
| <div class="graph-legend hidden" id="graph-legend"> |
| <div class="edge-legend" id="edge-legend-simple"> |
| <span class="legend-item"> |
| <span class="legend-line regular"></span> |
| <span>Etymology</span> |
| </span> |
| <span class="legend-item"> |
| <span class="legend-line compound"></span> |
| <span>Compound</span> |
| </span> |
| </div> |
| <div class="edge-legend hidden" id="edge-legend-detailed"> |
| <span class="legend-item"> |
| <span class="legend-line link-inh"></span> |
| <span>Inherited</span> |
| </span> |
| <span class="legend-item"> |
| <span class="legend-line link-bor"></span> |
| <span>Borrowed</span> |
| </span> |
| <span class="legend-item"> |
| <span class="legend-line link-der"></span> |
| <span>Derived</span> |
| </span> |
| <span class="legend-item"> |
| <span class="legend-line link-cog"></span> |
| <span>Cognate</span> |
| </span> |
| <span class="legend-item"> |
| <span class="legend-line link-cmpd"></span> |
| <span>Compound</span> |
| </span> |
| </div> |
| <span class="legend-divider">|</span> |
| <div id="direction-indicator"> |
| <span class="direction-label direction-recent">Recent</span> |
| <span class="direction-arrow">→</span> |
| <span class="direction-label direction-ancient">Ancient</span> |
| </div> |
| </div> |
|
|
| |
| <div id="node-detail" class="hidden"> |
| <button id="detail-close" title="Close" aria-label="Close"> |
| <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| <line x1="18" y1="6" x2="6" y2="18"></line> |
| <line x1="6" y1="6" x2="18" y2="18"></line> |
| </svg> |
| </button> |
| <div class="detail-header"> |
| <span id="detail-lang" class="detail-lang"></span> |
| <span id="detail-word" class="detail-word"></span> |
| </div> |
| <div class="detail-row"> |
| <span class="detail-label"> |
| Family |
| <span class="info-btn" role="button" tabindex="0">i<span class="tooltip">Language family tree showing historical relationships</span></span> |
| </span> |
| <span id="detail-family" class="detail-value"></span> |
| </div> |
| <div class="detail-row"> |
| <span class="detail-label">Meaning</span> |
| <span id="detail-sense" class="detail-value"></span> |
| </div> |
| </div> |
| </div> |
|
|
| <div id="word-info" class="hidden"> |
| <span id="current-word"></span> |
| <span class="info-divider"></span> |
| <div id="lang-breakdown"></div> |
| <button id="stats-toggle" class="stats-toggle" title="Toggle graph statistics"> |
| <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| <line x1="18" y1="20" x2="18" y2="10"></line> |
| <line x1="12" y1="20" x2="12" y2="4"></line> |
| <line x1="6" y1="20" x2="6" y2="14"></line> |
| </svg> |
| Graph Stats |
| </button> |
| </div> |
|
|
| |
| <div id="stats-panel" class="stats-panel hidden"> |
| <div class="stats-grid"> |
| <div class="stat-item"> |
| <span class="stat-value" id="stat-nodes">0</span> |
| <span class="stat-label">nodes</span> |
| </div> |
| <div class="stat-item"> |
| <span class="stat-value" id="stat-edges">0</span> |
| <span class="stat-label">edges</span> |
| </div> |
| <div class="stat-item"> |
| <span class="stat-value" id="stat-langs">0</span> |
| <span class="stat-label">languages</span> |
| </div> |
| <div class="stat-item"> |
| <span class="stat-value" id="stat-depth">0</span> |
| <span class="stat-label">depth</span> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="about-modal" class="modal hidden"> |
| <div class="modal-backdrop"></div> |
| <div class="modal-content"> |
| <button id="about-close" class="modal-close" aria-label="Close"> |
| <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> |
| <line x1="18" y1="6" x2="6" y2="18"></line> |
| <line x1="6" y1="6" x2="18" y2="18"></line> |
| </svg> |
| </button> |
|
|
| <div class="modal-tabs"> |
| <button class="modal-tab active" data-tab="about">About</button> |
| <button class="modal-tab" data-tab="glossary">Glossary</button> |
| <button class="modal-tab" data-tab="how">How It Works</button> |
| </div> |
|
|
| <div id="tab-about" class="tab-content active"> |
| <h2>Etymology for All</h2> |
| <p class="philosophy"> |
| Etymology should be a <strong>public good</strong>. |
| </p> |
| <p> |
| Many apps gatekeep etymological knowledge behind paywalls or proprietary databases. |
| We believe the history of language belongs to everyone. |
| </p> |
| <p> |
| This project exists to make word origins accessible, explorable, and free. |
| Every word has a story—discover where yours came from. |
| </p> |
| <h3>Data Sources</h3> |
| <p> |
| <strong>Etymology:</strong> <a href="https://github.com/clefourrier/EtymDB" target="_blank" rel="noopener">EtymDB 2.1</a>, |
| an open etymological database derived from Wiktionary containing 1.9 million words across 2,500+ languages. |
| </p> |
| <p class="citation"> |
| <a href="https://aclanthology.org/2020.lrec-1.392/" target="_blank" rel="noopener">Fourrier & Sagot (2020)</a>, "Methodological Aspects of Developing and Managing an Etymological Lexical Resource", LREC 2020 |
| </p> |
| <p> |
| <strong>Definitions:</strong> <a href="https://dictionaryapi.dev/" target="_blank" rel="noopener">Free Dictionary API</a>, |
| a community-driven dictionary service also sourced from Wiktionary. |
| </p> |
| </div> |
|
|
| <div id="tab-glossary" class="tab-content"> |
| <h2>Glossary</h2> |
| <h3>Etymology</h3> |
| <p> |
| The study of word origins and how their meanings have changed throughout history. |
| An etymology traces a word back through time to its earliest known form. |
| </p> |
| <h3>Ancestors</h3> |
| <p> |
| Words that a modern word <strong>inherited from</strong> or <strong>derived from</strong>. |
| These form the direct lineage of a word through time. |
| </p> |
| <p class="example"> |
| Example: English "mother" ← Old English "mōdor" ← Proto-Germanic "*mōdēr" ← Proto-Indo-European "*méh₂tēr" |
| </p> |
| <h3>Cognates</h3> |
| <p> |
| Words in <strong>different languages</strong> that share a common ancestor. |
| They evolved separately but have the same root. |
| </p> |
| <p class="example"> |
| Example: English "friend", German "Freund", Dutch "vriend", Gothic "frijōnds" |
| — all from Proto-Germanic "*frijōndz" |
| </p> |
| <h3>Compound Etymology</h3> |
| <p> |
| When a word is formed from <strong>multiple source words</strong> or morphemes combined together. |
| These are shown with blue edges in the graph. |
| </p> |
| <p class="example"> |
| Example: "uplander" = "upland" + "-er" (the suffix meaning "one who") |
| </p> |
| <h3>Morpheme</h3> |
| <p> |
| The smallest meaningful unit of language. Words are built from morphemes, |
| including roots, prefixes, and suffixes. |
| </p> |
| <p class="example"> |
| Example: "unhappiness" contains three morphemes: "un-" (not) + "happy" (root) + "-ness" (state of) |
| </p> |
| <h3>Graph Traversal</h3> |
| <p> |
| The graph follows etymology connections recursively up to 5 levels deep, |
| including both ancestors and cognates. |
| </p> |
| <p class="example"> |
| Example: "friend" connects to 12 words directly. Following each of those |
| recursively for 5 levels yields 28 total nodes in the graph. |
| </p> |
| <h3>Language Family</h3> |
| <p> |
| A group of languages descended from a common ancestral language. |
| </p> |
| <p class="example"> |
| Example: English, German, Dutch, and Swedish are all part of the <strong>Germanic</strong> branch |
| of the <strong>Indo-European</strong> family. |
| </p> |
| <h3>Proto-language</h3> |
| <p> |
| A reconstructed ancestral language that existed before writing. |
| Linguists use the prefix "Proto-" and asterisks (*) for reconstructed forms. |
| </p> |
| <p class="example"> |
| Example: Proto-Indo-European (*méh₂tēr) is the reconstructed ancestor of words for "mother" |
| across many languages, from English to Hindi. |
| </p> |
| <h3>Language Codes</h3> |
| <p> |
| Languages are identified by standardized codes from <a href="https://en.wikipedia.org/wiki/ISO_639-3" target="_blank" rel="noopener">ISO 639</a>, |
| similar to how countries have two-letter codes (US, UK, DE). |
| These codes help linguists and researchers categorize the world's ~7,000 languages consistently. |
| </p> |
| <p class="example"> |
| Examples: <code>en</code> = English, <code>la</code> = Latin, <code>grc</code> = Ancient Greek, |
| <code>ang</code> = Old English, <code>gem-pro</code> = Proto-Germanic, <code>ine-pro</code> = Proto-Indo-European |
| </p> |
| <h3>Why Random Words Are Obscure</h3> |
| <p> |
| Word usage follows <a href="https://en.wikipedia.org/wiki/Zipf%27s_law" target="_blank" rel="noopener">Zipf's Law</a>—a |
| small number of words make up most of what we read, while thousands of rare words form a "long tail." |
| Our random button samples uniformly, giving you equal chances of discovering hidden gems like "cystolithic" or "auxotrophy." |
| </p> |
| </div> |
|
|
| <div id="tab-how" class="tab-content"> |
| <h2>How It Works</h2> |
| <h3>The Data Pipeline</h3> |
| <ol> |
| <li><strong>Source:</strong> EtymDB extracts etymology data from Wiktionary</li> |
| <li><strong>Curation:</strong> We filter for clean English words with valid etymology links (~40K words)</li> |
| <li><strong>Language metadata:</strong> Each word is tagged with its language family (e.g., "Germanic → Indo-European")</li> |
| <li><strong>Definitions:</strong> Enriched from Free Dictionary API (~21K definitions)</li> |
| </ol> |
| <h3>Reading the Graph</h3> |
| <ul> |
| <li><strong>Arrows</strong> point from modern words to their ancestors</li> |
| <li><strong>Click any word</strong> to see its language family and definition</li> |
| <li><strong>Language families</strong> show how languages are historically related</li> |
| </ul> |
| <h3>Definition Matching</h3> |
| <p> |
| EtymDB provides a <strong>sense</strong> field for each word entry (e.g., "bank" might have |
| senses like "financial institution" or "side of a river"). When the sense differs from |
| the word itself, we display it directly. |
| </p> |
| <p> |
| <strong>Key assumption:</strong> When a word's sense equals its lexeme (e.g., sense="bank" |
| for word "bank"), we fall back to the <strong>first definition</strong> from the Free Dictionary API. |
| We assume this primary definition corresponds to the word's main etymological meaning. |
| This may not always be accurate for words with multiple distinct origins. |
| </p> |
| <h3>Limitations</h3> |
| <p> |
| Not all words have definitions available. Some etymology connections may be incomplete |
| or reflect Wiktionary's editorial choices. Compound word breakdowns (e.g., "magn-animus") |
| are not yet supported. Definition matching between EtymDB senses and dictionary entries |
| is approximate—there is no shared identifier between the two data sources. |
| </p> |
| </div> |
| </div> |
| </div> |
| </main> |
|
|
| <footer> |
| <p>Etymology from <a href="https://github.com/clefourrier/EtymDB" target="_blank" rel="noopener">EtymDB 2.1</a>. Definitions from <a href="https://dictionaryapi.dev/" target="_blank" rel="noopener">Free Dictionary API</a>.</p> |
| <p>Built by <a href="https://github.com/lucharo" target="_blank" rel="noopener">@lucharo</a> · <a href="https://github.com/lucharo/etymology-for-all" target="_blank" rel="noopener">Source code</a></p> |
| <details id="version-details" class="version-details"> |
| <summary id="version-summary">Version info</summary> |
| <div id="version-content" class="version-content">Loading...</div> |
| </details> |
| </footer> |
|
|
| <script type="module" src="js/app.js?v=6"></script> |
| </body> |
| </html> |
|
|