Repository navigation
Expand file tree
/
Copy pathindex.html
More file actions
215 lines (193 loc) · 11.8 KB
/
Copy pathindex.html
File metadata and controls
215 lines (193 loc) · 11.8 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
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Social Boosting Directory - Double Collapsible</title>
<script src="https://cdn.tailwindcss.com"></script>
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/collapse@3.x.x/dist/cdn.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Inter', sans-serif; scroll-behavior: smooth; }
[x-cloak] { display: none !important; }
.website-card { transition: all 0.3s ease; }
.service-link:hover { transform: translateX(5px); }
</style>
</head>
<body class="bg-gray-50 text-gray-800" x-data="boosterApp()" x-init="initData()">
<!-- Header -->
<header class="bg-gradient-to-r from-blue-700 to-indigo-800 text-white py-12 px-4 shadow-xl">
<div class="max-w-6xl mx-auto text-center">
<h1 class="text-3xl md:text-5xl font-extrabold mb-4 tracking-tight">Free Social Boosting</h1>
<p class="text-blue-100 mb-8 max-w-2xl mx-auto opacity-90">
Klik pada Website untuk melihat Platform, lalu klik Platform untuk melihat Layanan.
</p>
<!-- Main Search -->
<div class="relative max-w-2xl mx-auto">
<input
type="text"
placeholder="Cari Website, Platform, atau Layanan..."
class="w-full px-6 py-4 rounded-2xl text-gray-900 focus:outline-none focus:ring-4 focus:ring-blue-400 shadow-2xl"
x-model="searchQuery"
>
</div>
</div>
</header>
<main class="max-w-4xl mx-auto px-4 py-8">
<!-- Stats & Loading -->
<div class="mb-6 flex justify-between items-center">
<p class="text-gray-500 text-sm">
Menampilkan <span class="font-bold text-blue-600" x-text="filteredData.length"></span> Website
</p>
<div x-show="isLoading" class="animate-spin rounded-full h-5 w-5 border-b-2 border-blue-600"></div>
</div>
<!-- Double Hierarchical List -->
<div class="space-y-4" x-cloak>
<template x-for="site in filteredData" :key="site.name">
<!-- WEBSITE LEVEL COLLAPSIBLE -->
<div x-data="{ siteExpanded: false }"
x-init="$watch('searchQuery', value => { if(value.length > 0) siteExpanded = true })"
class="website-card bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
<!-- Website Header (Toggle Button) -->
<button @click="siteExpanded = !siteExpanded"
class="w-full bg-white px-6 py-5 flex justify-between items-center hover:bg-gray-50 transition-colors">
<div class="flex flex-col items-start">
<h2 class="text-xl font-black text-gray-800 uppercase tracking-tight" x-text="site.name"></h2>
<div class="flex gap-2 mt-1">
<span class="text-[10px] font-bold text-blue-600 uppercase tracking-widest" x-text="site.platforms.length + ' Platforms'"></span>
<span class="text-[10px] font-bold text-gray-400 uppercase tracking-widest">•</span>
<span class="text-[10px] font-bold text-indigo-600 uppercase tracking-widest" x-text="countServices(site) + ' Services'"></span>
</div>
</div>
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-gray-400 transition-transform duration-300" :class="siteExpanded ? 'rotate-180' : ''" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<!-- Website Content (Platform List) -->
<div x-show="siteExpanded" x-collapse>
<div class="p-4 pt-0 space-y-3 bg-gray-50/50">
<template x-for="platform in site.platforms" :key="platform.name">
<!-- PLATFORM LEVEL COLLAPSIBLE -->
<div x-data="{ platExpanded: false }"
x-init="$watch('searchQuery', value => { if(value.length > 0) platExpanded = true })"
class="bg-white border border-gray-100 rounded-xl overflow-hidden shadow-sm">
<!-- Platform Header (Toggle Button) -->
<button @click="platExpanded = !platExpanded"
class="w-full flex justify-between items-center p-4 hover:bg-blue-50/50 transition-colors group">
<h3 class="text-xs font-bold text-gray-500 group-hover:text-blue-600 uppercase tracking-widest flex items-center">
<span class="w-2 h-2 bg-blue-500 rounded-full mr-3"></span>
<span x-text="platform.name"></span>
<span class="ml-2 text-[10px] text-gray-300" x-text="'(' + platform.services.length + ')'"></span>
</h3>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-300 transition-transform duration-300" :class="platExpanded ? 'rotate-180' : ''" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<!-- Service List Content -->
<div x-show="platExpanded" x-collapse>
<div class="p-4 pt-0 grid grid-cols-1 gap-2">
<template x-for="service in platform.services" :key="service.type">
<a :href="service.url" target="_blank"
class="service-link group flex justify-between items-center p-3 rounded-xl bg-gray-50 hover:bg-blue-600 hover:text-white transition-all duration-200">
<span class="font-medium text-sm" x-text="service.type"></span>
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 opacity-0 group-hover:opacity-100 transition-opacity" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
</svg>
</a>
</template>
</div>
</div>
</div>
</template>
</div>
</div>
</div>
</template>
</div>
<!-- Empty State -->
<div x-show="filteredData.length === 0 && !isLoading" class="text-center py-20 bg-white rounded-3xl border-2 border-dashed border-gray-100">
<p class="text-gray-400 text-lg">Tidak ada hasil yang ditemukan.</p>
</div>
</main>
<footer class="text-center py-12 text-gray-400 text-[10px] uppercase tracking-[0.2em] font-bold">
© 2024 Social Boosting Directory • Markdown Powered
</footer>
<script>
function boosterApp() {
return {
isLoading: true,
searchQuery: '',
sites: [],
async initData() {
try {
const response = await fetch('README.md');
const text = await response.text();
this.sites = this.parseMarkdown(text);
} catch (error) {
console.error("Gagal memuat README.md", error);
} finally {
this.isLoading = false;
}
},
countServices(site) {
return site.platforms.reduce((acc, p) => acc + p.services.length, 0);
},
parseMarkdown(md) {
const lines = md.split('\n');
const sites = [];
let currentSite = null;
let currentPlatform = null;
lines.forEach(line => {
if (line.startsWith('# ')) {
currentSite = {
name: line.replace(/#\s?(\d+\.)?/, '').trim(),
platforms: []
};
sites.push(currentSite);
currentPlatform = null;
}
else if (currentSite && (line.startsWith(' * ') || line.startsWith(' - '))) {
currentPlatform = {
name: line.replace(/[*-]/, '').trim(),
services: []
};
currentSite.platforms.push(currentPlatform);
}
else if (currentPlatform && (line.startsWith(' * ') || line.startsWith(' - '))) {
const linkMatch = line.match(/\[(.*?)\]\((.*?)\)/);
if (linkMatch) {
currentPlatform.services.push({
type: linkMatch[1],
url: linkMatch[2]
});
} else {
const textOnly = line.replace(/[*-]/, '').trim();
if(textOnly) {
currentPlatform.services.push({ type: textOnly, url: '#' });
}
}
}
});
return sites;
},
get filteredData() {
if (!this.searchQuery) return this.sites;
const query = this.searchQuery.toLowerCase();
return this.sites.map(site => {
const filteredPlatforms = site.platforms.map(platform => {
const filteredServices = platform.services.filter(service =>
site.name.toLowerCase().includes(query) ||
platform.name.toLowerCase().includes(query) ||
service.type.toLowerCase().includes(query)
);
return { ...platform, services: filteredServices };
}).filter(p => p.services.length > 0 || p.name.toLowerCase().includes(query));
return { ...site, platforms: filteredPlatforms };
}).filter(s => s.platforms.length > 0 || s.name.toLowerCase().includes(query));
}
}
}
</script>
</body>
</html>