-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathscript.js
More file actions
2403 lines (2162 loc) · 228 KB
/
script.js
File metadata and controls
2403 lines (2162 loc) · 228 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Tag categories definition
const tagCategories = {
"Signing Devices": ["ColdCard Q", "ColdCard MK(1-4)", "Jade", "Jade Plus", "Krux", "Trezor One", "Trezor T", "Trezor Safe", "Ledger Flex", "KeepKey", "SeedSigner", "Passport Core", "Keystone", "Tapsigner", "Seedkeeper", "Satochip", "Specter DIY", "KeyFlint", "BitBox", "Bitkey", "Ledger Nano(S/X)", "Satodime", "Satscard", "OneKey", "Signing Devices (General)", "Opendime", "Frostsnap"],
"Wallets": ["Sparrow", "Electrum", "BlueWallet", "Ginger", "Wasabi", "Phoenix", "Zeus", "Muun", "Nunchuk", "Liana", "Blockstream App", "Ashigaru", "Aqua", "Bitcoin Core Wallet", "Bitcoin Keeper", "JAM", "Envoy", "Fedi", "Minibits", "Mercury", "Nutstash", "Samourai", "Proton", "Specter Desktop", "Blitz", "Blixt", "Breez", "Cake", "Joltz", "Mutiny", "Theya", "Speed", "Yeti", "Zebedee", "Lily", "Wallet of Satoshi", "Cashu.me", "Spark", "eNuts", "Bull Bitcoin Wallet", "Cove"],
"Nodes & Servers": ["Bitcoin Core", "Bitcoin Knots", "Umbrel", "Start9", "Bitcoin Core Node", "Citadel", "Fully Noded", "Raspiblitz", "RoninDojo", "Parmanode", "Electrum Rust Server (Electrs)", "Ubuntu Node Box", "MyNode", "Ashigaru Dojo", "Fulcrum", "Bitcoin Node Box"],
"Mining": ["Avalon Nano", "NerdAxe", "Bitaxe", "Braiins Mini Miner", "Braiins Deck", "Ocean Pool/DATUM", "Futurebit", "Braiins Pool", "Heatbit"],
"Lightning Network": ["Lightning", "Thunderhub", "Alby", "Lightning Network Daemon (LND)", "Lightning Node Connect", "LNbits", "Ride The Lightning", "Voltage", "Core Lightning", "Bolt Ring", "Boltz", "Pool", "Loop"],
"Services & Exchanges": ["Bitcoin Well", "Hodl Hodl", "Kraken", "BTCPay Server", "Debifi", "Azteco", "Bisq", "Casa", "Unchained/Caravan", "Bittr", "Bitrefill", "Fountain", "Strike", "Spike to Spike", "Ledn", "Anchorwatch/Trident", "IBEXPay", "Robosats", "Peach", "Coinos", "Shakepay", "River", "Bull Bitcoin", "Bitaroo", "Mempool.space"],
"Tokens": ["Liquid", "USDT", "Testnet"],
"Ecash": ["Fedimint", "Cashu"],
"Security": ["Verifying Downloads", "Seed Phrases (General)", "UTXO Management", "SeedQR", "Encrypted Backups", "Child Seeds (BIP 85)", "Multisig", "Seed XOR", "Shamir's Secret Sharing (SLIP-39)", "Derivation Paths"],
"Privacy": ["Coinjoin (JoinMarket)", "Coinjoin (Wabisabi)", "Coinjoin (Whirlpool)", "Non-KYC", "Payjoin", "Paynyms (BIP 47)", "Silent Payments"],
"Advanced Features": ["Timelocks", "FROST", "Border Wallet", "Child Pays For Parent (CPFP)", "Replace By Fee (RBF)", "Gettxoutsetinfo (Audit Supply)", "Taproot Assets", "Partially Signed Bitcoin Transactions (PSBT)", "Statechains"]
};
// Available tag icons - update this list when adding new icon files
// To regenerate: ls "tag icons" | sed 's/.png$//' | sort
const availableTagIcons = new Set([
"alby", "anchorwatchtrident", "aqua", "ashigaru", "ashigaru-dojo", "avalon-nano", "azteco",
"bisq", "bitaroo", "bitaxe", "bitbox", "bitcoin-core", "bitcoin-core-wallet", "bitcoin-keeper",
"bitcoin-knots", "bitcoin-node-box", "bitcoin-well", "bitkey", "bitrefill", "bittr", "blitz",
"blixt", "blockstream-app", "bluewallet", "bolt-ring", "boltz", "braiins-deck", "braiins-mini-miner", "braiins-pool",
"breez", "btcpay-server", "bull-bitcoin", "bull-bitcoin-wallet", "cake", "casa", "cashu", "cashume", "citadel", "coinos",
"coldcard-mk1-4", "coldcard-q", "core-lightning", "cove", "ocean-pooldatum", "debifi", "electrum",
"electrum-rust-server-electrs", "enuts", "envoy", "fedi", "fedimint", "fountain", "frostsnap",
"fulcrum", "fully-noded", "futurebit", "ginger", "heatbit", "hodl-hodl", "ibexpay", "jade", "jade-plus",
"jam", "joltz", "keepkey", "keystone", "kraken", "krux", "ledger-flex", "ledger-nanosx", "ledn",
"liana", "lightning-network-daemon-lnd", "lightning-node-connect", "lily", "liquid", "lnbits",
"loop", "mempoolspace", "mercury", "minibits", "mutiny", "muun", "mynode", "nerdaxe",
"nunchuk", "nutstash", "onekey", "opendime", "parmanode", "passport-core", "peach", "phoenix",
"pool", "proton", "raspiblitz", "ride-the-lightning", "river", "robosats", "ronindojo", "satochip",
"satodime", "satscard", "seedkeeper", "seedsigner", "shakepay", "spark", "sparrow", "specter-desktop",
"specter-diy", "speed", "spike-to-spike", "start9", "strike", "tapsigner", "testnet", "theya",
"thunderhub", "trezor-one", "trezor-safe", "trezor-t", "ubuntu-node-box", "umbrel",
"unchainedcaravan", "usdt", "voltage", "wallet-of-satoshi", "wasabi", "yeti", "zebedee", "zeus"
]);
// Available creator icons - update this list when adding new icon files
// To regenerate: ls "creator icons" | sed 's/.png$//' | sort
const availableCreatorIcons = new Set([
"402-payment-required", "88-sats-radio", "adam-obrien", "adam-soltys", "ai-invests", "altair-technology",
"anchorwatch", "andrew-with-laser-eyes", "area-bitcoin-english", "arman-the-parman", "avalon-support",
"avoidbit", "bb-2k16", "bitbagger", "bitbox", "bitcoin-brandon", "bitcoin-classroom", "bitcoin-daytrader",
"bitcoin-education", "bitcoin-learning", "bitcoin-magazine", "bitcoin-not-crypto", "bitcoin-quickies",
"bitcoin-takeover", "bitcoin-unlocked", "bitcoiner-malaysia", "bithyve", "bitsoloplayer-official",
"blkbox-trading", "blockchain-academics", "blockdyor", "blockstream", "btc-sessions", "btcpay-server",
"bull-bitcoin", "cake-wallet", "canadian-bitcoiners", "chris-bagnell", "coinkite", "corner-culture",
"crrdlx", "crypto-blick", "crypto-bull", "crypto-friday", "crypto-guide", "crypto-moose", "cryptojar",
"curiousinventor", "dairebtc", "darren-honeysett", "davani", "elestio", "explore-crypto", "forresthodl", "foundation",
"frostsnap", "getbittr", "ginger-wallet", "guy-swann", "hardcore-bitcoiner", "ian-major", "imineblocks",
"jbinzala", "joe-nakamoto", "jonathan-levi", "justh0dl", "kahoobb", "keepkey", "kevin-mulcrone", "kf-chan", "l33t-guy",
"laine", "ledn", "liana-wallet", "lightning-labs", "lnbits", "lukes-tech", "ministry-of-nodes",
"money-reimagined", "moneyzg", "mynode", "natalie-brunell", "nico-moran", "ocean", "paul-lamb", "peach",
"piecover-btc", "pioneering-freedom", "plan-b-network", "rabid-mining", "red-panda-mining", "rhett-reisman",
"robotechy", "ronindojo", "ryan-matta", "ryan-scribner", "saniexp", "satashi21", "satoshi-radio-global",
"seedsigner", "southern-bitcoiner", "sovereign-money", "sterling", "specter", "tanleybench", "tech-express", "tftc",
"the-bitcoin-hardware-store", "the-cryptodad", "the-hobbyist-miner", "the-social-guide", "the-space",
"thebtccourse", "theya", "tj-free", "trezor", "unchained", "understanding-bitcoin", "vasker-media",
"voltage", "vortex-bitcoin", "voskcoin", "wandering-maverick", "wantclue", "wasabi-wallet",
"wicked-smart-bitcoin", "wizardsardine", "yeti-bitcoin-wallet"
]);
// Your video database - add your own tutorials here
const videoData = [];
let currentVideos = [...videoData];
let sortAscending = false;
// Search functionality variables
let activeFilters = new Set();
let filterTypes = new Map(); // Store the type of each filter
let allTags = [];
let allCreators = [];
let openInfoBoxes = new Set(); // Store which info boxes are currently open
// Filter info for social media links
let filterInfoMap = new Map(); // Store social links by "name:type" key
// DOM elements
const videoGrid = document.getElementById('video-grid');
const sortBy = document.getElementById('sort-by');
const sortOrderBtn = document.getElementById('sort-order');
const searchInput = document.getElementById('search-input');
const searchSuggestions = document.getElementById('search-suggestions');
const clearSearchBtn = document.getElementById('clear-search');
const activeFiltersContainer = document.getElementById('active-filters');
const tagCategoriesContainer = document.getElementById('tag-categories');
const videoCountNumber = document.getElementById('video-count-number');
const priceValue = document.getElementById('price-value');
const blockHeight = document.getElementById('block-height');
const videoModal = document.getElementById('video-modal');
const videoIframe = document.getElementById('video-iframe');
const videoModalTitle = document.getElementById('video-modal-title');
const videoModalCreator = document.getElementById('video-modal-creator');
const videoModalClose = document.querySelector('.video-modal-close');
const headerBanner = document.getElementById('header-banner');
const showFavoritesBtn = document.getElementById('show-favorites-btn');
// Favorites functionality
let favorites = [];
let showingFavoritesOnly = false;
const FAVORITES_STORAGE_KEY = 'bitcoinTutorialsFavorites';
// Bitcoin API functions
async function fetchBitcoinPrice() {
try {
const response = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd');
const data = await response.json();
const price = Math.round(data.bitcoin.usd);
priceValue.textContent = price.toLocaleString();
} catch (error) {
console.error('Error fetching Bitcoin price:', error);
// Try backup API
try {
const backupResponse = await fetch('https://api.coinbase.com/v2/exchange-rates?currency=BTC');
const backupData = await backupResponse.json();
const backupPrice = Math.round(parseFloat(backupData.data.rates.USD));
priceValue.textContent = backupPrice.toLocaleString();
} catch (backupError) {
console.error('Backup API also failed:', backupError);
priceValue.textContent = 'Error';
}
}
}
async function fetchBlockHeight() {
try {
const response = await fetch('https://blockstream.info/api/blocks/tip/height');
const height = await response.text();
blockHeight.textContent = parseInt(height).toLocaleString();
} catch (error) {
console.error('Error fetching block height:', error);
blockHeight.textContent = 'Error';
}
}
// Video modal functions
function openVideoModal(video) {
videoModalTitle.textContent = video.title;
videoModalCreator.textContent = `by ${video.creator}`;
videoIframe.src = `https://www.youtube-nocookie.com/embed/${video.youtubeId}?rel=0&modestbranding=1&showinfo=0`;
videoModal.style.display = 'block';
document.body.style.overflow = 'hidden'; // Prevent background scrolling
}
function closeVideoModal() {
videoModal.style.display = 'none';
videoIframe.src = ''; // Stop video playback
document.body.style.overflow = 'auto'; // Restore background scrolling
}
// Initialize Bitcoin data
function initBitcoinData() {
fetchBitcoinPrice();
fetchBlockHeight();
// Update every 5 minutes
setInterval(() => {
fetchBitcoinPrice();
fetchBlockHeight();
}, 5 * 60 * 1000);
}
// Favorites functions
function loadFavorites() {
try {
const stored = localStorage.getItem(FAVORITES_STORAGE_KEY);
favorites = stored ? JSON.parse(stored) : [];
console.log(`Loaded ${favorites.length} favorites from localStorage`);
} catch (error) {
console.error('Error loading favorites:', error);
favorites = [];
}
}
function saveFavorites() {
try {
localStorage.setItem(FAVORITES_STORAGE_KEY, JSON.stringify(favorites));
console.log(`Saved ${favorites.length} favorites to localStorage`);
} catch (error) {
console.error('Error saving favorites:', error);
}
}
function isFavorited(youtubeId) {
return favorites.includes(youtubeId);
}
function toggleFavorite(youtubeId) {
const index = favorites.indexOf(youtubeId);
if (index === -1) {
// Add to favorites
favorites.push(youtubeId);
} else {
// Remove from favorites
favorites.splice(index, 1);
}
saveFavorites();
updateFavoritesButton();
// Update the favorite button on the card
const favoriteBtn = document.querySelector(`.favorite-button[data-youtube-id="${youtubeId}"]`);
if (favoriteBtn) {
favoriteBtn.classList.toggle('active', isFavorited(youtubeId));
}
// If showing favorites only, re-filter to remove unfavorited videos
if (showingFavoritesOnly) {
updateActiveFiltersDisplay();
applyActiveFilters();
}
}
function toggleFavoritesView() {
showingFavoritesOnly = !showingFavoritesOnly;
updateFavoritesButton();
updateActiveFiltersDisplay();
// Use the unified filter system which now respects favorites
applyActiveFilters();
}
function updateFavoritesButton() {
if (!showFavoritesBtn) return;
if (showingFavoritesOnly) {
showFavoritesBtn.textContent = '🔖 Show All';
showFavoritesBtn.classList.add('active');
} else {
showFavoritesBtn.textContent = `🔖 Show Favorites (${favorites.length})`;
showFavoritesBtn.classList.remove('active');
}
}
function clearAllFavorites() {
if (favorites.length === 0) {
alert('You have no favorites to clear.');
return;
}
const confirmed = confirm(`Are you sure you want to clear all ${favorites.length} favorites? This cannot be undone.`);
if (!confirmed) return;
// Clear favorites array
favorites = [];
saveFavorites();
// If currently showing favorites only, switch back to all videos
if (showingFavoritesOnly) {
showingFavoritesOnly = false;
}
// Update UI
updateFavoritesButton();
updateActiveFiltersDisplay(); // Update filter pills to remove favorites pill
applyActiveFilters(); // Use unified filter system
// Update all favorite buttons on cards
document.querySelectorAll('.favorite-button.active').forEach(btn => {
btn.classList.remove('active');
});
}
// Initialize the application
function init() {
loadFavorites(); // Load favorites from localStorage
loadDefaultCSV();
loadFilterInfo();
initBitcoinData();
}
// Load default CSV file on startup
function loadDefaultCSV() {
fetch('video_template.csv')
.then(response => response.text())
.then(csvContent => {
const newVideos = parseCSV(csvContent);
videoData.push(...newVideos);
// Always sort by date descending (newest first) on initial load
currentVideos = sortVideos([...videoData], 'date', false);
initializeSearchData();
populateTagSidebar();
renderVideos(currentVideos);
setupEventListeners();
})
.catch(error => {
console.error('Error loading CSV file:', error);
// Continue with empty data if CSV fails to load
initializeSearchData();
populateTagSidebar();
renderVideos(currentVideos);
setupEventListeners();
});
}
// Initialize search data arrays
function initializeSearchData() {
allTags = [...new Set(videoData.flatMap(video => video.tags))].sort();
allCreators = [...new Set(videoData.map(video => video.creator))].sort();
}
// Load filter info CSV
function loadFilterInfo() {
fetch('social-icons-info.csv')
.then(response => response.text())
.then(csvContent => {
parseFilterInfo(csvContent);
updateActiveFiltersDisplay(); // Refresh display with new social links
})
.catch(error => {
console.log('Social icons info not available:', error);
// Site continues to work without social links
});
}
// Parse filter info CSV
function parseFilterInfo(csvContent) {
const lines = csvContent.trim().split('\n');
if (lines.length < 2) return; // No data rows
const headers = lines[0].split(',').map(h => h.trim());
for (let i = 1; i < lines.length; i++) {
const line = lines[i].trim();
if (!line) continue;
const values = parseCSVLine(line);
if (values.length < 2) continue;
const name = values[0].replace(/"/g, '').trim();
const type = values[1].replace(/"/g, '').trim();
const website = values[2] ? values[2].replace(/"/g, '').trim() : '';
const youtube = values[3] ? values[3].replace(/"/g, '').trim() : '';
const github = values[4] ? values[4].replace(/"/g, '').trim() : '';
const twitter = values[5] ? values[5].replace(/"/g, '').trim() : '';
const nostr = values[6] ? values[6].replace(/"/g, '').trim() : '';
const platforms = values[7] ? values[7].replace(/"/g, '').trim() : '';
const key = `${name}:${type}`;
filterInfoMap.set(key, { website, youtube, github, twitter, nostr, platforms });
}
console.log(`Loaded info for ${filterInfoMap.size} filters`);
}
// Search functionality
function performSearch(query) {
if (!query.trim()) {
currentVideos = [...videoData];
renderVideos(currentVideos);
return;
}
const searchTerm = query.toLowerCase().trim();
const filteredVideos = videoData.filter(video => {
const titleMatch = video.title.toLowerCase().includes(searchTerm);
const creatorMatch = video.creator.toLowerCase().includes(searchTerm);
const tagMatch = video.tags.some(tag => tag.toLowerCase().includes(searchTerm));
return titleMatch || creatorMatch || tagMatch;
});
currentVideos = sortVideos(filteredVideos, sortBy.value, sortAscending);
renderVideos(currentVideos);
}
// Generate search suggestions
function generateSuggestions(query) {
if (!query.trim()) {
hideSuggestions();
return;
}
const searchTerm = query.toLowerCase().trim();
const suggestions = [];
// Add matching creators
allCreators.forEach(creator => {
if (creator.toLowerCase().includes(searchTerm)) {
suggestions.push({ text: creator, type: 'creator' });
}
});
// Add matching tags
allTags.forEach(tag => {
if (tag.toLowerCase().includes(searchTerm)) {
suggestions.push({ text: tag, type: 'tag' });
}
});
// Limit to 8 suggestions
const limitedSuggestions = suggestions.slice(0, 8);
displaySuggestions(limitedSuggestions);
}
// Display search suggestions
function displaySuggestions(suggestions) {
if (suggestions.length === 0) {
hideSuggestions();
return;
}
searchSuggestions.innerHTML = '';
suggestions.forEach(suggestion => {
const suggestionElement = document.createElement('div');
suggestionElement.className = 'suggestion-item';
if (suggestion.type === 'tag') {
// For tags: use category color background and add tag icon to the left
const categoryClass = getCategoryClass(suggestion.text);
const tagIcon = getTagIconWithFallback(suggestion.text);
suggestionElement.classList.add('tag-suggestion', categoryClass);
suggestionElement.innerHTML = `
<span class="suggestion-icon-container">${tagIcon}</span>
<span class="suggestion-text suggestion-tag">${suggestion.text}</span>
`;
} else {
// For creators: use YouTube icon to distinguish from tags
suggestionElement.innerHTML = `
<span class="suggestion-icon-container"><img src="youtube.png" class="creator-icon" alt="YouTube"></span>
<span class="suggestion-text">${suggestion.text}</span>
`;
}
suggestionElement.addEventListener('click', () => {
addActiveFilter(suggestion.text, suggestion.type);
// Update sidebar tag visual state if it's a tag filter
if (suggestion.type === 'tag') {
const sidebarTag = document.querySelector(`.sidebar-tag[data-tag="${suggestion.text}"]`);
if (sidebarTag) {
sidebarTag.classList.add('active');
}
}
searchInput.value = ''; // Clear search input
hideSuggestions();
toggleClearButton();
applyActiveFilters();
});
searchSuggestions.appendChild(suggestionElement);
});
searchSuggestions.style.display = 'block';
}
// Hide search suggestions
function hideSuggestions() {
searchSuggestions.style.display = 'none';
}
// Add active filter
function addActiveFilter(filterText, filterType) {
// Create unique identifier for creator vs tag with same name
const filterKey = `${filterText}:${filterType}`;
if (!activeFilters.has(filterKey)) {
activeFilters.add(filterKey);
filterTypes.set(filterKey, filterType); // Store the type
updateActiveFiltersDisplay();
}
}
// Remove active filter
function removeActiveFilter(filterKey) {
activeFilters.delete(filterKey);
filterTypes.delete(filterKey); // Remove the type
openInfoBoxes.delete(filterKey); // Remove from open info boxes
// Extract the original filter text for sidebar update
const filterText = filterKey.split(':')[0];
const filterType = filterKey.split(':')[1];
// Update sidebar tag visual state only if it's a tag filter
if (filterType === 'tag') {
// Check if there are still other tag filters with the same name
const hasOtherTagFilters = Array.from(activeFilters).some(key =>
key.startsWith(`${filterText}:tag`) && key !== filterKey
);
if (!hasOtherTagFilters) {
const sidebarTag = document.querySelector(`.sidebar-tag[data-tag="${filterText}"]`);
if (sidebarTag) {
sidebarTag.classList.remove('active');
}
}
}
updateActiveFiltersDisplay();
applyActiveFilters();
}
// Get filter sort order
function getFilterSortOrder(filterKey) {
const filterText = filterKey.split(':')[0];
const filterType = filterKey.split(':')[1];
// Creator filters always come first
if (filterType === 'creator') {
return 0;
}
// Tag filters are sorted by category order
if (filterType === 'tag') {
const categoryOrder = getCategoryOrder(filterText);
return categoryOrder + 1; // +1 to ensure they come after creators
}
// All other filters come last
return 100;
}
// Check if a filter has social icons
function filterHasSocialIcons(filterKey) {
const info = filterInfoMap.get(filterKey);
if (!info) return false;
// Return true if any social icon exists
return !!(info.website || info.youtube || info.github || info.twitter || info.nostr);
}
// Check if a filter should have an info box
function filterHasInfoBox(filterText, filterType) {
// Create the filter key
const filterKey = `${filterText}:${filterType}`;
// Tags with social icons should have info boxes
if (filterType === 'tag' && filterHasSocialIcons(filterKey)) {
return true;
}
// Tags with educational text should have info boxes
if (filterType === 'tag' && filterText === "Child Seeds (BIP 85)") {
return true;
}
if (filterType === 'tag' && filterText === "Coinjoin (JoinMarket)") {
return true;
}
if (filterType === 'tag' && filterText === "Coinjoin (Wabisabi)") {
return true;
}
if (filterType === 'tag' && filterText === "Coinjoin (Whirlpool)") {
return true;
}
if (filterType === 'tag' && filterText === "Encrypted Backups") {
return true;
}
if (filterType === 'tag' && filterText === "Multisig") {
return true;
}
if (filterType === 'tag' && filterText === "Non-KYC") {
return true;
}
if (filterType === 'tag' && filterText === "Payjoin") {
return true;
}
if (filterType === 'tag' && filterText === "Paynyms (BIP 47)") {
return true;
}
if (filterType === 'tag' && filterText === "Seed Phrases (General)") {
return true;
}
if (filterType === 'tag' && filterText === "Seed XOR") {
return true;
}
if (filterType === 'tag' && filterText === "SeedQR") {
return true;
}
if (filterType === 'tag' && filterText === "Shamir's Secret Sharing (SLIP-39)") {
return true;
}
if (filterType === 'tag' && filterText === "Silent Payments") {
return true;
}
if (filterType === 'tag' && filterText === "UTXO Management") {
return true;
}
if (filterType === 'tag' && filterText === "Verifying Downloads") {
return true;
}
if (filterType === 'tag' && filterText === "Border Wallet") {
return true;
}
if (filterType === 'tag' && filterText === "Child Pays For Parent (CPFP)") {
return true;
}
if (filterType === 'tag' && filterText === "FROST") {
return true;
}
if (filterType === 'tag' && filterText === "Gettxoutsetinfo (Audit Supply)") {
return true;
}
if (filterType === 'tag' && filterText === "Partially Signed Bitcoin Transactions (PSBT)") {
return true;
}
if (filterType === 'tag' && filterText === "Replace By Fee (RBF)") {
return true;
}
if (filterType === 'tag' && filterText === "Statechains") {
return true;
}
if (filterType === 'tag' && filterText === "Taproot Assets") {
return true;
}
if (filterType === 'tag' && filterText === "Timelocks") {
return true;
}
if (filterType === 'tag' && filterText === "Nunchuk") {
return true;
}
if (filterType === 'tag' && filterText === "Derivation Paths") {
return true;
}
if (filterType === 'tag' && filterText === "Signing Devices (General)") {
return true;
}
if (filterType === 'tag' && filterText === "Aqua") {
return true;
}
if (filterType === 'tag' && filterText === "Ashigaru") {
return true;
}
if (filterType === 'tag' && filterText === "Lightning") {
return true;
}
if (filterType === 'tag' && filterText === "Boltz") {
return true;
}
if (filterType === 'tag' && filterText === "Liquid") {
return true;
}
if (filterType === 'tag' && filterText === "Testnet") {
return true;
}
if (filterType === 'tag' && filterText === "USDT") {
return true;
}
if (filterType === 'tag' && filterText === "Cashu") {
return true;
}
if (filterType === 'tag' && filterText === "Fedimint") {
return true;
}
if (filterType === 'tag' && filterText === "Bitcoin Core Wallet") {
return true;
}
if (filterType === 'tag' && filterText === "Bitcoin Keeper") {
return true;
}
if (filterType === 'tag' && filterText === "Blitz") {
return true;
}
if (filterType === 'tag' && filterText === "Blixt") {
return true;
}
if (filterType === 'tag' && filterText === "Blockstream App") {
return true;
}
if (filterType === 'tag' && filterText === "BlueWallet") {
return true;
}
if (filterType === 'tag' && filterText === "Breez") {
return true;
}
if (filterType === 'tag' && filterText === "Cake") {
return true;
}
if (filterType === 'tag' && filterText === "Cashu.me") {
return true;
}
if (filterType === 'tag' && filterText === "Electrum") {
return true;
}
if (filterType === 'tag' && filterText === "Envoy") {
return true;
}
if (filterType === 'tag' && filterText === "Fedi") {
return true;
}
if (filterType === 'tag' && filterText === "Ginger") {
return true;
}
if (filterType === 'tag' && filterText === "JAM") {
return true;
}
if (filterType === 'tag' && filterText === "Joltz") {
return true;
}
if (filterType === 'tag' && filterText === "Liana") {
return true;
}
if (filterType === 'tag' && filterText === "Lily") {
return true;
}
if (filterType === 'tag' && filterText === "Mercury") {
return true;
}
if (filterType === 'tag' && filterText === "Minibits") {
return true;
}
if (filterType === 'tag' && filterText === "Mutiny") {
return true;
}
if (filterType === 'tag' && filterText === "Muun") {
return true;
}
if (filterType === 'tag' && filterText === "Nunchuk") {
return true;
}
if (filterType === 'tag' && filterText === "Nutstash") {
return true;
}
if (filterType === 'tag' && filterText === "Phoenix") {
return true;
}
if (filterType === 'tag' && filterText === "Proton") {
return true;
}
if (filterType === 'tag' && filterText === "Spark") {
return true;
}
if (filterType === 'tag' && filterText === "Sparrow") {
return true;
}
if (filterType === 'tag' && filterText === "Specter Desktop") {
return true;
}
if (filterType === 'tag' && filterText === "Speed") {
return true;
}
if (filterType === 'tag' && filterText === "Theya") {
return true;
}
if (filterType === 'tag' && filterText === "Wasabi") {
return true;
}
if (filterType === 'tag' && filterText === "Yeti") {
return true;
}
if (filterType === 'tag' && filterText === "Zebedee") {
return true;
}
if (filterType === 'tag' && filterText === "Zeus") {
return true;
}
if (filterType === 'tag' && filterText === "eNuts") {
return true;
}
if (filterType === 'tag' && filterText === "Bull Bitcoin Wallet") {
return true;
}
if (filterType === 'tag' && filterText === "Bitcoin Core") {
return true;
}
if (filterType === 'tag' && filterText === "Bitcoin Knots") {
return true;
}
if (filterType === 'tag' && filterText === "Ashigaru Dojo") {
return true;
}
if (filterType === 'tag' && filterText === "Bitcoin Node Box") {
return true;
}
if (filterType === 'tag' && filterText === "Citadel") {
return true;
}
if (filterType === 'tag' && filterText === "Electrum Rust Server (Electrs)") {
return true;
}
if (filterType === 'tag' && filterText === "Fulcrum") {
return true;
}
if (filterType === 'tag' && filterText === "Fully Noded") {
return true;
}
if (filterType === 'tag' && filterText === "MyNode") {
return true;
}
if (filterType === 'tag' && filterText === "Parmanode") {
return true;
}
if (filterType === 'tag' && filterText === "Raspiblitz") {
return true;
}
if (filterType === 'tag' && filterText === "Start9") {
return true;
}
if (filterType === 'tag' && filterText === "Ubuntu Node Box") {
return true;
}
if (filterType === 'tag' && filterText === "Umbrel") {
return true;
}
if (filterType === 'tag' && filterText === "RoninDojo") {
return true;
}
// Signing Devices
if (filterType === 'tag' && filterText === "ColdCard Q") {
return true;
}
if (filterType === 'tag' && filterText === "ColdCard MK(1-4)") {
return true;
}
if (filterType === 'tag' && filterText === "Jade") {
return true;
}
if (filterType === 'tag' && filterText === "Jade Plus") {
return true;
}
if (filterType === 'tag' && filterText === "Krux") {
return true;
}
if (filterType === 'tag' && filterText === "Trezor One") {
return true;
}
if (filterType === 'tag' && filterText === "Trezor T") {
return true;
}
if (filterType === 'tag' && filterText === "Trezor Safe") {
return true;
}
if (filterType === 'tag' && filterText === "Ledger Flex") {
return true;
}
if (filterType === 'tag' && filterText === "KeepKey") {
return true;
}
if (filterType === 'tag' && filterText === "SeedSigner") {
return true;
}
if (filterType === 'tag' && filterText === "Passport Core") {
return true;
}
if (filterType === 'tag' && filterText === "Keystone") {
return true;
}
if (filterType === 'tag' && filterText === "Tapsigner") {
return true;
}
if (filterType === 'tag' && filterText === "Seedkeeper") {
return true;
}
if (filterType === 'tag' && filterText === "Satochip") {
return true;
}
if (filterType === 'tag' && filterText === "Specter DIY") {
return true;
}
if (filterType === 'tag' && filterText === "KeyFlint") {
return true;
}
if (filterType === 'tag' && filterText === "BitBox") {
return true;
}
if (filterType === 'tag' && filterText === "Bitkey") {
return true;
}
if (filterType === 'tag' && filterText === "Ledger Nano(S/X)") {
return true;
}
if (filterType === 'tag' && filterText === "Satodime") {
return true;
}
if (filterType === 'tag' && filterText === "Satscard") {
return true;
}
if (filterType === 'tag' && filterText === "OneKey") {
return true;
}
if (filterType === 'tag' && filterText === "Opendime") {
return true;
}
if (filterType === 'tag' && filterText === "Frostsnap") {
return true;
}
// Mining
if (filterType === 'tag' && filterText === "Avalon Nano") {
return true;
}
if (filterType === 'tag' && filterText === "NerdAxe") {
return true;
}
if (filterType === 'tag' && filterText === "Bitaxe") {
return true;
}
if (filterType === 'tag' && filterText === "Braiins Mini Miner") {
return true;
}
if (filterType === 'tag' && filterText === "Braiins Deck") {
return true;
}
if (filterType === 'tag' && filterText === "Ocean Pool/DATUM") {
return true;
}
if (filterType === 'tag' && filterText === "Futurebit") {
return true;
}
if (filterType === 'tag' && filterText === "Braiins Pool") {
return true;
}
// Lightning Network
if (filterType === 'tag' && filterText === "Thunderhub") {
return true;
}
if (filterType === 'tag' && filterText === "Alby") {
return true;
}
if (filterType === 'tag' && filterText === "Lightning Network Daemon (LND)") {
return true;
}
if (filterType === 'tag' && filterText === "Lightning Node Connect") {
return true;
}
if (filterType === 'tag' && filterText === "LNbits") {
return true;
}
if (filterType === 'tag' && filterText === "Ride The Lightning") {
return true;
}
if (filterType === 'tag' && filterText === "Voltage") {
return true;
}
if (filterType === 'tag' && filterText === "Core Lightning") {
return true;
}
if (filterType === 'tag' && filterText === "Bolt Ring") {
return true;
}
if (filterType === 'tag' && filterText === "Pool") {
return true;
}
if (filterType === 'tag' && filterText === "Loop") {
return true;
}
// Services & Exchanges
if (filterType === 'tag' && filterText === "Bitcoin Well") {
return true;
}
if (filterType === 'tag' && filterText === "Hodl Hodl") {
return true;
}
if (filterType === 'tag' && filterText === "Kraken") {
return true;
}
if (filterType === 'tag' && filterText === "BTCPay Server") {
return true;
}
if (filterType === 'tag' && filterText === "Debifi") {
return true;
}
if (filterType === 'tag' && filterText === "Azteco") {
return true;
}
if (filterType === 'tag' && filterText === "Bisq") {
return true;
}
if (filterType === 'tag' && filterText === "Casa") {
return true;
}
if (filterType === 'tag' && filterText === "Unchained/Caravan") {
return true;
}
if (filterType === 'tag' && filterText === "Bittr") {
return true;
}
if (filterType === 'tag' && filterText === "Bitrefill") {
return true;
}
if (filterType === 'tag' && filterText === "Fountain") {
return true;
}
if (filterType === 'tag' && filterText === "Strike") {
return true;
}
if (filterType === 'tag' && filterText === "Spike to Spike") {
return true;
}
if (filterType === 'tag' && filterText === "Ledn") {
return true;
}
if (filterType === 'tag' && filterText === "Anchorwatch/Trident") {
return true;
}
if (filterType === 'tag' && filterText === "IBEXPay") {
return true;
}
if (filterType === 'tag' && filterText === "Robosats") {
return true;
}
if (filterType === 'tag' && filterText === "Peach") {
return true;
}
if (filterType === 'tag' && filterText === "Coinos") {
return true;
}
if (filterType === 'tag' && filterText === "Shakepay") {
return true;
}
if (filterType === 'tag' && filterText === "River") {