etymology / frontend /index.html
lucharo's picture
Super-squash branch 'main' using huggingface_hub
13812dc
<!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">
<!-- Desktop: individual 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>
<!-- Mobile: single menu button -->
<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 &rsaquo;</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;">&#x1F3B2;</span>
</button>
</div>
<p class="search-hint">Click any word in the graph to see its definition</p>
<!-- Backdrop for expanded graph -->
<div id="graph-backdrop" class="graph-backdrop"></div>
<!-- Graph options -->
<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">
<!-- Expand/minimize toggle -->
<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;">&#x1F3B2;</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>
<!-- Tree view (alternative to graph) -->
<div id="tree-view" class="tree-view hidden"></div>
<!-- Graph legend -->
<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>
<!-- Node detail panel -->
<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>
<!-- Stats panel expands below word-info, pushing footer down -->
<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>
<!-- About Modal -->
<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>