+3
README.md
+3
README.md
···
11
11
"url": "https://ypds.example.com",
12
12
"supportedHandles": ["*.example.com", "*.example.net"],
13
13
"maintainer": "@your-handle.example.com",
14
+
"serverLocation": "United States",
14
15
"tosUrl": "https://pds.example.com/terms",
15
16
"privacyUrl": "https://pds.example.com/privacy",
16
17
"inviteCodeRequired": false
···
24
25
| `url` | string | ✅ Yes | The base URL of your PDS server (must include https://) |
25
26
| `supportedHandles` | array of strings | ✅ Yes | Domain patterns for handles your PDS supports (e.g., `*.example.com`) |
26
27
| `maintainer` | string | ✅ Yes | Bluesky handle of the server maintainer (format: `@handle.domain.com`) |
28
+
| `serverLocation` | string | ⚠️ Optional | Geographic location of the server (e.g., `United States`, `European Union`, `Canada`) |
27
29
| `contactEmail` | string | ⚠️ Optional | Contact email for the PDS administrator |
28
30
| `tosUrl` | string | ⚠️ Optional | URL to your Terms of Service page |
29
31
| `privacyUrl` | string | ⚠️ Optional | URL to your Privacy Policy page |
···
39
41
"*.myserver.org"
40
42
],
41
43
"maintainer": "@admin.myserver.com",
44
+
"serverLocation": "Canada",
42
45
"contactEmail": "admin@myserver.com",
43
46
"tosUrl": "https://pds.myserver.com/terms-of-service",
44
47
"privacyUrl": "https://pds.myserver.com/privacy-policy",
+81
-5
app.js
+81
-5
app.js
···
1
1
// Load PDS data and populate table
2
2
document.addEventListener('DOMContentLoaded', function() {
3
+
// Check if mobile and show popup
4
+
if (window.innerWidth < 768) {
5
+
document.getElementById('mobile-popup').style.display = 'flex';
6
+
}
7
+
8
+
// Close mobile popup button
9
+
document.getElementById('close-mobile-popup').addEventListener('click', function() {
10
+
document.getElementById('mobile-popup').style.display = 'none';
11
+
});
12
+
13
+
// Search input
14
+
document.getElementById('search-input').addEventListener('input', filterData);
15
+
16
+
// Invite filter radios
17
+
document.querySelectorAll('input[name="invite-filter"]').forEach(radio => {
18
+
radio.addEventListener('change', filterData);
19
+
});
20
+
3
21
loadPDSData();
4
22
});
23
+
24
+
// Store all PDS data globally for filtering
25
+
let allPDSData = [];
5
26
6
27
async function loadPDSData() {
7
28
const loading = document.getElementById('loading');
···
13
34
try {
14
35
const response = await fetch('./pdslist.json');
15
36
const pdsData = await response.json();
37
+
allPDSData = pdsData; // Store for filtering
16
38
17
39
// Hide loading
18
40
loading.style.display = 'none';
···
31
53
const row = createTableRow(pds);
32
54
tableBody.appendChild(row);
33
55
});
56
+
57
+
// Set initial filtered count to total
58
+
document.getElementById('filtered-count').textContent = pdsData.length;
34
59
35
60
} catch (error) {
36
61
console.error('Error loading PDS data:', error);
···
38
63
}
39
64
}
40
65
66
+
function filterData() {
67
+
const searchTerm = document.getElementById('search-input').value.toLowerCase();
68
+
const inviteFilter = document.querySelector('input[name="invite-filter"]:checked').value;
69
+
70
+
const tableBody = document.getElementById('table-body');
71
+
const rows = tableBody.querySelectorAll('tr');
72
+
let visibleCount = 0;
73
+
74
+
rows.forEach((row, index) => {
75
+
const pds = allPDSData[index];
76
+
if (shouldShowRow(pds, searchTerm, inviteFilter)) {
77
+
row.style.display = '';
78
+
visibleCount++;
79
+
} else {
80
+
row.style.display = 'none';
81
+
}
82
+
});
83
+
84
+
// Update the filtered count
85
+
document.getElementById('filtered-count').textContent = visibleCount;
86
+
}
87
+
88
+
function shouldShowRow(pds, searchTerm, inviteFilter) {
89
+
// Apply search filter
90
+
const searchMatch = !searchTerm ||
91
+
pds.url.toLowerCase().includes(searchTerm) ||
92
+
(pds.supportedHandles && pds.supportedHandles.some(h => h.toLowerCase().includes(searchTerm))) ||
93
+
(pds.maintainer && pds.maintainer.toLowerCase().includes(searchTerm)) ||
94
+
(pds.contactEmail && pds.contactEmail.toLowerCase().includes(searchTerm));
95
+
96
+
// Apply invite filter
97
+
const inviteMatch = inviteFilter === 'all' ||
98
+
(inviteFilter === 'yes' && pds.inviteCodeRequired) ||
99
+
(inviteFilter === 'no' && !pds.inviteCodeRequired);
100
+
101
+
return searchMatch && inviteMatch;
102
+
}
103
+
41
104
function createTableRow(pds) {
42
105
const row = document.createElement('tr');
43
106
44
107
// URL
45
108
const urlCell = document.createElement('td');
46
-
const urlLink = document.createElement('a');
47
-
urlLink.href = pds.url;
109
+
const urlLink = document.createElement('p');
110
+
// sanitize URL by removing any '@' characters
111
+
const safeUrl = pds.url ? pds.url.replace(/@/g, '') : '';
112
+
urlLink.href = safeUrl;
48
113
urlLink.target = '_blank';
49
-
urlLink.textContent = pds.url;
114
+
urlLink.textContent = safeUrl;
50
115
urlCell.appendChild(urlLink);
51
116
row.appendChild(urlCell);
52
117
···
63
128
const maintainerCell = document.createElement('td');
64
129
if (pds.maintainer) {
65
130
const link = document.createElement('a');
66
-
link.href = `https://madebydanny.uk/followonbsky?did=${pds.maintainer}`;
131
+
// sanitize maintainer by removing any '@' characters before using in URL and display
132
+
const safeMaintainer = pds.maintainer.replace(/@/g, '');
133
+
link.href = `https://madebydanny.uk/followonbsky.html?did=${encodeURIComponent(safeMaintainer)}`;
67
134
link.target = '_blank';
68
-
link.textContent = pds.maintainer;
135
+
link.textContent = safeMaintainer;
69
136
maintainerCell.appendChild(link);
70
137
} else {
71
138
maintainerCell.textContent = '—';
72
139
}
73
140
row.appendChild(maintainerCell);
141
+
142
+
// Server Location
143
+
const locationCell = document.createElement('td');
144
+
if (pds.serverLocation) {
145
+
locationCell.textContent = pds.serverLocation;
146
+
} else {
147
+
locationCell.textContent = '—';
148
+
}
149
+
row.appendChild(locationCell);
74
150
75
151
// Contact Email
76
152
const emailCell = document.createElement('td');
+77
-28
index.html
+77
-28
index.html
···
3
3
<head>
4
4
<meta charset="UTF-8">
5
5
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6
-
<title>Bluesky PDS Directory</title>
6
+
<title>PDSList - A database of Bluesky PDSes </title>
7
+
<link rel="stylesheet" href="styles.css">
8
+
<meta name="description" content="A database of Bluesky PDSes that are 'open' - by madebydanny.uk">
9
+
<meta property="og:image" content="/media/ogimg.png">
10
+
<link rel="icon" href="/media/database-solid-full.svg" type="image/x-icon"/>
11
+
<link rel="shortcut icon" href="/media/database-solid-full.svg" type="image/x-icon"/>
7
12
</head>
8
13
<body>
9
-
<h1>Bluesky PDS Directory</h1>
10
-
<p>Community-maintained database of Personal Data Servers</p>
11
-
<p><b>NOTE: Data on this site is sourced from <a href="https://pdsls.dev">PDSls</a></b></p>
12
-
<p><a href="https://tangled.org/madebydanny.uk/pdslist#adding-your-pds">Add a PDS</a></p>
14
+
<div class="container">
15
+
<h1>PDSList - A database of Bluesky PDSes </h1>
16
+
<p>A database of Bluesky PDSes that are "open"</p>
17
+
<p><b>NOTE: Data on this site is sourced from the server's</b> <i>/xrpc/com.atproto.server.describeServer</i> <b>endpoint</b></p>
18
+
<p><a href="https://tangled.org/madebydanny.uk/pdslist#adding-your-pds">→ Add Your PDS</a></p>
19
+
20
+
<div id="loading">
21
+
<p>Loading database...</p>
22
+
</div>
13
23
14
-
<div id="loading">
15
-
<p>Loading database...</p>
16
-
</div>
24
+
<div id="content" style="display: none;">
25
+
<div class="stats">
26
+
<div class="stat">
27
+
<div class="stat-label">Total Servers</div>
28
+
<div class="stat-value" id="server-count">0</div>
29
+
</div>
30
+
<div class="stat">
31
+
<div class="stat-label">Showing</div>
32
+
<div class="stat-value" id="filtered-count">0</div>
33
+
</div>
34
+
</div>
17
35
18
-
<div id="content" style="display: none;">
19
-
<p>Total Servers: <strong id="server-count">0</strong></p>
20
-
21
-
<table id="pds-table" border="1" cellpadding="10" cellspacing="0">
22
-
<thead>
23
-
<tr>
24
-
<th>URL</th>
25
-
<th>Handles</th>
26
-
<th>Maintainer</th>
27
-
<th>Email</th>
28
-
<th>Invite Code Required</th>
29
-
<th>Terms</th>
30
-
<th>Privacy</th>
31
-
</tr>
32
-
</thead>
33
-
<tbody id="table-body">
34
-
</tbody>
35
-
</table>
36
+
<!-- Mobile Popup -->
37
+
<div id="mobile-popup" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 1000; justify-content: center; align-items: center;">
38
+
<div style="background: white; padding: 30px; border-radius: 8px; max-width: 400px; text-align: center; box-shadow: 0 4px 20px rgba(0,0,0,0.2);">
39
+
<h2 style="margin-top: 0; color: #0066cc;">Mobile Notice</h2>
40
+
<p>This site is best viewed on a desktop for the best experience.</p>
41
+
<p style="font-size: 14px; color: #666;">The database table is optimized for larger screens.</p>
42
+
<button id="close-mobile-popup" style="padding: 10px 20px; background: #0066cc; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 14px;">Got it, continue anyway</button>
43
+
</div>
44
+
</div>
45
+
46
+
<!-- Search and Filters -->
47
+
<div style="margin-bottom: 20px; padding: 15px; background: #f0f4f8; border-radius: 4px; border: 1px solid #ddd;">
48
+
<input type="text" id="search-input" placeholder="Search by URL, handles, maintainer, or email..." style="width: 100%; padding: 10px 12px; margin-bottom: 15px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px;">
49
+
50
+
<div style="display: flex; gap: 15px; flex-wrap: wrap; align-items: center;">
51
+
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
52
+
<input type="radio" name="invite-filter" value="all" checked>
53
+
<span>All Servers</span>
54
+
</label>
55
+
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
56
+
<input type="radio" name="invite-filter" value="yes">
57
+
<span>Invite Required</span>
58
+
</label>
59
+
<label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
60
+
<input type="radio" name="invite-filter" value="no">
61
+
<span>No Invite Required</span>
62
+
</label>
63
+
</div>
64
+
</div>
65
+
66
+
<div style="overflow-x: auto; -webkit-overflow-scrolling: touch;">
67
+
<table id="pds-table" border="1" cellpadding="10" cellspacing="0">
68
+
<thead>
69
+
<tr>
70
+
<th>URL</th>
71
+
<th>Handles</th>
72
+
<th>Maintainer</th>
73
+
<th>Server Location</th>
74
+
<th>Email</th>
75
+
<th>Invite Code Needed</th>
76
+
<th>Terms</th>
77
+
<th>Privacy</th>
78
+
</tr>
79
+
</thead>
80
+
<tbody id="table-body">
81
+
</tbody>
82
+
</table>
83
+
</div>
36
84
37
-
<div id="no-data" style="display: none;">
38
-
<p>No PDS servers found.</p>
85
+
<div id="no-data" style="display: none;">
86
+
<p>No PDS servers found.</p>
87
+
</div>
39
88
</div>
40
89
</div>
41
90
+1
media/database-solid-full.svg
+1
media/database-solid-full.svg
···
1
+
<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" viewBox="0 0 640 640"><!--!Font Awesome Free 7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--><path fill="#74C0FC" d="M544 269.8C529.2 279.6 512.2 287.5 494.5 293.8C447.5 310.6 385.8 320 320 320C254.2 320 192.4 310.5 145.5 293.8C127.9 287.5 110.8 279.6 96 269.8L96 352C96 396.2 196.3 432 320 432C443.7 432 544 396.2 544 352L544 269.8zM544 192L544 144C544 99.8 443.7 64 320 64C196.3 64 96 99.8 96 144L96 192C96 236.2 196.3 272 320 272C443.7 272 544 236.2 544 192zM494.5 453.8C447.6 470.5 385.9 480 320 480C254.1 480 192.4 470.5 145.5 453.8C127.9 447.5 110.8 439.6 96 429.8L96 496C96 540.2 196.3 576 320 576C443.7 576 544 540.2 544 496L544 429.8C529.2 439.6 512.2 447.5 494.5 453.8z"/></svg>
media/ogimg.png
media/ogimg.png
This is a binary file and will not be displayed.
+49
-29
pdslist.json
+49
-29
pdslist.json
···
1
1
[
2
2
{
3
-
"url": "https://bsky.social",
4
-
"supportedHandles": [
5
-
"*.bsky.social"
6
-
],
7
-
"maintainer": "@bsky.app",
8
-
"tosUrl": "https://bsky.social/about/support/tos",
9
-
"privacyUrl": "https://bsky.social/about/support/privacy-policy",
10
-
"inviteCodeRequired": false
11
-
},
12
-
{
13
-
"url": "https://pds.madebydanny.uk",
3
+
"url": "pds.madebydanny.uk",
14
4
"supportedHandles": [
15
5
".pds.madebydanny.uk",
16
6
".pds.danielmorrisey.com",
17
7
".good-example.com",
18
-
".mbdio.uk.",
19
-
"certifiedshitposter.com"
8
+
".mbdio.uk",
9
+
".certifiedshitposter.com"
20
10
],
11
+
"serverLocation": "New York, US",
21
12
"contactEmail": "danielmorrisey@pm.me",
22
13
"maintainer": "@madebydanny.uk",
23
14
"tosUrl": "https://pds.madebydanny.uk/about/terms.html",
···
25
16
"inviteCodeRequired": false
26
17
},
27
18
{
28
-
"url": "https://blacksky.app",
19
+
"url": "blacksky.app",
29
20
"supportedHandles": [
30
21
".myatproto.social",
31
22
".blacksky.app",
···
38
29
"inviteCodeRequired": false
39
30
},
40
31
{
41
-
"url": "https://northsky.social",
32
+
"url": "northsky.social",
42
33
"supportedHandles": [
43
34
".northsky.social"
44
35
],
···
48
39
"inviteCodeRequired": true
49
40
},
50
41
{
51
-
"url": "https://selfhosted.social",
42
+
"url": "selfhosted.social",
52
43
"supportedHandles": [
53
44
".selfhosted.social"
54
45
],
···
59
50
"inviteCodeRequired": false
60
51
},
61
52
{
62
-
"url": "https://altq.net",
53
+
"url": "altq.net",
63
54
"supportedHandles": [
64
55
".altq.net"
65
56
],
···
67
58
"inviteCodeRequired": "true"
68
59
},
69
60
{
70
-
"url": "https://tngl.sh",
61
+
"url": "tngl.sh",
71
62
"supportedHandles": [
72
63
".tngl.sh"
73
64
],
65
+
"contactEmail": "team@tangled.org",
74
66
"maintainer": "@tangled.org",
67
+
"privacyUrl": "https://tangled.org/privacy",
68
+
"tosUrl": "https://tangled.org/terms",
75
69
"inviteCodeRequired": "true"
76
70
},
77
71
{
78
-
"url": "https://pds.tgirl.cloud",
72
+
"url": "pds.tgirl.cloud",
79
73
"supportedHandles": [
80
74
".tgirl.beauty"
81
75
],
82
-
"maintainer": "tgirl.cloud",
76
+
"maintainer": "@tgirl.cloud",
83
77
"inviteCodeRequired": "true"
84
78
},
85
79
{
86
-
"url": "https://pds.witchcraft.systems",
80
+
"url": "pds.witchcraft.systems",
87
81
"supportedHandles": [
88
82
".pds.witchcraft.systems"
89
83
],
···
91
85
"inviteCodeRequired": "true"
92
86
},
93
87
{
94
-
"url": "https://evil.gay",
88
+
"url": "evil.gay",
95
89
"supportedHandles": [
96
90
".evil.gay",
97
91
".pds.mmatt.net"
···
100
94
"inviteCodeRequired": "true"
101
95
},
102
96
{
103
-
"url": "https://totallynotseth.dev",
97
+
"url": "totallynotseth.dev",
104
98
"supportedHandles": [
105
99
".totallynotseth.dev"
106
100
],
···
108
102
"inviteCodeRequired": "true"
109
103
},
110
104
{
111
-
"url": "https://pds.atpota.to",
105
+
"url": "pds.atpota.to",
112
106
"supportedHandles": [
113
107
".flush.es",
114
108
".on.anisota.net"
···
117
111
"inviteCodeRequired": "true"
118
112
},
119
113
{
120
-
"url": "https://katproto.girlonthemoon.xyz",
114
+
"url": "katproto.girlonthemoon.xyz",
121
115
"supportedHandles": [
122
116
".katproto.girlonthemoon.xyz"
123
117
],
···
126
120
"inviteCodeRequired": "true"
127
121
},
128
122
{
129
-
"url": "https://pds.tophhie.cloud",
123
+
"url": "pds.tophhie.cloud",
130
124
"supportedHandles": [
131
125
".tophhie.social"
132
126
],
···
137
131
"inviteCodeRequired": false
138
132
},
139
133
{
140
-
"url": "https://pds.sprk.so",
134
+
"url": "pds.sprk.so",
141
135
"supportedHandles": [
142
136
".sprk.so"
143
137
],
···
148
142
"inviteCodeRequired": false
149
143
},
150
144
{
151
-
"url": "https://zio.blue",
145
+
"url": "zio.blue",
152
146
"supportedHandles": [
153
147
".zio.blue"
154
148
],
···
156
150
"inviteCodeRequired": false
157
151
},
158
152
{
159
-
"url": "https://peedee.es",
153
+
"url": "peedee.es",
160
154
"supportedHandles": [
161
155
".peedee.es"
162
156
],
···
164
158
"maintainer": "@angrydutchman.peedee.es",
165
159
"privacyUrl": "https://peedee.es/@peedee/md/privacy.html",
166
160
"tosUrl": "https://peedee.es/@peedee/md/terms.html",
161
+
"inviteCodeRequired": true
162
+
},
163
+
{
164
+
"url": "caramelo.social.br",
165
+
"supportedHandles": [
166
+
".caramelo.social.br"
167
+
],
168
+
"contactEmail": "atlas@caramelo.social.br",
169
+
"maintainer": "@luan.caramelo.social.br",
170
+
"inviteCodeRequired": true
171
+
},
172
+
{
173
+
"url": "micro.blog.br",
174
+
"supportedHandles": [
175
+
".micro.blog.br"
176
+
],
177
+
"maintainer": "@joseli.to",
178
+
"inviteCodeRequired": true
179
+
},
180
+
{
181
+
"url": "at.app.wafrn.net",
182
+
"supportedHandles": [
183
+
".at.app.wafrn.net"
184
+
],
185
+
"contactEmail": "info@wafrn.net",
186
+
"privacyUrl": "https://app.wafrn.net/article/system.privacy-policy",
167
187
"inviteCodeRequired": true
168
188
}
169
189
]
+326
-118
styles.css
+326
-118
styles.css
···
1
1
* {
2
-
margin: 0;
3
-
padding: 0;
4
-
box-sizing: border-box;
2
+
margin: 0;
3
+
padding: 0;
4
+
box-sizing: border-box;
5
5
}
6
6
7
7
body {
8
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
9
-
background-color: #f5f5f5;
10
-
color: #333;
11
-
line-height: 1.6;
8
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
9
+
padding: 20px;
10
+
background-color: #f8f9fa;
11
+
color: #333;
12
+
line-height: 1.6;
12
13
}
13
14
14
15
.container {
15
-
max-width: 1200px;
16
-
margin: 0 auto;
17
-
padding: 20px;
16
+
max-width: 1200px;
17
+
margin: 0 auto;
18
+
background-color: white;
19
+
padding: 30px;
20
+
border-radius: 8px;
21
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
18
22
}
19
23
20
-
header {
21
-
background-color: white;
22
-
padding: 30px 0;
23
-
margin-bottom: 30px;
24
-
border-bottom: 2px solid #e0e0e0;
24
+
h1 {
25
+
color: #1a1a1a;
26
+
margin-bottom: 10px;
27
+
font-size: 28px;
25
28
}
26
29
27
-
h1 {
28
-
font-size: 32px;
29
-
margin-bottom: 10px;
30
+
h2 {
31
+
color: #0066cc;
32
+
margin-top: 20px;
33
+
margin-bottom: 15px;
34
+
font-size: 22px;
30
35
}
31
36
32
-
.subtitle {
33
-
color: #666;
34
-
font-size: 16px;
35
-
margin-bottom: 20px;
37
+
p {
38
+
margin-bottom: 15px;
39
+
color: #555;
36
40
}
37
41
38
-
.header-links {
39
-
display: flex;
40
-
gap: 15px;
42
+
a {
43
+
color: #0066cc;
44
+
text-decoration: none;
45
+
transition: color 0.2s;
41
46
}
42
47
43
-
.header-link {
44
-
color: #0066cc;
45
-
text-decoration: none;
46
-
font-size: 14px;
48
+
a:hover {
49
+
color: #0052a3;
50
+
text-decoration: underline;
47
51
}
48
52
49
-
.header-link:hover {
50
-
text-decoration: underline;
53
+
#loading {
54
+
text-align: center;
55
+
padding: 40px;
56
+
font-size: 18px;
57
+
color: #666;
51
58
}
52
59
53
-
.loading {
54
-
text-align: center;
55
-
padding: 40px;
60
+
#content {
61
+
padding: 20px 0;
56
62
}
57
63
58
-
.spinner {
59
-
border: 4px solid #e0e0e0;
60
-
border-top: 4px solid #0066cc;
61
-
border-radius: 50%;
62
-
width: 40px;
63
-
height: 40px;
64
-
animation: spin 1s linear infinite;
65
-
margin: 0 auto 20px;
64
+
/* Tables */
65
+
table {
66
+
width: 100%;
67
+
border-collapse: collapse;
68
+
margin: 20px 0;
69
+
font-size: 14px;
70
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
71
+
overflow-x: auto;
72
+
display: block;
66
73
}
67
74
68
-
@keyframes spin {
69
-
0% { transform: rotate(0deg); }
70
-
100% { transform: rotate(360deg); }
75
+
table th,
76
+
table td {
77
+
padding: 12px;
78
+
text-align: left;
79
+
border-bottom: 1px solid #e0e0e0;
71
80
}
72
81
73
-
.content {
74
-
background-color: white;
75
-
padding: 20px;
76
-
border-radius: 4px;
82
+
table th {
83
+
background-color: #0066cc;
84
+
color: white;
85
+
font-weight: 600;
86
+
border-bottom: 2px solid #0052a3;
87
+
position: sticky;
88
+
top: 0;
77
89
}
78
90
79
-
.content.hidden,
80
-
.no-data.hidden,
81
-
#loading.hidden {
82
-
display: none;
91
+
table tr {
92
+
display: table;
93
+
width: 100%;
94
+
table-layout: fixed;
83
95
}
84
96
85
-
.stats {
86
-
margin-bottom: 20px;
87
-
padding-bottom: 20px;
88
-
border-bottom: 1px solid #e0e0e0;
97
+
table tr:hover {
98
+
background-color: #f5f5f5;
89
99
}
90
100
91
-
.stat-item {
92
-
font-size: 16px;
101
+
table a {
102
+
color: #0066cc;
103
+
word-break: break-all;
93
104
}
94
105
95
-
.stat-item strong {
96
-
font-weight: 600;
106
+
table a:hover {
107
+
text-decoration: underline;
97
108
}
98
109
99
-
.pds-table {
100
-
width: 100%;
101
-
border-collapse: collapse;
102
-
margin-top: 20px;
110
+
/* Make Terms and Privacy columns smaller */
111
+
table th:nth-child(7),
112
+
table th:nth-child(8),
113
+
table td:nth-child(7),
114
+
table td:nth-child(8) {
115
+
padding: 4px 2px;
116
+
font-size: 12px;
117
+
text-align: center;
118
+
width: 60px;
119
+
}
120
+
table th:nth-child(6),
121
+
table td:nth-child(6),
122
+
table th:nth-child(4),
123
+
table td:nth-child(4)
124
+
{
125
+
padding: 4px 2px;
126
+
font-size: 12px;
127
+
text-align: center;
128
+
width: 90px;
129
+
}
130
+
/* Responsive Table */
131
+
@media (max-width: 1024px) {
132
+
table {
133
+
font-size: 13px;
134
+
}
135
+
136
+
table th,
137
+
table td {
138
+
padding: 10px 8px;
139
+
}
140
+
table th:nth-child(6),
141
+
table td:nth-child(6),
142
+
table th:nth-child(4),
143
+
table td:nth-child(4),
144
+
table th:nth-child(7),
145
+
table th:nth-child(8),
146
+
table td:nth-child(7),
147
+
table td:nth-child(8) {
148
+
padding: 8px 4px;
149
+
width: 50px;
150
+
}
103
151
}
104
152
105
-
.pds-table thead {
106
-
background-color: #f9f9f9;
107
-
border-bottom: 2px solid #e0e0e0;
153
+
@media (max-width: 768px) {
154
+
table {
155
+
font-size: 12px;
156
+
}
157
+
158
+
table th,
159
+
table td {
160
+
padding: 8px 6px;
161
+
word-break: break-word;
162
+
}
163
+
164
+
/* Hide certain columns on mobile */
165
+
table th:nth-child(4),
166
+
table th:nth-child(5),
167
+
table th:nth-child(7),
168
+
table th:nth-child(8),
169
+
table td:nth-child(4),
170
+
table td:nth-child(5),
171
+
table td:nth-child(7),
172
+
table td:nth-child(8) {
173
+
display: none;
174
+
}
175
+
176
+
/* Make URL and Maintainer wider on mobile */
177
+
table th:nth-child(1),
178
+
table th:nth-child(3),
179
+
table td:nth-child(1),
180
+
table td:nth-child(3) {
181
+
min-width: 100px;
182
+
}
183
+
184
+
.container {
185
+
padding: 15px;
186
+
}
187
+
188
+
h1 {
189
+
font-size: 20px;
190
+
}
108
191
}
109
192
110
-
.pds-table th {
111
-
text-align: left;
112
-
padding: 15px;
113
-
font-weight: 600;
114
-
font-size: 14px;
115
-
text-transform: uppercase;
116
-
color: #666;
193
+
@media (max-width: 480px) {
194
+
table {
195
+
font-size: 11px;
196
+
}
197
+
198
+
table th,
199
+
table td {
200
+
padding: 6px 4px;
201
+
}
202
+
203
+
/* On very small screens, show only essential columns */
204
+
table th:nth-child(2),
205
+
table th:nth-child(6),
206
+
table td:nth-child(2),
207
+
table td:nth-child(6) {
208
+
display: none;
209
+
}
210
+
211
+
/* Make columns more compact */
212
+
table th:nth-child(1),
213
+
table th:nth-child(3),
214
+
table td:nth-child(1),
215
+
table td:nth-child(3) {
216
+
min-width: 80px;
217
+
max-width: 150px;
218
+
}
219
+
220
+
.container {
221
+
padding: 10px;
222
+
}
223
+
224
+
h1 {
225
+
font-size: 18px;
226
+
}
227
+
228
+
p {
229
+
font-size: 13px;
230
+
}
117
231
}
118
232
119
-
.pds-table td {
120
-
padding: 12px 15px;
121
-
border-bottom: 1px solid #e0e0e0;
122
-
font-size: 14px;
123
-
vertical-align: top;
233
+
/* Buttons */
234
+
button {
235
+
padding: 12px 16px;
236
+
border: none;
237
+
border-radius: 4px;
238
+
cursor: pointer;
239
+
font-size: 15px;
240
+
font-weight: 600;
241
+
transition: all 0.3s ease;
242
+
width: 100%;
243
+
margin-bottom: 10px;
124
244
}
125
245
126
-
.pds-table tbody tr:hover {
127
-
background-color: #f9f9f9;
246
+
button:hover {
247
+
transform: translateY(-2px);
248
+
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
128
249
}
129
250
130
-
.pds-table a {
131
-
color: #0066cc;
132
-
text-decoration: none;
133
-
word-break: break-all;
251
+
#toggle-pds {
252
+
background: #0066cc;
253
+
color: white;
134
254
}
135
255
136
-
.pds-table a:hover {
137
-
text-decoration: underline;
256
+
#toggle-pds:hover {
257
+
background: #0052a3;
138
258
}
139
259
140
-
.handles-container {
141
-
display: flex;
142
-
flex-wrap: wrap;
143
-
gap: 6px;
260
+
#toggle-bsky {
261
+
background: #0066cc;
262
+
color: white;
144
263
}
145
264
146
-
.handle-tag {
147
-
background-color: #e8f0ff;
148
-
color: #0066cc;
149
-
padding: 4px 8px;
150
-
border-radius: 3px;
151
-
font-size: 12px;
152
-
white-space: nowrap;
153
-
border: 1px solid #cce0ff;
265
+
#toggle-bsky:hover {
266
+
background: #0052a3;
154
267
}
155
268
156
-
.link-button {
157
-
display: inline-block;
158
-
background-color: #0066cc;
159
-
color: white;
160
-
padding: 6px 12px;
161
-
border-radius: 3px;
162
-
font-size: 12px;
163
-
text-decoration: none;
164
-
transition: background-color 0.2s;
269
+
/* Sections */
270
+
#pds-section,
271
+
#bsky-section,
272
+
#cool-section {
273
+
margin-top: 15px;
274
+
padding: 15px;
275
+
background: #f9f9f9;
276
+
border-radius: 4px;
277
+
border-left: 4px solid #0066cc;
165
278
}
166
279
167
-
.link-button:hover {
168
-
background-color: #0052a3;
169
-
text-decoration: none;
280
+
/* Search and Filters */
281
+
#search-input {
282
+
width: 100%;
283
+
padding: 10px 12px;
284
+
margin-bottom: 10px;
285
+
border: 1px solid #ddd;
286
+
border-radius: 4px;
287
+
font-size: 14px;
288
+
}
289
+
290
+
#search-input:focus {
291
+
outline: none;
292
+
border-color: #0066cc;
293
+
box-shadow: 0 0 0 3px rgba(0, 102, 204, 0.1);
294
+
}
295
+
296
+
#filters {
297
+
margin-bottom: 20px;
298
+
padding: 15px;
299
+
background: #f0f4f8;
300
+
border-radius: 4px;
301
+
border: 1px solid #ddd;
302
+
}
303
+
304
+
#filters label {
305
+
display: inline-flex;
306
+
align-items: center;
307
+
gap: 8px;
308
+
margin-right: 15px;
309
+
cursor: pointer;
310
+
color: #333;
311
+
}
312
+
313
+
#filters input[type="radio"] {
314
+
cursor: pointer;
315
+
width: 18px;
316
+
height: 18px;
317
+
}
318
+
319
+
/* Mobile Popup Styles */
320
+
#mobile-popup {
321
+
backdrop-filter: blur(4px);
322
+
}
323
+
324
+
#mobile-popup button {
325
+
padding: 10px 20px !important;
326
+
width: auto !important;
327
+
margin: 0 !important;
328
+
}
329
+
330
+
/* Info boxes */
331
+
#no-data {
332
+
text-align: center;
333
+
padding: 40px 20px;
334
+
color: #999;
335
+
font-size: 16px;
170
336
}
171
337
172
-
.no-data {
173
-
text-align: center;
174
-
padding: 40px;
175
-
color: #999;
338
+
.stats {
339
+
display: flex;
340
+
gap: 20px;
341
+
margin-bottom: 20px;
342
+
flex-wrap: wrap;
343
+
}
344
+
345
+
.stat {
346
+
padding: 15px;
347
+
background: linear-gradient(135deg, #0066cc 0%, #0052a3 100%);
348
+
color: white;
349
+
border-radius: 4px;
350
+
font-weight: 600;
351
+
min-width: 200px;
352
+
}
353
+
354
+
.stat-label {
355
+
font-size: 12px;
356
+
opacity: 0.9;
357
+
margin-bottom: 5px;
358
+
}
359
+
360
+
.stat-value {
361
+
font-size: 24px;
176
362
}
177
363
178
-
.hidden {
179
-
display: none;
364
+
@media (max-width: 768px) {
365
+
.stat {
366
+
min-width: 150px;
367
+
flex: 1;
368
+
}
369
+
370
+
.stat-value {
371
+
font-size: 20px;
372
+
}
180
373
}
374
+
375
+
@media (max-width: 480px) {
376
+
.stats {
377
+
gap: 10px;
378
+
}
379
+
380
+
.stat {
381
+
min-width: 120px;
382
+
padding: 12px;
383
+
}
384
+
385
+
.stat-value {
386
+
font-size: 18px;
387
+
}
388
+
}