|
5 | 5 | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
6 | 6 | <title>Crypto Lab — Paul Clark / systemslibrarian</title> |
7 | 7 | <meta name="description" content="A curated cryptography lab — from fundamentals to cutting-edge research" /> |
| 8 | + <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='6' fill='%23162b2a'/%3E%3Ctext x='16' y='22' font-family='monospace' font-size='14' font-weight='700' text-anchor='middle' fill='%234dcfb0'%3ECL%3C/text%3E%3C/svg%3E" /> |
8 | 9 | <link rel="preconnect" href="https://fonts.googleapis.com" /> |
9 | 10 | <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> |
10 | 11 | <link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@300;400;500&family=Fraunces:opsz,wght,SOFT@9..144,400,50;9..144,500,50&display=swap" rel="stylesheet" /> |
|
385 | 386 |
|
386 | 387 | .search-bar input { |
387 | 388 | width: 100%; |
388 | | - padding: 14px 18px 14px 44px; |
| 389 | + padding: 14px 50px 14px 44px; |
389 | 390 | font-family: var(--mono); |
390 | 391 | font-size: 13px; |
391 | 392 | color: var(--text-hi); |
392 | 393 | background: rgba(9, 18, 24, 0.9); |
393 | 394 | border: 1px solid var(--border-strong); |
394 | 395 | outline: none; |
395 | | - transition: border-color 0.2s ease; |
| 396 | + transition: border-color 0.2s ease, box-shadow 0.2s ease; |
396 | 397 | } |
397 | 398 |
|
398 | 399 | .search-bar input::placeholder { |
|
402 | 403 |
|
403 | 404 | .search-bar input:focus { |
404 | 405 | border-color: var(--teal); |
| 406 | + box-shadow: 0 0 0 3px rgba(53, 214, 187, 0.15); |
405 | 407 | } |
406 | 408 |
|
407 | 409 | .search-bar::before { |
|
494 | 496 | } |
495 | 497 |
|
496 | 498 | .filter-chip { |
| 499 | + display: inline-flex; |
| 500 | + align-items: center; |
| 501 | + gap: 7px; |
497 | 502 | padding: 6px 10px; |
498 | 503 | min-height: 30px; |
499 | 504 | border: 1px solid var(--border-strong); |
|
504 | 509 | font-family: var(--mono); |
505 | 510 | background: rgba(8, 17, 24, 0.78); |
506 | 511 | cursor: pointer; |
507 | | - transition: border-color 0.2s ease, color 0.2s ease, background 0.2s ease; |
| 512 | + transition: border-color 0.2s ease, color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease; |
| 513 | + } |
| 514 | + |
| 515 | + .filter-chip .chip-count { |
| 516 | + color: var(--text-dim); |
| 517 | + opacity: 0.65; |
| 518 | + font-variant-numeric: tabular-nums; |
| 519 | + letter-spacing: 0.04em; |
| 520 | + transition: color 0.2s ease, opacity 0.2s ease; |
508 | 521 | } |
509 | 522 |
|
510 | 523 | .filter-chip:hover { |
511 | | - border-color: rgba(242, 248, 251, 0.3); |
| 524 | + border-color: rgba(53, 214, 187, 0.45); |
| 525 | + color: var(--text); |
| 526 | + background: rgba(53, 214, 187, 0.04); |
| 527 | + } |
| 528 | + |
| 529 | + .filter-chip:hover .chip-count { |
512 | 530 | color: var(--text); |
| 531 | + opacity: 0.9; |
513 | 532 | } |
514 | 533 |
|
515 | 534 | .filter-chip.active { |
516 | 535 | border-color: var(--teal); |
517 | 536 | color: var(--teal); |
518 | | - background: rgba(53, 214, 187, 0.08); |
| 537 | + background: rgba(53, 214, 187, 0.16); |
| 538 | + box-shadow: inset 0 0 0 1px rgba(53, 214, 187, 0.4); |
| 539 | + } |
| 540 | + |
| 541 | + .filter-chip.active .chip-count { |
| 542 | + color: var(--teal); |
| 543 | + opacity: 1; |
519 | 544 | } |
520 | 545 |
|
521 | 546 | .card-count { |
| 547 | + display: inline-flex; |
| 548 | + align-items: center; |
| 549 | + gap: 12px; |
522 | 550 | color: var(--text-dim); |
523 | 551 | font-size: 11px; |
524 | 552 | letter-spacing: 0.06em; |
525 | 553 | white-space: nowrap; |
526 | 554 | padding-top: 4px; |
| 555 | + transition: color 0.2s ease; |
| 556 | + } |
| 557 | + |
| 558 | + .card-count.filtered .card-count-text { |
| 559 | + color: var(--teal); |
| 560 | + } |
| 561 | + |
| 562 | + .filter-clear { |
| 563 | + font-family: var(--mono); |
| 564 | + font-size: 10px; |
| 565 | + letter-spacing: 0.1em; |
| 566 | + text-transform: uppercase; |
| 567 | + color: var(--text-dim); |
| 568 | + background: transparent; |
| 569 | + border: 1px solid var(--border-strong); |
| 570 | + padding: 4px 9px; |
| 571 | + cursor: pointer; |
| 572 | + transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease; |
| 573 | + } |
| 574 | + |
| 575 | + .filter-clear:hover { |
| 576 | + color: var(--text-hi); |
| 577 | + border-color: var(--teal); |
| 578 | + background: rgba(53, 214, 187, 0.10); |
| 579 | + } |
| 580 | + |
| 581 | + .filter-clear[hidden] { |
| 582 | + display: none; |
527 | 583 | } |
528 | 584 |
|
529 | 585 | .no-results { |
|
958 | 1014 | } |
959 | 1015 |
|
960 | 1016 | html.light .filter-chip.active { |
961 | | - background: rgba(26, 171, 148, 0.08); |
| 1017 | + background: rgba(26, 171, 148, 0.16); |
| 1018 | + box-shadow: inset 0 0 0 1px rgba(26, 171, 148, 0.4); |
| 1019 | + } |
| 1020 | + |
| 1021 | + html.light .filter-chip:hover { |
| 1022 | + border-color: rgba(26, 171, 148, 0.55); |
| 1023 | + background: rgba(26, 171, 148, 0.06); |
| 1024 | + } |
| 1025 | + |
| 1026 | + html.light .search-bar input:focus { |
| 1027 | + box-shadow: 0 0 0 3px rgba(26, 171, 148, 0.18); |
962 | 1028 | } |
963 | 1029 |
|
964 | 1030 | html.light .search-kbd { |
|
1305 | 1371 | <div class="wrap"> |
1306 | 1372 | <div class="filter-bar" id="filter-bar"> |
1307 | 1373 | <div class="search-bar"> |
1308 | | - <input type="text" id="demo-search" placeholder="Filter demos…" aria-label="Filter demos" /> |
| 1374 | + <input type="text" id="demo-search" placeholder="Filter demos… try FROST, noise, or Shor" aria-label="Filter demos" /> |
1309 | 1375 | <button class="search-clear" id="search-clear" aria-label="Clear search" hidden>✕</button> |
1310 | 1376 | <kbd class="search-kbd" aria-hidden="true">/</kbd> |
1311 | 1377 | </div> |
1312 | 1378 | <div class="filter-row"> |
1313 | 1379 | <div class="filter-chips" id="filter-chips"></div> |
1314 | | - <div class="card-count" id="card-count"></div> |
| 1380 | + <div class="card-count" id="card-count"> |
| 1381 | + <span class="card-count-text"></span> |
| 1382 | + <button class="filter-clear" id="filter-clear" type="button" hidden>Clear</button> |
| 1383 | + </div> |
1315 | 1384 | </div> |
1316 | 1385 | </div> |
1317 | 1386 | <div class="no-results" id="no-results" hidden></div> |
|
3208 | 3277 | var input = document.getElementById('demo-search'); |
3209 | 3278 | var noResults = document.getElementById('no-results'); |
3210 | 3279 | var cardCount = document.getElementById('card-count'); |
| 3280 | + var cardCountText = cardCount.querySelector('.card-count-text'); |
| 3281 | + var filterClearBtn = document.getElementById('filter-clear'); |
3211 | 3282 | var chipsEl = document.getElementById('filter-chips'); |
3212 | 3283 | var clearBtn = document.getElementById('search-clear'); |
3213 | 3284 | var treeEl = document.querySelector('.crypto-tree'); |
|
3387 | 3458 | }); |
3388 | 3459 | } |
3389 | 3460 |
|
3390 | | - /* Build filter chips */ |
| 3461 | + /* Build filter chips with per-category counts */ |
| 3462 | + var categoryCounts = {}; |
| 3463 | + cardData.forEach(function (d) { |
| 3464 | + d.categories.forEach(function (c) { |
| 3465 | + categoryCounts[c] = (categoryCounts[c] || 0) + 1; |
| 3466 | + }); |
| 3467 | + }); |
| 3468 | + |
3391 | 3469 | var categoriesWithAll = ['ALL'].concat(CATEGORIES); |
3392 | 3470 | var splitIndex = Math.ceil(categoriesWithAll.length / 2); |
3393 | 3471 | var topRowEl = document.createElement('div'); |
|
3402 | 3480 | var isAll = cat === 'ALL'; |
3403 | 3481 | btn.className = 'filter-chip' + (isAll ? ' active' : ''); |
3404 | 3482 | btn.setAttribute('data-category', isAll ? '' : cat); |
3405 | | - btn.textContent = cat; |
| 3483 | + var labelSpan = document.createElement('span'); |
| 3484 | + labelSpan.className = 'chip-label'; |
| 3485 | + labelSpan.textContent = cat; |
| 3486 | + var countSpan = document.createElement('span'); |
| 3487 | + countSpan.className = 'chip-count'; |
| 3488 | + countSpan.textContent = isAll ? total : (categoryCounts[cat] || 0); |
| 3489 | + btn.appendChild(labelSpan); |
| 3490 | + btn.appendChild(countSpan); |
3406 | 3491 | if (index < splitIndex) topRowEl.appendChild(btn); |
3407 | 3492 | else bottomRowEl.appendChild(btn); |
3408 | 3493 | }); |
|
3432 | 3517 | noResults.hidden = !empty; |
3433 | 3518 | noResults.style.display = empty ? 'block' : 'none'; |
3434 | 3519 | noResults.textContent = empty ? 'No demos found.' : ''; |
3435 | | - cardCount.textContent = 'Showing ' + visible + ' of ' + total; |
| 3520 | + cardCountText.textContent = 'Showing ' + visible + ' of ' + total; |
| 3521 | + var isFiltered = !!(activeCategory || activeSet || input.value); |
| 3522 | + cardCount.classList.toggle('filtered', isFiltered); |
| 3523 | + filterClearBtn.hidden = !isFiltered; |
3436 | 3524 | syncHash(); |
3437 | 3525 | } |
3438 | 3526 |
|
| 3527 | + function resetFilters() { |
| 3528 | + input.value = ''; |
| 3529 | + updateClear(); |
| 3530 | + activeCategory = ''; |
| 3531 | + activeSet = null; |
| 3532 | + clearTreeActive(); |
| 3533 | + Array.from(chipsEl.querySelectorAll('.filter-chip')).forEach(function (c) { |
| 3534 | + c.classList.toggle('active', c.getAttribute('data-category') === ''); |
| 3535 | + }); |
| 3536 | + filter(); |
| 3537 | + } |
| 3538 | + |
| 3539 | + filterClearBtn.addEventListener('click', function () { |
| 3540 | + resetFilters(); |
| 3541 | + input.focus(); |
| 3542 | + }); |
| 3543 | + |
3439 | 3544 | /* Category clicks */ |
3440 | 3545 | chipsEl.addEventListener('click', function (e) { |
3441 | 3546 | var btn = e.target.closest('.filter-chip'); |
|
0 commit comments