quickgrid commited on
Commit
a7b2cc4
Β·
verified Β·
1 Parent(s): 6a8f8b2
Files changed (1) hide show
  1. index.html +246 -43
index.html CHANGED
@@ -175,9 +175,7 @@
175
  transition: background .12s;
176
  }
177
  .dropdown-item:hover { background: var(--bg4); }
178
- .dropdown-item-dot {
179
- width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0;
180
- }
181
  .dropdown-item-name { font-weight: 600; color: var(--text); font-family: 'Bricolage Grotesque', sans-serif; }
182
  .dropdown-item-detail { font-size: 10px; color: var(--text3); font-family: 'JetBrains Mono', monospace; margin-left: auto; white-space: nowrap; }
183
  /* ─── Icon Toggle Buttons ────────────────────────────── */
@@ -189,6 +187,36 @@
189
  .icon-toggle-btn:hover { border-color: var(--border2); color: var(--text); }
190
  .icon-toggle-btn.active { border-color: var(--accent); color: var(--accent); background: rgba(77,158,245,.1); }
191
  .icon-toggle-btn svg { width: 16px; height: 16px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  /* ─── Main Split ─────────────────────────────────────── */
193
  main {
194
  flex: 1; min-height: 0; overflow: hidden;
@@ -418,22 +446,124 @@
418
  /* ─── Animations ─────────────────────────────────────── */
419
  @keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
420
  .fade-in { animation: fadeIn .2s ease forwards; }
421
- /* ─── Responsive ─────────────────────────────────────── */
422
- @media (max-width: 1100px) {
423
- main { grid-template-columns: 1fr 1fr; }
424
- main.single-panel { grid-template-columns: 1fr 2fr; }
425
- .output-panel:last-child { display: none; }
426
- .searchbar-group:last-of-type { display: none; }
427
- }
428
- @media (max-width: 768px) {
429
- header { padding: 0 12px; gap: 8px; flex-wrap: wrap; height: auto; padding: 8px 12px; }
430
- .logo-name { font-size: 14px; }
431
- main { grid-template-columns: 1fr; }
432
- main.single-panel { grid-template-columns: 1fr; }
433
- .input-panel { border-right: none; border-bottom: 1px solid var(--border); max-height: 35vh; }
434
- .output-panel { border-left: none !important; }
435
- .output-panel:last-child { display: none; }
436
- .searchbar-group { min-width: 140px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
437
  }
438
  </style>
439
  </head>
@@ -446,14 +576,14 @@
446
  <div class="logo">
447
  <div class="logo-hex">T</div>
448
  <span class="logo-name">TokenLens</span>
449
- <span class="logo-tag">v1.2</span>
450
  </div>
451
  <div class="header-divider"></div>
452
  <div class="header-controls">
453
  <!-- Search bar A -->
454
  <div class="searchbar-group" id="search-group-0">
455
  <span class="searchbar-label label-a">A</span>
456
- <input class="searchbar-input" id="search-input-0" type="text" placeholder="HF model id… e.g. Xenova/gpt2" />
457
  <button class="searchbar-dropdown-btn" id="dropdown-btn-0" title="Predefined models">
458
  <svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 3.5L5 6.5L8 3.5"/></svg>
459
  </button>
@@ -463,7 +593,7 @@
463
  <!-- Search bar B -->
464
  <div class="searchbar-group" id="search-group-1">
465
  <span class="searchbar-label label-b">B</span>
466
- <input class="searchbar-input" id="search-input-1" type="text" placeholder="HF model id… e.g. Xenova/llama-tokenizer" />
467
  <button class="searchbar-dropdown-btn" id="dropdown-btn-1" title="Predefined models">
468
  <svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 3.5L5 6.5L8 3.5"/></svg>
469
  </button>
@@ -481,10 +611,16 @@
481
  </button>
482
  </div>
483
  </header>
 
 
 
 
 
 
484
  <!-- Main -->
485
  <main id="main-grid">
486
  <!-- Left: Input -->
487
- <div class="input-panel">
488
  <div class="panel-header">
489
  <div class="panel-title">
490
  <div class="panel-title-icon">✎</div>
@@ -679,22 +815,27 @@ const panels = [
679
  { tokenizer: null, modelId: null, view: 'text' },
680
  { tokenizer: null, modelId: null, view: 'text' },
681
  ];
682
- let tokenizerCache = {};
683
- let panel1Visible = true;
684
- let debounceTimer = null;
 
685
 
686
  // ── DOM References ─────────────────────────────────────────
687
- const $overlay = document.getElementById('loading-overlay');
688
- const $loadTitle = document.getElementById('loading-title');
689
- const $loadSub = document.getElementById('loading-sub');
690
- const $loadBar = document.getElementById('loading-bar');
691
- const $loadFile = document.getElementById('loading-file');
692
- const $input = document.getElementById('input-area');
693
- const $charCount = document.getElementById('char-count');
694
- const $toast = document.getElementById('toast');
695
- const $mainGrid = document.getElementById('main-grid');
696
- const $panelToggle = document.getElementById('panel-toggle');
697
- const $themeToggle = document.getElementById('theme-toggle');
 
 
 
 
698
 
699
  // ── Utilities ──────────────────────────────────────────────
700
  function showOverlay(title, sub) {
@@ -751,6 +892,65 @@ function decodeTokenString(raw) {
751
  return s;
752
  }
753
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
754
  // ── Tokenize for a specific panel ─────────────────────────
755
  async function tokenizeForPanel(idx, text) {
756
  const p = panels[idx];
@@ -785,7 +985,7 @@ async function tokenizeForPanel(idx, text) {
785
  // ── Render Views ───────────────────────────────────────────
786
  function renderView(idx, tokens) {
787
  const view = panels[idx].view;
788
- if (view === 'text') renderTextView(idx, tokens);
789
  else if (view === 'ids') renderIdView(idx, tokens);
790
  else if (view === 'list') renderListView(idx, tokens);
791
  }
@@ -1012,17 +1212,17 @@ document.querySelectorAll('.sample-btn').forEach(btn => {
1012
  $panelToggle.addEventListener('click', () => {
1013
  panel1Visible = !panel1Visible;
1014
  $panelToggle.classList.toggle('active', panel1Visible);
1015
- const $panel1 = document.getElementById('panel-1');
 
 
1016
  const $searchGroup1 = document.getElementById('search-group-1');
 
1017
  if (panel1Visible) {
1018
- $panel1.style.display = '';
1019
- $searchGroup1.style.display = '';
1020
  $mainGrid.classList.remove('single-panel');
1021
  } else {
1022
- $panel1.style.display = 'none';
1023
- $searchGroup1.style.display = 'none';
1024
  $mainGrid.classList.add('single-panel');
1025
  }
 
1026
  runTokenize();
1027
  });
1028
 
@@ -1054,6 +1254,9 @@ buildDropdowns();
1054
 
1055
  setTheme('dark');
1056
 
 
 
 
1057
  document.getElementById('search-input-0').value = MODELS[0].id;
1058
  loadModel(0, MODELS[0].id);
1059
  </script>
 
175
  transition: background .12s;
176
  }
177
  .dropdown-item:hover { background: var(--bg4); }
178
+ .dropdown-item-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
 
 
179
  .dropdown-item-name { font-weight: 600; color: var(--text); font-family: 'Bricolage Grotesque', sans-serif; }
180
  .dropdown-item-detail { font-size: 10px; color: var(--text3); font-family: 'JetBrains Mono', monospace; margin-left: auto; white-space: nowrap; }
181
  /* ─── Icon Toggle Buttons ────────────────────────────── */
 
187
  .icon-toggle-btn:hover { border-color: var(--border2); color: var(--text); }
188
  .icon-toggle-btn.active { border-color: var(--accent); color: var(--accent); background: rgba(77,158,245,.1); }
189
  .icon-toggle-btn svg { width: 16px; height: 16px; }
190
+ /* ─── Mobile Tab Bar ─────────────────────────────────── */
191
+ .mobile-tab-bar {
192
+ display: none;
193
+ border-bottom: 1px solid var(--border);
194
+ flex-shrink: 0;
195
+ background: var(--bg2);
196
+ padding: 0 4px;
197
+ }
198
+ .mobile-tab {
199
+ flex: 1;
200
+ padding: 10px 4px;
201
+ border: none;
202
+ background: transparent;
203
+ color: var(--text2);
204
+ font-family: 'DM Sans', sans-serif;
205
+ font-size: 12px;
206
+ font-weight: 500;
207
+ cursor: pointer;
208
+ border-bottom: 2px solid transparent;
209
+ transition: all .15s;
210
+ text-align: center;
211
+ }
212
+ .mobile-tab.active {
213
+ color: var(--accent);
214
+ border-bottom-color: var(--accent);
215
+ }
216
+ .mobile-tab.tab-disabled {
217
+ opacity: 0.3;
218
+ pointer-events: none;
219
+ }
220
  /* ─── Main Split ─────────────────────────────────────── */
221
  main {
222
  flex: 1; min-height: 0; overflow: hidden;
 
446
  /* ─── Animations ─────────────────────────────────────── */
447
  @keyframes fadeIn { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
448
  .fade-in { animation: fadeIn .2s ease forwards; }
449
+ /* ─── Responsive: Tablet (600–900px) ──────────────────── */
450
+ @media (min-width: 601px) and (max-width: 900px) {
451
+ header {
452
+ flex-wrap: wrap;
453
+ height: auto;
454
+ padding: 8px 12px;
455
+ gap: 8px;
456
+ }
457
+ .header-divider { display: none; }
458
+ .header-controls {
459
+ flex-basis: 100%;
460
+ display: flex;
461
+ flex-wrap: wrap;
462
+ gap: 8px;
463
+ }
464
+ .searchbar-group {
465
+ flex: 1 1 180px;
466
+ }
467
+ main {
468
+ grid-template-columns: 1fr;
469
+ grid-template-rows: auto 1fr 1fr;
470
+ }
471
+ main.single-panel {
472
+ grid-template-columns: 1fr;
473
+ grid-template-rows: auto 1fr;
474
+ }
475
+ .input-panel {
476
+ border-right: none;
477
+ border-bottom: 1px solid var(--border);
478
+ max-height: 25vh;
479
+ }
480
+ .output-panel {
481
+ border-left: none !important;
482
+ border-bottom: 1px solid var(--border);
483
+ }
484
+ .output-panel:last-child {
485
+ border-bottom: none;
486
+ }
487
+ .stat-card { padding: 7px 10px; }
488
+ .stat-value { font-size: 17px; }
489
+ .stat-sub { font-size: 8px; }
490
+ .stat-label { font-size: 8px; }
491
+ .output-panel-header { padding: 8px 10px; }
492
+ .view-toggle { padding: 6px 10px; }
493
+ }
494
+ /* ─── Responsive: Mobile (≀600px) ────────────────────── */
495
+ @media (max-width: 600px) {
496
+ header {
497
+ flex-wrap: wrap;
498
+ height: auto;
499
+ padding: 6px 10px;
500
+ gap: 6px;
501
+ }
502
+ .header-divider { display: none; }
503
+ .logo-tag { display: none; }
504
+ .header-controls {
505
+ flex-basis: 100%;
506
+ display: flex;
507
+ flex-wrap: wrap;
508
+ gap: 6px;
509
+ }
510
+ .searchbar-group {
511
+ flex: 1 1 100%;
512
+ }
513
+ .searchbar-label { display: none; }
514
+ .dropdown-menu {
515
+ left: 0 !important;
516
+ right: 0 !important;
517
+ min-width: unset;
518
+ }
519
+ .mobile-tab-bar {
520
+ display: flex;
521
+ }
522
+ main {
523
+ grid-template-columns: 1fr;
524
+ grid-template-rows: 1fr;
525
+ }
526
+ .input-panel,
527
+ .output-panel {
528
+ display: none;
529
+ }
530
+ .mobile-active {
531
+ display: flex !important;
532
+ }
533
+ .input-panel {
534
+ border-right: none;
535
+ border-bottom: none;
536
+ max-height: none;
537
+ }
538
+ .output-panel {
539
+ border-left: none !important;
540
+ border-bottom: none;
541
+ }
542
+ .panel-header {
543
+ flex-wrap: wrap;
544
+ gap: 6px;
545
+ padding: 10px 12px 8px;
546
+ }
547
+ .sample-btns { width: 100%; }
548
+ .sample-btn { font-size: 9px; padding: 3px 6px; }
549
+ .output-panel-header { padding: 6px 10px; }
550
+ .model-indicator { font-size: 10px; }
551
+ .stat-card { padding: 5px 8px; }
552
+ .stat-value { font-size: 16px; }
553
+ .stat-label { font-size: 7px; letter-spacing: .5px; margin-bottom: 2px; }
554
+ .stat-sub { font-size: 7px; }
555
+ .view-toggle { padding: 5px 10px; }
556
+ .toggle-btn { padding: 3px 8px; font-size: 10px; }
557
+ .token-display { padding: 10px; }
558
+ .token-text-view { font-size: 12px; line-height: 2; }
559
+ .tok-split-idx { width: 28px; font-size: 8px; }
560
+ .tok-split-text { font-size: 11px; }
561
+ .tok-split-id { font-size: 9px; }
562
+ footer {
563
+ padding: 6px 12px;
564
+ font-size: 9px;
565
+ }
566
+ footer span:last-child { display: none; }
567
  }
568
  </style>
569
  </head>
 
576
  <div class="logo">
577
  <div class="logo-hex">T</div>
578
  <span class="logo-name">TokenLens</span>
579
+ <span class="logo-tag">v1.3</span>
580
  </div>
581
  <div class="header-divider"></div>
582
  <div class="header-controls">
583
  <!-- Search bar A -->
584
  <div class="searchbar-group" id="search-group-0">
585
  <span class="searchbar-label label-a">A</span>
586
+ <input class="searchbar-input" id="search-input-0" type="text" placeholder="Model A: HF repo id…" />
587
  <button class="searchbar-dropdown-btn" id="dropdown-btn-0" title="Predefined models">
588
  <svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 3.5L5 6.5L8 3.5"/></svg>
589
  </button>
 
593
  <!-- Search bar B -->
594
  <div class="searchbar-group" id="search-group-1">
595
  <span class="searchbar-label label-b">B</span>
596
+ <input class="searchbar-input" id="search-input-1" type="text" placeholder="Model B: HF repo id…" />
597
  <button class="searchbar-dropdown-btn" id="dropdown-btn-1" title="Predefined models">
598
  <svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M2 3.5L5 6.5L8 3.5"/></svg>
599
  </button>
 
611
  </button>
612
  </div>
613
  </header>
614
+ <!-- Mobile Tab Bar -->
615
+ <div class="mobile-tab-bar" id="mobile-tab-bar">
616
+ <button class="mobile-tab active" data-tab="input">Input</button>
617
+ <button class="mobile-tab" data-tab="panel-0">Model A</button>
618
+ <button class="mobile-tab" data-tab="panel-1">Model B</button>
619
+ </div>
620
  <!-- Main -->
621
  <main id="main-grid">
622
  <!-- Left: Input -->
623
+ <div class="input-panel mobile-active" id="input-panel">
624
  <div class="panel-header">
625
  <div class="panel-title">
626
  <div class="panel-title-icon">✎</div>
 
815
  { tokenizer: null, modelId: null, view: 'text' },
816
  { tokenizer: null, modelId: null, view: 'text' },
817
  ];
818
+ let tokenizerCache = {};
819
+ let panel1Visible = true;
820
+ let debounceTimer = null;
821
+ let mobileActiveTab = 'input';
822
 
823
  // ── DOM References ─────────────────────────────────────────
824
+ const $overlay = document.getElementById('loading-overlay');
825
+ const $loadTitle = document.getElementById('loading-title');
826
+ const $loadSub = document.getElementById('loading-sub');
827
+ const $loadBar = document.getElementById('loading-bar');
828
+ const $loadFile = document.getElementById('loading-file');
829
+ const $input = document.getElementById('input-area');
830
+ const $charCount = document.getElementById('char-count');
831
+ const $toast = document.getElementById('toast');
832
+ const $mainGrid = document.getElementById('main-grid');
833
+ const $panelToggle = document.getElementById('panel-toggle');
834
+ const $themeToggle = document.getElementById('theme-toggle');
835
+ const $inputPanel = document.getElementById('input-panel');
836
+ const $panel0 = document.getElementById('panel-0');
837
+ const $panel1 = document.getElementById('panel-1');
838
+ const $mobileTabs = document.querySelectorAll('.mobile-tab');
839
 
840
  // ── Utilities ──────────────────────────────────────────────
841
  function showOverlay(title, sub) {
 
892
  return s;
893
  }
894
 
895
+ // ── Mobile Tab Handling ────────────────────────────────────
896
+ function isMobile() { return window.innerWidth <= 600; }
897
+
898
+ function applyMobileTab(tabId) {
899
+ mobileActiveTab = tabId;
900
+ $mobileTabs.forEach(t => t.classList.toggle('active', t.dataset.tab === tabId));
901
+ // Remove mobile-active from all panels
902
+ $inputPanel.classList.remove('mobile-active');
903
+ $panel0.classList.remove('mobile-active');
904
+ $panel1.classList.remove('mobile-active');
905
+ // Add to the target
906
+ if (tabId === 'input') $inputPanel.classList.add('mobile-active');
907
+ if (tabId === 'panel-0') $panel0.classList.add('mobile-active');
908
+ if (tabId === 'panel-1') $panel1.classList.add('mobile-active');
909
+ }
910
+
911
+ $mobileTabs.forEach(tab => {
912
+ tab.addEventListener('click', () => {
913
+ applyMobileTab(tab.dataset.tab);
914
+ });
915
+ });
916
+
917
+ // Handle resize: reset display properties when switching between mobile/desktop
918
+ function handleResize() {
919
+ if (!isMobile()) {
920
+ // Desktop/tablet: remove mobile-active, reset display for all panels
921
+ $inputPanel.classList.remove('mobile-active');
922
+ $panel0.classList.remove('mobile-active');
923
+ $panel1.classList.remove('mobile-active');
924
+ $inputPanel.style.display = '';
925
+ $panel0.style.display = '';
926
+ $panel1.style.display = panel1Visible ? '' : 'none';
927
+ } else {
928
+ // Mobile: apply mobile tab logic
929
+ $inputPanel.style.display = '';
930
+ $panel0.style.display = '';
931
+ $panel1.style.display = '';
932
+ applyMobileTab(mobileActiveTab);
933
+ }
934
+ updateMobileTabBState();
935
+ }
936
+
937
+ function updateMobileTabBState() {
938
+ const $tabB = document.querySelector('.mobile-tab[data-tab="panel-1"]');
939
+ if ($tabB) {
940
+ if (panel1Visible) {
941
+ $tabB.classList.remove('tab-disabled');
942
+ } else {
943
+ $tabB.classList.add('tab-disabled');
944
+ // If currently on tab B, switch away
945
+ if (mobileActiveTab === 'panel-1') {
946
+ applyMobileTab('panel-0');
947
+ }
948
+ }
949
+ }
950
+ }
951
+
952
+ window.addEventListener('resize', handleResize);
953
+
954
  // ── Tokenize for a specific panel ─────────────────────────
955
  async function tokenizeForPanel(idx, text) {
956
  const p = panels[idx];
 
985
  // ── Render Views ───────────────────────────────────────────
986
  function renderView(idx, tokens) {
987
  const view = panels[idx].view;
988
+ if (view === 'text') renderTextView(idx, tokens);
989
  else if (view === 'ids') renderIdView(idx, tokens);
990
  else if (view === 'list') renderListView(idx, tokens);
991
  }
 
1212
  $panelToggle.addEventListener('click', () => {
1213
  panel1Visible = !panel1Visible;
1214
  $panelToggle.classList.toggle('active', panel1Visible);
1215
+ if (!isMobile()) {
1216
+ $panel1.style.display = panel1Visible ? '' : 'none';
1217
+ }
1218
  const $searchGroup1 = document.getElementById('search-group-1');
1219
+ $searchGroup1.style.display = panel1Visible ? '' : 'none';
1220
  if (panel1Visible) {
 
 
1221
  $mainGrid.classList.remove('single-panel');
1222
  } else {
 
 
1223
  $mainGrid.classList.add('single-panel');
1224
  }
1225
+ updateMobileTabBState();
1226
  runTokenize();
1227
  });
1228
 
 
1254
 
1255
  setTheme('dark');
1256
 
1257
+ // Set initial mobile state
1258
+ handleResize();
1259
+
1260
  document.getElementById('search-input-0').value = MODELS[0].id;
1261
  loadModel(0, MODELS[0].id);
1262
  </script>