-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmcatalog.html
More file actions
138 lines (111 loc) · 4.88 KB
/
mcatalog.html
File metadata and controls
138 lines (111 loc) · 4.88 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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Math</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-zinc-950 text-zinc-100 min-h-screen">
<div class="max-w-6xl mx-auto px-6 py-12">
<header class="flex flex-col md:flex-row md:items-center md:justify-between gap-6 mb-12">
<div>
<h1 class="text-4xl md:text-5xl font-bold tracking-tight">Math Catalog</h1>
</div>
<div class="w-full md:w-80">
<input id="searchInput" type="text" placeholder="Search links..." class="w-full px-4 py-3 rounded-2xl bg-zinc-900 border border-zinc-800 focus:outline-none focus:ring-2 focus:ring-indigo-500" />
</div>
</header>
<div id="categories" class="space-y-12"></div>
</div>
<script>
const categoriesContainer = document.getElementById('categories')
const searchInput = document.getElementById('searchInput')
const state = { categories: {} }
function addCategory(title) {
if (state.categories[title]) return
const section = document.createElement('section')
section.className = 'category-section'
section.dataset.category = title
const header = document.createElement('div')
header.className = 'flex items-center justify-between cursor-pointer select-none mb-6 group'
const heading = document.createElement('h2')
heading.className = 'text-2xl font-semibold'
heading.textContent = title
const toggleIcon = document.createElement('span')
toggleIcon.className = 'transition-transform duration-300 text-zinc-400 group-hover:text-indigo-400'
toggleIcon.textContent = '▾'
header.appendChild(heading)
header.appendChild(toggleIcon)
const gridWrapper = document.createElement('div')
gridWrapper.className = 'overflow-visible transition-all duration-500 ease-in-out'
const grid = document.createElement('div')
grid.className = 'grid sm:grid-cols-2 lg:grid-cols-3 gap-6 overflow-visible'
grid.dataset.grid = title
gridWrapper.appendChild(grid)
section.appendChild(header)
section.appendChild(gridWrapper)
categoriesContainer.appendChild(section)
let collapsed = false
header.addEventListener('click', () => {
collapsed = !collapsed
gridWrapper.classList.toggle('max-h-0', collapsed)
gridWrapper.classList.toggle('opacity-0', collapsed)
gridWrapper.classList.toggle('mb-0', collapsed)
gridWrapper.classList.toggle('max-h-[2000px]', !collapsed)
gridWrapper.classList.toggle('opacity-100', !collapsed)
toggleIcon.classList.toggle('rotate-180', collapsed)
})
gridWrapper.classList.add('max-h-[2000px]', 'opacity-100')
state.categories[title] = grid
}
function addLink(title, url, category) {
if (!state.categories[category]) addCategory(category)
const grid = state.categories[category]
const link = document.createElement('a')
link.href = url
link.target = '_blank'
link.className = 'link-card transform-gpu group p-6 rounded-2xl bg-zinc-900 border border-zinc-800 hover:border-indigo-500 transition-all duration-300 hover:scale-[1.03] hover:-translate-y-1'
const wrapper = document.createElement('div')
wrapper.className = 'flex items-center justify-between'
const text = document.createElement('span')
text.className = 'text-lg font-medium'
text.textContent = title
const arrow = document.createElement('span')
arrow.className = 'text-zinc-500 group-hover:text-indigo-400 transition'
arrow.textContent = '↗'
wrapper.appendChild(text)
wrapper.appendChild(arrow)
link.appendChild(wrapper)
grid.appendChild(link)
link.classList.add('opacity-0', 'translate-y-2')
requestAnimationFrame(() => link.classList.remove('opacity-0', 'translate-y-2'))
}
searchInput.addEventListener('input', () => {
const query = searchInput.value.toLowerCase()
document.querySelectorAll('.link-card').forEach(card => {
card.classList.toggle('hidden', !card.innerText.toLowerCase().includes(query))
})
})
async function importFromJSON(path = 'mcatalog.json') {
try {
const res = await fetch(path)
const text = await res.text()
const cleanText = text.replace(/,\s*\]/g, ']').replace(/,\s*\}/g, '}')
const data = JSON.parse(cleanText)
if (data.categories && Array.isArray(data.categories)) {
data.categories.forEach(cat => {
addCategory(cat.title)
if (Array.isArray(cat.links)) {
cat.links.forEach(link => addLink(link.title, link.url, cat.title))
}
})
}
} catch (e) {
console.error('Failed to import JSON', e)
}
}
importFromJSON()
</script>
</body>
</html>