numclassify 0.3.1__tar.gz → 0.3.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. {numclassify-0.3.1 → numclassify-0.3.2.1}/CHANGELOG.md +13 -0
  2. {numclassify-0.3.1 → numclassify-0.3.2.1}/PKG-INFO +1 -1
  3. numclassify-0.3.2.1/docs/extra/saffron.css +37 -0
  4. {numclassify-0.3.1 → numclassify-0.3.2.1}/docs/playground.css +155 -0
  5. {numclassify-0.3.1 → numclassify-0.3.2.1}/docs/playground.html +17 -3
  6. {numclassify-0.3.1 → numclassify-0.3.2.1}/docs/playground.js +181 -21
  7. {numclassify-0.3.1 → numclassify-0.3.2.1}/mkdocs.yml +6 -2
  8. {numclassify-0.3.1 → numclassify-0.3.2.1}/pyproject.toml +1 -1
  9. {numclassify-0.3.1 → numclassify-0.3.2.1}/.github/workflows/ci.yml +0 -0
  10. {numclassify-0.3.1 → numclassify-0.3.2.1}/.github/workflows/docs.yml +0 -0
  11. {numclassify-0.3.1 → numclassify-0.3.2.1}/.github/workflows/publish.yml +0 -0
  12. {numclassify-0.3.1 → numclassify-0.3.2.1}/.gitignore +0 -0
  13. {numclassify-0.3.1 → numclassify-0.3.2.1}/CONTRIBUTING.md +0 -0
  14. {numclassify-0.3.1 → numclassify-0.3.2.1}/LICENSE +0 -0
  15. {numclassify-0.3.1 → numclassify-0.3.2.1}/README.md +0 -0
  16. {numclassify-0.3.1 → numclassify-0.3.2.1}/docs/api.md +0 -0
  17. {numclassify-0.3.1 → numclassify-0.3.2.1}/docs/changelog.md +0 -0
  18. {numclassify-0.3.1 → numclassify-0.3.2.1}/docs/cli.md +0 -0
  19. {numclassify-0.3.1 → numclassify-0.3.2.1}/docs/index.md +0 -0
  20. {numclassify-0.3.1 → numclassify-0.3.2.1}/examples/basic_usage.py +0 -0
  21. {numclassify-0.3.1 → numclassify-0.3.2.1}/examples/custom_type.py +0 -0
  22. {numclassify-0.3.1 → numclassify-0.3.2.1}/examples/find_perfect_numbers.py +0 -0
  23. {numclassify-0.3.1 → numclassify-0.3.2.1}/examples/random_classify.py +0 -0
  24. {numclassify-0.3.1 → numclassify-0.3.2.1}/examples/stream_large_range.py +0 -0
  25. {numclassify-0.3.1 → numclassify-0.3.2.1}/numclassify/__init__.py +0 -0
  26. {numclassify-0.3.1 → numclassify-0.3.2.1}/numclassify/__main__.py +0 -0
  27. {numclassify-0.3.1 → numclassify-0.3.2.1}/numclassify/_core/__init__.py +0 -0
  28. {numclassify-0.3.1 → numclassify-0.3.2.1}/numclassify/_core/combinatorial.py +0 -0
  29. {numclassify-0.3.1 → numclassify-0.3.2.1}/numclassify/_core/digital.py +0 -0
  30. {numclassify-0.3.1 → numclassify-0.3.2.1}/numclassify/_core/divisors.py +0 -0
  31. {numclassify-0.3.1 → numclassify-0.3.2.1}/numclassify/_core/figurate.py +0 -0
  32. {numclassify-0.3.1 → numclassify-0.3.2.1}/numclassify/_core/number_theory.py +0 -0
  33. {numclassify-0.3.1 → numclassify-0.3.2.1}/numclassify/_core/powers.py +0 -0
  34. {numclassify-0.3.1 → numclassify-0.3.2.1}/numclassify/_core/primes.py +0 -0
  35. {numclassify-0.3.1 → numclassify-0.3.2.1}/numclassify/_core/recreational.py +0 -0
  36. {numclassify-0.3.1 → numclassify-0.3.2.1}/numclassify/_core/sequences.py +0 -0
  37. {numclassify-0.3.1 → numclassify-0.3.2.1}/numclassify/_registry.py +0 -0
  38. {numclassify-0.3.1 → numclassify-0.3.2.1}/numclassify/cli.py +0 -0
  39. {numclassify-0.3.1 → numclassify-0.3.2.1}/numclassify/py.typed +0 -0
  40. {numclassify-0.3.1 → numclassify-0.3.2.1}/tests/test_registry.py +0 -0
@@ -6,6 +6,19 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
6
6
 
7
7
  ---
8
8
 
9
+ ## [0.3.2.1] - 2026-06-12
10
+
11
+ ### Fixed
12
+ - Search autocomplete dropdown not appearing — added fallback fetch of property names if initial Pyodide load fails, plus visible "Loading..." state in dropdown
13
+
14
+ ## [0.3.2] - 2026-06-12
15
+
16
+ ### Added
17
+ - Search autocomplete dropdown — suggests property names as you type in the search field
18
+ - Confetti celebration burst when a number scores >50 properties
19
+ - Keyboard shortcuts: `C` (classify), `S` (search), `N` (Number of the Day), `?`/`H` (show shortcuts overlay)
20
+ - `prefers-reduced-motion` support — disables all animations for accessibility
21
+
9
22
  ## [0.3.1] - 2026-06-12
10
23
 
11
24
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: numclassify
3
- Version: 0.3.1
3
+ Version: 0.3.2.1
4
4
  Summary: The most comprehensive Python library for number classification - 3000+ number types
5
5
  Project-URL: Homepage, https://github.com/aratrikghosh2011-tech/numclassify
6
6
  Project-URL: Repository, https://github.com/aratrikghosh2011-tech/numclassify
@@ -0,0 +1,37 @@
1
+ :root {
2
+ --md-primary-fg-color: #FF9933;
3
+ --md-primary-fg-color--light: #FFB366;
4
+ --md-primary-fg-color--dark: #E6821A;
5
+ --md-primary-bg-color: #0A0A0F;
6
+ --md-primary-bg-color--light: #12121A;
7
+ --md-accent-fg-color: #FF9933;
8
+ --md-accent-fg-color--light: #FFB366;
9
+ --md-accent-fg-color--dark: #E6821A;
10
+ --md-typeset-a-color: #FF9933;
11
+ }
12
+
13
+ [data-md-color-scheme="slate"] {
14
+ --md-default-bg-color: #0A0A0F;
15
+ --md-code-bg-color: #12121A;
16
+ }
17
+
18
+ .md-header {
19
+ background: linear-gradient(135deg, #E6821A, #FF9933) !important;
20
+ }
21
+
22
+ .md-tabs {
23
+ background: #E6821A !important;
24
+ }
25
+
26
+ .md-search__form {
27
+ background: rgba(255,255,255,0.15) !important;
28
+ }
29
+
30
+ a:hover {
31
+ color: #FFB366 !important;
32
+ }
33
+
34
+ .md-footer {
35
+ background: #0A0A0F !important;
36
+ border-top: 1px solid rgba(255,153,51,0.2);
37
+ }
@@ -999,3 +999,158 @@ footer a:hover {
999
999
  .tabs { gap: 2px; }
1000
1000
  .tab { font-size: 12px; padding: 8px 6px; }
1001
1001
  }
1002
+
1003
+ /* ── Search Autocomplete ────────────────────────────────────────────── */
1004
+
1005
+ .autocomplete-wrap {
1006
+ position: relative;
1007
+ flex: 1;
1008
+ min-width: 180px;
1009
+ }
1010
+
1011
+ #ac-dropdown {
1012
+ position: absolute;
1013
+ top: calc(100% + 4px);
1014
+ left: 0;
1015
+ right: 0;
1016
+ background: var(--bg-elevated);
1017
+ border: 1px solid var(--border);
1018
+ border-radius: 8px;
1019
+ max-height: 220px;
1020
+ overflow-y: auto;
1021
+ z-index: 50;
1022
+ display: none;
1023
+ box-shadow: 0 8px 24px rgba(0,0,0,0.3);
1024
+ }
1025
+
1026
+ #ac-dropdown.open { display: block; }
1027
+
1028
+ .ac-item {
1029
+ padding: 8px 14px;
1030
+ font-family: 'JetBrains Mono', monospace;
1031
+ font-size: 12px;
1032
+ cursor: pointer;
1033
+ transition: background 0.15s ease;
1034
+ color: var(--text);
1035
+ }
1036
+
1037
+ .ac-item:first-child { border-radius: 7px 7px 0 0; }
1038
+ .ac-item:last-child { border-radius: 0 0 7px 7px; }
1039
+
1040
+ .ac-item:hover, .ac-item.selected {
1041
+ background: var(--saffron-faint);
1042
+ color: var(--saffron);
1043
+ }
1044
+
1045
+ .ac-item .ac-cat {
1046
+ float: right;
1047
+ font-size: 10px;
1048
+ color: var(--text-muted);
1049
+ font-family: 'Inter', sans-serif;
1050
+ }
1051
+
1052
+ /* ── Confetti ────────────────────────────────────────────────────────── */
1053
+
1054
+ .confetti-container {
1055
+ position: fixed;
1056
+ inset: 0;
1057
+ pointer-events: none;
1058
+ z-index: 9999;
1059
+ overflow: hidden;
1060
+ }
1061
+
1062
+ .confetti-piece {
1063
+ position: absolute;
1064
+ width: 8px;
1065
+ height: 8px;
1066
+ top: -10px;
1067
+ animation: confettiFall linear forwards;
1068
+ }
1069
+
1070
+ @keyframes confettiFall {
1071
+ 0% {
1072
+ transform: translateY(0) rotate(0deg) scale(1);
1073
+ opacity: 1;
1074
+ }
1075
+ 100% {
1076
+ transform: translateY(100vh) rotate(720deg) scale(0.3);
1077
+ opacity: 0;
1078
+ }
1079
+ }
1080
+
1081
+ /* ── Keyboard Shortcuts Overlay ──────────────────────────────────────── */
1082
+
1083
+ .shortcuts-overlay {
1084
+ position: fixed;
1085
+ inset: 0;
1086
+ background: rgba(0,0,0,0.6);
1087
+ backdrop-filter: blur(4px);
1088
+ z-index: 9998;
1089
+ display: none;
1090
+ align-items: center;
1091
+ justify-content: center;
1092
+ }
1093
+
1094
+ .shortcuts-overlay.open { display: flex; }
1095
+
1096
+ .shortcuts-panel {
1097
+ background: var(--bg-card);
1098
+ border: 1px solid var(--border);
1099
+ border-radius: 16px;
1100
+ padding: 32px 40px;
1101
+ max-width: 400px;
1102
+ width: 90%;
1103
+ animation: slideUp 0.3s ease both;
1104
+ }
1105
+
1106
+ .shortcuts-panel h2 {
1107
+ font-size: 16px;
1108
+ font-weight: 700;
1109
+ color: var(--saffron);
1110
+ margin-bottom: 20px;
1111
+ text-transform: uppercase;
1112
+ letter-spacing: 0.08em;
1113
+ }
1114
+
1115
+ .shortcut-row {
1116
+ display: flex;
1117
+ align-items: center;
1118
+ justify-content: space-between;
1119
+ padding: 8px 0;
1120
+ border-bottom: 1px solid rgba(255,153,51,0.06);
1121
+ }
1122
+
1123
+ .shortcut-row:last-child { border-bottom: none; }
1124
+
1125
+ .shortcut-row .sk-key {
1126
+ background: var(--bg-input);
1127
+ border: 1px solid var(--border);
1128
+ border-radius: 4px;
1129
+ padding: 2px 8px;
1130
+ font-family: 'JetBrains Mono', monospace;
1131
+ font-size: 12px;
1132
+ color: var(--saffron);
1133
+ min-width: 28px;
1134
+ text-align: center;
1135
+ }
1136
+
1137
+ .shortcut-row .sk-desc {
1138
+ font-size: 13px;
1139
+ color: var(--text-muted);
1140
+ }
1141
+
1142
+ .shortcuts-close {
1143
+ margin-top: 20px;
1144
+ width: 100%;
1145
+ }
1146
+
1147
+ /* ── Reduced Motion ──────────────────────────────────────────────────── */
1148
+
1149
+ @media (prefers-reduced-motion: reduce) {
1150
+ *, *::before, *::after {
1151
+ animation-duration: 0.01ms !important;
1152
+ animation-iteration-count: 1 !important;
1153
+ transition-duration: 0.01ms !important;
1154
+ scroll-behavior: auto !important;
1155
+ }
1156
+ }
@@ -19,7 +19,7 @@
19
19
  <header>
20
20
  <button class="theme-toggle" onclick="toggleTheme()" title="Toggle theme"><span id="theme-icon">&#127769;</span></button>
21
21
  <div class="logo-badge">numclassify playground</div>
22
- <div class="version-badge" id="version-badge">v<span id="version-text">—</span></div>
22
+ <div class="version-badge" id="version-badge" data-version="0.3.2.1">v<span id="version-text">—</span></div>
23
23
  <h1>Number Intelligence</h1>
24
24
  <p class="subtitle">Classify any integer into 3000+ named mathematical types — powered by real Python in your browser.</p>
25
25
  </header>
@@ -60,7 +60,7 @@
60
60
  </div>
61
61
  </div>
62
62
  <div class="tags-wrap" id="classify-tags"></div>
63
- <div class="btn-row">
63
+ <div class="btn-row" id="batch-actions">
64
64
  <button class="btn btn-icon" onclick="copyResults()">Copy</button>
65
65
  <button class="btn btn-icon" onclick="shareURL()">Share</button>
66
66
  <button class="btn btn-icon" onclick="downloadJSON()">Download</button>
@@ -88,7 +88,10 @@
88
88
  <div class="card">
89
89
  <div class="card-title">Search by Property</div>
90
90
  <div class="input-row">
91
- <input type="text" id="input-property" placeholder="Property name (e.g. prime, perfect, triangular)..." />
91
+ <div class="autocomplete-wrap">
92
+ <input type="text" id="input-property" placeholder="Property name (e.g. prime, perfect, triangular)..." autocomplete="off" />
93
+ <div id="ac-dropdown"></div>
94
+ </div>
92
95
  </div>
93
96
  <div class="input-row" style="margin-top:10px;">
94
97
  <input type="number" id="input-range-start" placeholder="From" value="1" />
@@ -166,6 +169,17 @@
166
169
  <button id="scroll-top" onclick="scrollToTop()" title="Scroll to top">&uarr;</button>
167
170
  <div id="toast"></div>
168
171
 
172
+ <div class="shortcuts-overlay" id="shortcuts-overlay" onclick="closeShortcuts()">
173
+ <div class="shortcuts-panel" onclick="event.stopPropagation()">
174
+ <h2>Keyboard Shortcuts</h2>
175
+ <div class="shortcut-row"><span class="sk-key">C</span><span class="sk-desc">Classify tab</span></div>
176
+ <div class="shortcut-row"><span class="sk-key">S</span><span class="sk-desc">Search tab</span></div>
177
+ <div class="shortcut-row"><span class="sk-key">N</span><span class="sk-desc">Number of the Day</span></div>
178
+ <div class="shortcut-row"><span class="sk-key">?</span><span class="sk-desc">Toggle this panel</span></div>
179
+ <button class="btn shortcuts-close" onclick="closeShortcuts()">Close</button>
180
+ </div>
181
+ </div>
182
+
169
183
  <script src="playground.js"></script>
170
184
  </body>
171
185
  </html>
@@ -5,6 +5,7 @@ let allProperties = [];
5
5
  let searchPage = 0;
6
6
  const SEARCH_PAGE_SIZE = 50;
7
7
  let searchResultsAll = [];
8
+ let batchMode = false;
8
9
 
9
10
  // ── Utility ──────────────────────────────────────────────────────────────
10
11
 
@@ -166,20 +167,22 @@ function makeTags(props, container, delayBase = 0) {
166
167
 
167
168
  // ── Score Counter Animation ──────────────────────────────────────────────
168
169
 
169
- function animateScore(el, target, duration = 400) {
170
+ function animateScore(target, duration = 400) {
171
+ const counter = document.querySelector('#classify-score .score-count');
172
+ if (!counter) return;
170
173
  const start = performance.now();
171
- const startVal = 0;
172
174
  function step(now) {
173
175
  const t = Math.min((now - start) / duration, 1);
174
176
  const ease = 1 - Math.pow(1 - t, 3);
175
- const current = Math.round(startVal + (target - startVal) * ease);
176
- el.textContent = current + ' properties';
177
+ const current = Math.round(target * ease);
178
+ counter.textContent = current;
177
179
  if (t < 1) requestAnimationFrame(step);
178
180
  else {
179
- el.textContent = target + ' properties';
180
- el.classList.remove('pulse');
181
- void el.offsetWidth;
182
- el.classList.add('pulse');
181
+ counter.textContent = target;
182
+ const badge = $('classify-score');
183
+ badge.classList.remove('pulse');
184
+ void badge.offsetWidth;
185
+ badge.classList.add('pulse');
183
186
  }
184
187
  }
185
188
  requestAnimationFrame(step);
@@ -225,12 +228,18 @@ json.dumps(m)
225
228
  console.warn('Could not load category map', e);
226
229
  }
227
230
 
228
- // Fetch version number
231
+ // Fetch version number — prefer local source version as canonical
232
+ const badge = $('version-badge');
233
+ const srcVersion = badge ? badge.dataset.version : null;
229
234
  try {
230
- const ver = await pyodide.runPythonAsync('nc.__version__');
231
- $('version-text').textContent = ver;
235
+ const pypiVer = await pyodide.runPythonAsync('nc.__version__');
236
+ // Use the source version (from pyproject.toml) since PyPI may lag behind
237
+ $('version-text').textContent = srcVersion || pypiVer;
238
+ if (srcVersion && pypiVer !== srcVersion) {
239
+ badge.title = 'Source: v' + srcVersion + ' | PyPI: v' + pypiVer;
240
+ }
232
241
  } catch(e) {
233
- $('version-text').textContent = '0.3.0';
242
+ $('version-text').textContent = srcVersion || '?';
234
243
  }
235
244
 
236
245
  setProgress(100, 'Ready!');
@@ -256,6 +265,13 @@ json.dumps(m)
256
265
 
257
266
  async function doClassify(n = null) {
258
267
  if (!requireReady()) return;
268
+ // If no explicit number and input contains commas/spaces, go to batch mode
269
+ if (n === null) {
270
+ const raw = $('input-classify').value.trim();
271
+ if (raw.includes(',') || /\s/.test(raw)) {
272
+ return doBatchClassify();
273
+ }
274
+ }
259
275
  const val = n !== null ? n : parseInt($('input-classify').value);
260
276
  if (isNaN(val)) { toast('Enter a valid integer.'); return; }
261
277
 
@@ -271,9 +287,12 @@ json.dumps({"number": r["number"], "score": r["score"], "props": r["true_propert
271
287
  `);
272
288
  const data = JSON.parse(result);
273
289
 
290
+ batchMode = false;
274
291
  renderNumber($('classify-number'), data.number);
275
- animateScore($('classify-score'), data.score);
292
+ animateScore(data.score);
276
293
  makeTags(data.props, $('classify-tags'));
294
+ $('batch-actions').style.display = '';
295
+ if (data.score > 50) burstConfetti();
277
296
 
278
297
  const res = $('result-classify');
279
298
  res.style.display = 'block';
@@ -327,6 +346,7 @@ json.dumps(results)
327
346
  `);
328
347
  const results = JSON.parse(result);
329
348
 
349
+ batchMode = true;
330
350
  const res = $('result-classify');
331
351
  res.style.display = 'block';
332
352
  res.classList.remove('show');
@@ -334,7 +354,8 @@ json.dumps(results)
334
354
  res.classList.add('show');
335
355
 
336
356
  $('classify-number').innerHTML = `<span style="font-size:24px;background:none;-webkit-text-fill-color:var(--saffron);color:var(--saffron)">Batch (${results.length})</span>`;
337
- $('classify-score').textContent = '';
357
+ $('classify-score').innerHTML = '<span class="score-count"></span>';
358
+ $('batch-actions').style.display = 'none';
338
359
 
339
360
  let html = '<table class="batch-table"><thead><tr><th>Number</th><th>Score</th><th>Properties</th></tr></thead><tbody>';
340
361
  results.forEach(r => {
@@ -538,6 +559,7 @@ json.dumps({"only_a": only_a, "only_b": only_b, "shared": shared})
538
559
  // ── Number of the Day ────────────────────────────────────────────────────
539
560
 
540
561
  async function computeNOTD(dateStr) {
562
+ if (!pyReady) return;
541
563
  try {
542
564
  if ($('notd-content')) $('notd-content').style.display = 'none';
543
565
  if ($('notd-loading')) $('notd-loading').style.display = 'block';
@@ -580,6 +602,7 @@ function onNotdDateChange(input) {
580
602
  // ── Copy & Share & Download ──────────────────────────────────────────────
581
603
 
582
604
  async function copyResults() {
605
+ if (batchMode) { toast('Cannot copy batch results — classify a single number first.'); return; }
583
606
  const num = $('classify-number').textContent;
584
607
  const score = $('classify-score').textContent;
585
608
  const tags = [...$('classify-tags').querySelectorAll('.tag')].map(t => t.textContent).join(', ');
@@ -606,10 +629,12 @@ function shareURL() {
606
629
  }
607
630
 
608
631
  function downloadJSON() {
632
+ if (batchMode) { toast('Cannot download batch results — classify a single number first.'); return; }
609
633
  const num = $('classify-number').textContent;
610
- const score = $('classify-score').textContent.replace(' properties', '');
634
+ const scoreEl = document.querySelector('#classify-score .score-count');
635
+ const score = scoreEl ? parseInt(scoreEl.textContent) || 0 : 0;
611
636
  const tags = [...$('classify-tags').querySelectorAll('.tag')].map(t => t.textContent);
612
- const data = { number: parseInt(num) || num, score: parseInt(score) || 0, properties: tags };
637
+ const data = { number: parseInt(num) || num, score, properties: tags };
613
638
  const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
614
639
  const url = URL.createObjectURL(blob);
615
640
  const a = document.createElement('a');
@@ -643,16 +668,149 @@ function scrollToTop() {
643
668
  window.scrollTo({ top: 0, behavior: 'smooth' });
644
669
  }
645
670
 
671
+ // ── Search Autocomplete ───────────────────────────────────────────────────
672
+
673
+ function setupAutocomplete() {
674
+ const input = $('input-property');
675
+ if (!input) return;
676
+ const dropdown = $('ac-dropdown');
677
+ let selectedIdx = -1;
678
+
679
+ input.addEventListener('input', async () => {
680
+ const val = input.value.trim().toLowerCase();
681
+ if (!val) { dropdown.classList.remove('open'); return; }
682
+
683
+ // Fallback: if allProperties is empty (Pyodide category fetch failed), try on-the-fly
684
+ if (!allProperties.length && pyReady && pyodide) {
685
+ try {
686
+ const sample = await pyodide.runPythonAsync(`
687
+ import json
688
+ from numclassify._registry import REGISTRY
689
+ keys = list(REGISTRY.keys())
690
+ json.dumps(keys)
691
+ `);
692
+ const keys = JSON.parse(sample);
693
+ if (keys.length) {
694
+ allProperties = keys;
695
+ categoryMap = {};
696
+ keys.forEach(k => { categoryMap[k] = ''; });
697
+ console.log('Autocomplete: fallback loaded', keys.length, 'properties');
698
+ }
699
+ } catch(e) {
700
+ console.warn('Autocomplete fallback failed', e);
701
+ }
702
+ }
703
+
704
+ const matches = allProperties
705
+ .filter(p => p.includes(val))
706
+ .slice(0, 10);
707
+ if (!matches.length) {
708
+ if (!allProperties.length) {
709
+ dropdown.innerHTML = '<div class="ac-item" style="cursor:default;color:var(--text-muted)">Loading properties...</div>';
710
+ dropdown.classList.add('open');
711
+ } else {
712
+ dropdown.classList.remove('open');
713
+ }
714
+ return;
715
+ }
716
+ selectedIdx = -1;
717
+ dropdown.innerHTML = matches.map((p, i) =>
718
+ `<div class="ac-item${i === 0 ? ' selected' : ''}" data-prop="${p}" onclick="pickAutocomplete('${p}')">
719
+ ${p.replace(/_/g, ' ')}
720
+ <span class="ac-cat">${(categoryMap[p] || '').replace(/_/g, ' ')}</span>
721
+ </div>`
722
+ ).join('');
723
+ dropdown.classList.add('open');
724
+ });
725
+
726
+ input.addEventListener('keydown', e => {
727
+ const items = dropdown.querySelectorAll('.ac-item');
728
+ if (!items.length) return;
729
+ if (e.key === 'ArrowDown') {
730
+ e.preventDefault();
731
+ selectedIdx = Math.min(selectedIdx + 1, items.length - 1);
732
+ } else if (e.key === 'ArrowUp') {
733
+ e.preventDefault();
734
+ selectedIdx = Math.max(selectedIdx - 1, -1);
735
+ } else if (e.key === 'Enter' && selectedIdx >= 0) {
736
+ e.preventDefault();
737
+ items[selectedIdx].click();
738
+ return;
739
+ } else return;
740
+ items.forEach((el, i) => el.classList.toggle('selected', i === selectedIdx));
741
+ });
742
+
743
+ document.addEventListener('click', e => {
744
+ if (!input.contains(e.target) && !dropdown.contains(e.target)) {
745
+ dropdown.classList.remove('open');
746
+ }
747
+ });
748
+ }
749
+
750
+ function pickAutocomplete(prop) {
751
+ $('input-property').value = prop;
752
+ $('ac-dropdown').classList.remove('open');
753
+ doSearch();
754
+ }
755
+
756
+ // ── Confetti ──────────────────────────────────────────────────────────────
757
+
758
+ function burstConfetti() {
759
+ const container = document.createElement('div');
760
+ container.className = 'confetti-container';
761
+ const colors = ['#FF9933', '#FF6B6B', '#4ECDC4', '#45B7D1', '#F7DC6F', '#DDA0DD', '#98D8C8', '#BB8FCE', '#FFEAA7', '#4CAF7D'];
762
+ for (let i = 0; i < 80; i++) {
763
+ const piece = document.createElement('div');
764
+ piece.className = 'confetti-piece';
765
+ const size = 4 + Math.random() * 8;
766
+ piece.style.width = size + 'px';
767
+ piece.style.height = size + 'px';
768
+ piece.style.background = colors[Math.floor(Math.random() * colors.length)];
769
+ piece.style.left = Math.random() * 100 + '%';
770
+ piece.style.borderRadius = Math.random() > 0.5 ? '50%' : '2px';
771
+ piece.style.animationDuration = (1.5 + Math.random() * 2) + 's';
772
+ piece.style.animationDelay = Math.random() * 0.5 + 's';
773
+ container.appendChild(piece);
774
+ }
775
+ document.body.appendChild(container);
776
+ setTimeout(() => container.remove(), 3500);
777
+ }
778
+
779
+ // ── Keyboard Shortcuts ────────────────────────────────────────────────────
780
+
781
+ const SHORTCUT_MAP = {
782
+ c: 'classify',
783
+ s: 'search',
784
+ n: 'notd',
785
+ };
786
+
787
+ function setupShortcuts() {
788
+ document.addEventListener('keydown', e => {
789
+ if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
790
+ const key = e.key.toLowerCase();
791
+ if (key === '?' || key === 'h') {
792
+ e.preventDefault();
793
+ $('shortcuts-overlay').classList.toggle('open');
794
+ return;
795
+ }
796
+ const tab = SHORTCUT_MAP[key];
797
+ if (tab) {
798
+ e.preventDefault();
799
+ switchTab(tab);
800
+ }
801
+ });
802
+ }
803
+
804
+ function closeShortcuts() {
805
+ $('shortcuts-overlay').classList.remove('open');
806
+ }
807
+
646
808
  // ── Init ─────────────────────────────────────────────────────────────────
647
809
 
648
810
  document.addEventListener('DOMContentLoaded', () => {
649
811
  // Enter key support
650
812
  $('input-classify')?.addEventListener('keydown', e => {
651
- if (e.key === 'Enter') {
652
- const raw = e.target.value.trim();
653
- if (raw.includes(',') || raw.includes(' ')) doBatchClassify();
654
- else doClassify();
655
- }
813
+ if (e.key === 'Enter') doClassify();
656
814
  });
657
815
  $('input-property')?.addEventListener('keydown', e => {
658
816
  if (e.key === 'Enter') doSearch();
@@ -668,5 +826,7 @@ document.addEventListener('DOMContentLoaded', () => {
668
826
  if ($('theme-icon')) $('theme-icon').textContent = savedTheme === 'light' ? '☀️' : '🌙';
669
827
  }
670
828
 
829
+ setupAutocomplete();
830
+ setupShortcuts();
671
831
  initPyodide();
672
832
  });
@@ -7,13 +7,17 @@ repo_name: aratrikghosh2011-tech/numclassify
7
7
  theme:
8
8
  name: material
9
9
  palette:
10
- primary: indigo
11
- accent: indigo
10
+ primary: custom
11
+ accent: custom
12
+ scheme: slate
12
13
  features:
13
14
  - navigation.tabs
14
15
  - navigation.top
15
16
  - content.code.copy
16
17
 
18
+ extra_css:
19
+ - extra/saffron.css
20
+
17
21
  nav:
18
22
  - Home: index.md
19
23
  - API Reference: api.md
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "numclassify"
7
- version = "0.3.1"
7
+ version = "0.3.2.1"
8
8
  description = "The most comprehensive Python library for number classification - 3000+ number types"
9
9
  authors = [{name = "Aratrik Ghosh", email = "aratrikghosh2011@gmail.com"}]
10
10
  readme = "README.md"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes