My nix-darwin and NixOS config
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

server/pds: migrate pds-landing to pkgs monorepo

-732
modules/server/pds-landing/assets/icon/android-icon-192x192.png

This is a binary file and will not be displayed.

modules/server/pds-landing/assets/icon/apple-icon-114x114.png

This is a binary file and will not be displayed.

modules/server/pds-landing/assets/icon/apple-icon-120x120.png

This is a binary file and will not be displayed.

modules/server/pds-landing/assets/icon/apple-icon-144x144.png

This is a binary file and will not be displayed.

modules/server/pds-landing/assets/icon/apple-icon-152x152.png

This is a binary file and will not be displayed.

modules/server/pds-landing/assets/icon/apple-icon-180x180.png

This is a binary file and will not be displayed.

modules/server/pds-landing/assets/icon/apple-icon-57x57.png

This is a binary file and will not be displayed.

modules/server/pds-landing/assets/icon/apple-icon-60x60.png

This is a binary file and will not be displayed.

modules/server/pds-landing/assets/icon/apple-icon-72x72.png

This is a binary file and will not be displayed.

modules/server/pds-landing/assets/icon/apple-icon-76x76.png

This is a binary file and will not be displayed.

-2
modules/server/pds-landing/assets/icon/browserconfig.xml
··· 1 - <?xml version="1.0" encoding="utf-8"?> 2 - <browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>
modules/server/pds-landing/assets/icon/favicon-16x16.png

This is a binary file and will not be displayed.

modules/server/pds-landing/assets/icon/favicon-256x256.png

This is a binary file and will not be displayed.

modules/server/pds-landing/assets/icon/favicon-32x32.png

This is a binary file and will not be displayed.

modules/server/pds-landing/assets/icon/favicon-96x96.png

This is a binary file and will not be displayed.

modules/server/pds-landing/assets/icon/favicon.ico

This is a binary file and will not be displayed.

-41
modules/server/pds-landing/assets/icon/manifest.json
··· 1 - { 2 - "name": "App", 3 - "icons": [ 4 - { 5 - "src": "\/android-icon-36x36.png", 6 - "sizes": "36x36", 7 - "type": "image\/png", 8 - "density": "0.75" 9 - }, 10 - { 11 - "src": "\/android-icon-48x48.png", 12 - "sizes": "48x48", 13 - "type": "image\/png", 14 - "density": "1.0" 15 - }, 16 - { 17 - "src": "\/android-icon-72x72.png", 18 - "sizes": "72x72", 19 - "type": "image\/png", 20 - "density": "1.5" 21 - }, 22 - { 23 - "src": "\/android-icon-96x96.png", 24 - "sizes": "96x96", 25 - "type": "image\/png", 26 - "density": "2.0" 27 - }, 28 - { 29 - "src": "\/android-icon-144x144.png", 30 - "sizes": "144x144", 31 - "type": "image\/png", 32 - "density": "3.0" 33 - }, 34 - { 35 - "src": "\/android-icon-192x192.png", 36 - "sizes": "192x192", 37 - "type": "image\/png", 38 - "density": "4.0" 39 - } 40 - ] 41 - }
modules/server/pds-landing/assets/icon/ms-icon-144x144.png

This is a binary file and will not be displayed.

modules/server/pds-landing/assets/icon/ms-icon-150x150.png

This is a binary file and will not be displayed.

modules/server/pds-landing/assets/icon/ms-icon-310x310.png

This is a binary file and will not be displayed.

modules/server/pds-landing/assets/icon/ms-icon-70x70.png

This is a binary file and will not be displayed.

-78
modules/server/pds-landing/assets/thumb.svg
··· 1 - <svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630" viewBox="0 0 1200 630"> 2 - <defs> 3 - <!-- grid pattern background --> 4 - <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse"> 5 - <path d="M 40 0 L 0 0 0 40" fill="none" stroke="#1e3328" stroke-width="0.5" opacity="0.4"/> 6 - </pattern> 7 - <!-- subtle vignette --> 8 - <radialGradient id="vignette" cx="50%" cy="50%" r="70%"> 9 - <stop offset="0%" stop-color="transparent"/> 10 - <stop offset="100%" stop-color="#0d1210" stop-opacity="0.6"/> 11 - </radialGradient> 12 - <!-- card shadow --> 13 - <filter id="shadow" x="-5%" y="-5%" width="110%" height="120%"> 14 - <feDropShadow dx="0" dy="8" stdDeviation="24" flood-color="#000000" flood-opacity="0.6"/> 15 - </filter> 16 - </defs> 17 - 18 - <!-- background --> 19 - <rect width="1200" height="630" fill="#0d1210"/> 20 - <rect width="1200" height="630" fill="url(#grid)"/> 21 - <rect width="1200" height="630" fill="url(#vignette)"/> 22 - 23 - <!-- card --> 24 - <g filter="url(#shadow)"> 25 - <rect x="160" y="90" width="880" height="450" rx="10" fill="#141c19" stroke="#2e3d34" stroke-width="1"/> 26 - <!-- outer glow ring --> 27 - <rect x="160" y="90" width="880" height="450" rx="10" fill="none" stroke="#a6e3a1" stroke-width="0.5" opacity="0.08"/> 28 - </g> 29 - 30 - <!-- titlebar --> 31 - <rect x="160" y="90" width="880" height="44" rx="10" fill="#243028"/> 32 - <rect x="160" y="114" width="880" height="20" fill="#243028"/> 33 - <rect x="160" y="91" width="880" height="1" fill="#2e3d34"/> 34 - <rect x="160" y="134" width="880" height="1" fill="#2e3d34"/> 35 - 36 - <!-- traffic lights --> 37 - <circle cx="196" cy="112" r="6" fill="#4a7a62" opacity="0.4"/> 38 - <circle cx="218" cy="112" r="6" fill="#4a7a62" opacity="0.4"/> 39 - <circle cx="240" cy="112" r="6" fill="#4a7a62" opacity="0.4"/> 40 - 41 - <!-- titlebar label --> 42 - <text x="264" y="117" font-family="'JetBrains Mono', 'Fira Mono', monospace" font-size="13" fill="#4a7a62">ewan's pds</text> 43 - 44 - <!-- prompt line --> 45 - <text x="200" y="210" font-family="'JetBrains Mono', 'Fira Mono', monospace" font-size="32" font-weight="700" fill="#a6e3a1">server@pds.ewancroft.uk</text> 46 - <text x="200" y="210" font-family="'JetBrains Mono', 'Fira Mono', monospace" font-size="32" font-weight="700" fill="#93b09a" opacity="0.6">:~</text> 47 - <!-- measure approx: "server@pds.ewancroft.uk" at 32px mono ≈ 23 chars × ~19.2px = ~442px, then ":~" ≈ 38px --> 48 - <text x="200" y="210" font-family="'JetBrains Mono', 'Fira Mono', monospace" font-size="32" font-weight="700"> 49 - <tspan fill="#a6e3a1">server@pds.ewancroft.uk</tspan><tspan fill="#93b09a" opacity="0.6">:~</tspan><tspan fill="#93b09a"> $</tspan> 50 - </text> 51 - 52 - <!-- tagline --> 53 - <text x="200" y="248" font-family="'JetBrains Mono', 'Fira Mono', monospace" font-size="15" fill="#4d6b58">Bluesky-compatible ATProto PDS · personal instance</text> 54 - 55 - <!-- divider --> 56 - <line x1="200" y1="272" x2="1000" y2="272" stroke="#1e3328" stroke-width="1" opacity="0.8"/> 57 - 58 - <!-- STATUS section --> 59 - <text x="200" y="300" font-family="'JetBrains Mono', 'Fira Mono', monospace" font-size="11" font-weight="700" letter-spacing="2" fill="#4d6b58">STATUS</text> 60 - 61 - <!-- kv rows --> 62 - <text x="200" y="328" font-family="'JetBrains Mono', 'Fira Mono', monospace" font-size="15" fill="#4a7a62" opacity="0.7">reachable</text> 63 - <text x="390" y="328" font-family="'JetBrains Mono', 'Fira Mono', monospace" font-size="15" fill="#a6e3a1">✓ online</text> 64 - 65 - <text x="200" y="356" font-family="'JetBrains Mono', 'Fira Mono', monospace" font-size="15" fill="#4a7a62" opacity="0.7">did</text> 66 - <text x="390" y="356" font-family="'JetBrains Mono', 'Fira Mono', monospace" font-size="15" fill="#cdd6f4" opacity="0.85">did:web:pds.ewancroft.uk</text> 67 - 68 - <text x="200" y="384" font-family="'JetBrains Mono', 'Fira Mono', monospace" font-size="15" fill="#4a7a62" opacity="0.7">invite required</text> 69 - <text x="390" y="384" font-family="'JetBrains Mono', 'Fira Mono', monospace" font-size="15" fill="#f9e2af">yes</text> 70 - 71 - <!-- divider --> 72 - <line x1="200" y1="410" x2="1000" y2="410" stroke="#1e3328" stroke-width="1" opacity="0.8"/> 73 - 74 - <!-- footer line --> 75 - <text x="600" y="475" font-family="'JetBrains Mono', 'Fira Mono', monospace" font-size="13" fill="#2e3d34" text-anchor="middle">powered by nixpkgs#bluesky-pds · atproto.com</text> 76 - 77 - 78 - </svg>
-36
modules/server/pds-landing/default.nix
··· 1 - { 2 - lib, 3 - stdenv, 4 - }: 5 - 6 - let 7 - # Explicitly reference the landing page files 8 - sourceFiles = lib.sourceByRegex ./. [ 9 - "^(index\.html|script\.js|status\.js|utils\.js)$" 10 - "^styles(/.*)?$" 11 - "^assets(/.*)?$" 12 - ]; 13 - in 14 - stdenv.mkDerivation { 15 - pname = "pds-landing"; 16 - version = "1.0.0"; 17 - 18 - src = sourceFiles; 19 - 20 - phases = [ 21 - "unpackPhase" 22 - "installPhase" 23 - ]; 24 - 25 - installPhase = '' 26 - mkdir -p $out 27 - cp -r . $out/ 28 - ''; 29 - 30 - meta = with lib; { 31 - description = "Ewan's personal PDS (Personal Data Server) landing page"; 32 - homepage = "https://pds.ewancroft.uk"; 33 - license = licenses.agpl3Only; 34 - platforms = platforms.all; 35 - }; 36 - }
-231
modules/server/pds-landing/index.html
··· 1 - <!doctype html> 2 - <html lang="en"> 3 - <head> 4 - <meta charset="UTF-8" /> 5 - <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 - <title>pds.ewancroft.uk — Ewan's ATProto PDS</title> 7 - 8 - <!-- Primary meta --> 9 - <meta 10 - name="description" 11 - content="Ewan's personal ATProto PDS — a self-hosted Bluesky-compatible server running at pds.ewancroft.uk." 12 - /> 13 - 14 - <!-- Open Graph --> 15 - <meta property="og:type" content="website" /> 16 - <meta property="og:url" content="https://pds.ewancroft.uk" /> 17 - <meta property="og:title" content="pds.ewancroft.uk — Ewan's ATProto PDS" /> 18 - <meta 19 - property="og:description" 20 - content="Ewan's personal ATProto PDS — a self-hosted Bluesky-compatible server running at pds.ewancroft.uk." 21 - /> 22 - <meta 23 - property="og:image" 24 - content="https://pds.ewancroft.uk/assets/thumb.svg" 25 - /> 26 - <meta property="og:image:width" content="1200" /> 27 - <meta property="og:image:height" content="630" /> 28 - <meta property="og:image:type" content="image/svg+xml" /> 29 - <meta 30 - property="og:image:alt" 31 - content="pds.ewancroft.uk — Ewan's self-hosted ATProto PDS" 32 - /> 33 - 34 - <!-- Twitter / Bluesky card --> 35 - <meta name="twitter:card" content="summary_large_image" /> 36 - <meta 37 - name="twitter:image" 38 - content="https://pds.ewancroft.uk/assets/thumb.svg" 39 - /> 40 - <meta 41 - name="twitter:image:alt" 42 - content="pds.ewancroft.uk — Ewan's self-hosted ATProto PDS" 43 - /> 44 - <meta 45 - name="twitter:title" 46 - content="pds.ewancroft.uk — Ewan's ATProto PDS" 47 - /> 48 - <meta 49 - name="twitter:description" 50 - content="Ewan's personal ATProto PDS — a self-hosted Bluesky-compatible server running at pds.ewancroft.uk." 51 - /> 52 - <link rel="preconnect" href="https://fonts.googleapis.com" crossorigin /> 53 - <link 54 - href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&display=swap" 55 - rel="stylesheet" 56 - /> 57 - <link rel="stylesheet" href="/style.css" /> 58 - 59 - <link rel="apple-touch-icon" sizes="57x57" href="/apple-icon-57x57.png" /> 60 - <link rel="apple-touch-icon" sizes="60x60" href="/apple-icon-60x60.png" /> 61 - <link rel="apple-touch-icon" sizes="72x72" href="/apple-icon-72x72.png" /> 62 - <link rel="apple-touch-icon" sizes="76x76" href="/apple-icon-76x76.png" /> 63 - <link 64 - rel="apple-touch-icon" 65 - sizes="114x114" 66 - href="/apple-icon-114x114.png" 67 - /> 68 - <link 69 - rel="apple-touch-icon" 70 - sizes="120x120" 71 - href="/apple-icon-120x120.png" 72 - /> 73 - <link 74 - rel="apple-touch-icon" 75 - sizes="144x144" 76 - href="/apple-icon-144x144.png" 77 - /> 78 - <link 79 - rel="apple-touch-icon" 80 - sizes="152x152" 81 - href="/apple-icon-152x152.png" 82 - /> 83 - <link 84 - rel="apple-touch-icon" 85 - sizes="180x180" 86 - href="/apple-icon-180x180.png" 87 - /> 88 - <link 89 - rel="icon" 90 - type="image/png" 91 - sizes="256x256" 92 - href="/favicon-256x256.png" 93 - /> 94 - <link 95 - rel="icon" 96 - type="image/png" 97 - sizes="192x192" 98 - href="/android-icon-192x192.png" 99 - /> 100 - <link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" /> 101 - <link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png" /> 102 - <link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" /> 103 - <link rel="manifest" href="/manifest.json" /> 104 - <meta name="msapplication-TileColor" content="#1a2420" /> 105 - <meta name="msapplication-TileImage" content="/ms-icon-144x144.png" /> 106 - <meta name="theme-color" content="#1a2420" /> 107 - </head> 108 - <body> 109 - <div class="card"> 110 - <div class="card-titlebar"> 111 - <span class="dot"></span> 112 - <span class="dot"></span> 113 - <span class="dot"></span> 114 - <span style="margin-left: 0.4rem">ewan's pds</span> 115 - </div> 116 - <div class="card-body"> 117 - <div class="prompt-line"> 118 - <span class="user-marker">server@pds.ewancroft.uk</span 119 - ><span class="prompt-path">:~</span 120 - ><span class="prompt-char"> $</span> 121 - </div> 122 - <p class="tagline"> 123 - Bluesky-compatible ATProto PDS · personal instance 124 - </p> 125 - 126 - <div class="section-label">status</div> 127 - <div class="kv-grid"> 128 - <span class="kv-key">reachable</span> 129 - <span class="kv-val loading" id="val-reachable">…</span> 130 - 131 - <span class="kv-key">version</span> 132 - <span class="kv-val loading" id="val-version">…</span> 133 - 134 - <span class="kv-key">did</span> 135 - <span class="kv-val loading" id="val-did">…</span> 136 - 137 - <span class="kv-key">accounts</span> 138 - <span class="kv-val loading" id="val-accounts">…</span> 139 - 140 - <span class="kv-key">invite required</span> 141 - <span class="kv-val loading" id="val-invite">…</span> 142 - 143 - <span class="kv-key" id="key-phone" style="display: none" 144 - >phone verify</span 145 - > 146 - <span class="kv-val" id="val-phone" style="display: none"></span> 147 - 148 - <span class="kv-key" id="key-domains" style="display: none" 149 - >user domains</span 150 - > 151 - <span class="kv-val" id="val-domains" style="display: none"></span> 152 - </div> 153 - 154 - <hr class="divider" /> 155 - 156 - <div class="section-label">endpoints</div> 157 - <p class="endpoints-note"> 158 - Most API routes are under <span class="highlight">/xrpc/</span> 159 - </p> 160 - <ul class="link-list"> 161 - <li> 162 - <a 163 - href="https://github.com/bluesky-social/atproto" 164 - target="_blank" 165 - rel="noopener" 166 - >atproto source code</a 167 - > 168 - </li> 169 - <li> 170 - <a 171 - href="https://github.com/bluesky-social/pds" 172 - target="_blank" 173 - rel="noopener" 174 - >self-hosting guide</a 175 - > 176 - </li> 177 - <li> 178 - <a href="https://atproto.com" target="_blank" rel="noopener" 179 - >protocol docs</a 180 - > 181 - </li> 182 - </ul> 183 - 184 - <hr class="divider" /> 185 - 186 - <div class="section-label">links</div> 187 - <ul class="link-list" id="links-list"> 188 - <li> 189 - <a href="https://witchsky.app" target="_blank" rel="noopener" 190 - >Witchsky Web Client</a 191 - > 192 - </li> 193 - </ul> 194 - 195 - <hr class="divider" /> 196 - 197 - <div class="section-label">contact</div> 198 - <p class="contact-note"> 199 - Send a mention on Bluesky to 200 - <a 201 - href="https://witchsky.app/profile/ewancroft.uk" 202 - target="_blank" 203 - rel="noopener" 204 - >@ewancroft.uk</a 205 - > 206 - </p> 207 - <p 208 - class="contact-note" 209 - id="val-contact-email" 210 - style="display: none; margin-top: 0.4rem" 211 - ></p> 212 - </div> 213 - </div> 214 - 215 - <footer> 216 - powered by 217 - <a 218 - href="https://search.nixos.org/packages?show=bluesky-pds" 219 - target="_blank" 220 - rel="noopener" 221 - >nixpkgs#bluesky-pds</a 222 - > 223 - &nbsp;·&nbsp; 224 - <a href="https://atproto.com" target="_blank" rel="noopener" 225 - >atproto.com</a 226 - > 227 - </footer> 228 - 229 - <script type="module" src="/script.js"></script> 230 - </body> 231 - </html>
-3
modules/server/pds-landing/script.js
··· 1 - import { loadStatus } from '/status.js'; 2 - 3 - loadStatus();
-94
modules/server/pds-landing/status.js
··· 1 - import { fetchJSON } from '/utils.js'; 2 - 3 - function show(id) { 4 - const el = document.getElementById(id); 5 - if (el) el.style.display = ''; 6 - } 7 - 8 - function set(id, text, cls) { 9 - const el = document.getElementById(id); 10 - if (!el) return; 11 - el.textContent = text; 12 - el.className = 'kv-val' + (cls ? ' ' + cls : ''); 13 - } 14 - 15 - export async function loadStatus() { 16 - // health 17 - try { 18 - const h = await fetchJSON('/xrpc/_health'); 19 - set('val-reachable', '✓ online', 'ok'); 20 - set('val-version', h.version ?? 'unknown'); 21 - } catch { 22 - set('val-reachable', '✗ unreachable', 'err'); 23 - set('val-version', '—', 'err'); 24 - } 25 - 26 - // description 27 - try { 28 - const d = await fetchJSON('/xrpc/com.atproto.server.describeServer'); 29 - set('val-did', d.did ?? '—'); 30 - set('val-invite', d.inviteCodeRequired ? 'yes' : 'no', 31 - d.inviteCodeRequired ? 'warn' : 'ok'); 32 - 33 - // phoneVerificationRequired (only show if present) 34 - if (typeof d.phoneVerificationRequired === 'boolean') { 35 - show('key-phone'); 36 - show('val-phone'); 37 - set('val-phone', d.phoneVerificationRequired ? 'yes' : 'no', 38 - d.phoneVerificationRequired ? 'warn' : 'ok'); 39 - } 40 - 41 - // availableUserDomains 42 - if (d.availableUserDomains?.length) { 43 - show('key-domains'); 44 - show('val-domains'); 45 - set('val-domains', d.availableUserDomains.join(', ')); 46 - } 47 - 48 - // links — append privacyPolicy / termsOfService to the links list 49 - if (d.links) { 50 - const list = document.getElementById('links-list'); 51 - const entries = [ 52 - ['privacyPolicy', 'Privacy Policy'], 53 - ['termsOfService', 'Terms of Service'], 54 - ]; 55 - for (const [key, label] of entries) { 56 - if (d.links[key]) { 57 - const li = document.createElement('li'); 58 - const a = document.createElement('a'); 59 - a.href = d.links[key]; 60 - a.target = '_blank'; 61 - a.rel = 'noopener'; 62 - a.textContent = label; 63 - li.appendChild(a); 64 - list.appendChild(li); 65 - } 66 - } 67 - } 68 - 69 - // contact email 70 - if (d.contact?.email) { 71 - const el = document.getElementById('val-contact-email'); 72 - el.style.display = ''; 73 - el.innerHTML = `Email: <a href="mailto:${d.contact.email}" style="color:var(--color-green)">${d.contact.email}</a>`; 74 - } 75 - } catch { 76 - set('val-did', '—'); 77 - set('val-invite', '—'); 78 - } 79 - 80 - // account count — paginate com.atproto.sync.listRepos (public, no auth) 81 - try { 82 - let cursor, total = 0; 83 - do { 84 - const url = '/xrpc/com.atproto.sync.listRepos?limit=1000' + 85 - (cursor ? '&cursor=' + encodeURIComponent(cursor) : ''); 86 - const r = await fetchJSON(url); 87 - total += (r.repos ?? []).length; 88 - cursor = r.cursor; 89 - } while (cursor); 90 - set('val-accounts', total.toString()); 91 - } catch { 92 - set('val-accounts', '—'); 93 - } 94 - }
-242
modules/server/pds-landing/styles/input.css
··· 1 - @tailwind base; 2 - @tailwind components; 3 - @tailwind utilities; 4 - 5 - /* ─── design tokens ───────────────────────────────────────────────────────── 6 - Single source of truth for every colour and font used across CSS and JS. 7 - JS can read these via getComputedStyle without any duplication. 8 - ───────────────────────────────────────────────────────────────────────── */ 9 - :root { 10 - --font-mono: 'JetBrains Mono', monospace; 11 - 12 - --color-crust: #0d1210; 13 - --color-mantle: #141c19; 14 - --color-base: #1a2420; 15 - --color-surface-0: #243028; 16 - --color-surface-1: #2e3d34; 17 - --color-overlay-0: #4d6b58; 18 - --color-text: #cdd6f4; 19 - --color-subtext-0: #93b09a; 20 - --color-green: #a6e3a1; 21 - --color-red: #f38ba8; 22 - --color-yellow: #f9e2af; 23 - --color-shadow: #000000; 24 - } 25 - 26 - /* ─── reset ──────────────────────────────────────────────────────────────── */ 27 - *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } 28 - 29 - html, body { 30 - @apply min-h-dvh; 31 - background-color: var(--color-crust); 32 - color: var(--color-text); 33 - font-family: var(--font-mono); 34 - font-size: clamp(13px, 3.5vw, 15px); 35 - line-height: 1.6; 36 - } 37 - 38 - body { 39 - @apply flex flex-col items-center; 40 - padding: clamp(1.5rem, 6vw, 4rem) clamp(1rem, 4vw, 1.5rem) clamp(1.5rem, 6vw, 3rem); 41 - gap: 2rem; 42 - } 43 - 44 - /* ─── terminal card ──────────────────────────────────────────────────────── */ 45 - .card { 46 - @apply w-full overflow-hidden rounded-lg; 47 - max-width: 680px; 48 - background-color: var(--color-mantle); 49 - border: 1px solid var(--color-surface-1); 50 - box-shadow: 51 - 0 0 0 1px color-mix(in srgb, var(--color-green) 6%, transparent), 52 - 0 8px 32px color-mix(in srgb, var(--color-shadow) 50%, transparent); 53 - } 54 - 55 - .card-titlebar { 56 - @apply flex items-center gap-2 text-xs; 57 - min-width: 0; 58 - padding: 0.55rem 1rem; 59 - background-color: var(--color-surface-0); 60 - color: var(--color-green); 61 - border-bottom: 1px solid color-mix(in srgb, var(--color-green) 15%, transparent); 62 - } 63 - 64 - .card-titlebar span:last-child { 65 - @apply truncate min-w-0; 66 - } 67 - 68 - .dot { 69 - @apply rounded-full shrink-0; 70 - width: 10px; 71 - height: 10px; 72 - background-color: color-mix(in srgb, var(--color-green) 25%, transparent); 73 - border: 1px solid color-mix(in srgb, var(--color-green) 40%, transparent); 74 - } 75 - 76 - .card-body { 77 - padding: 1.4rem 1.6rem; 78 - } 79 - 80 - /* ─── prompt header ──────────────────────────────────────────────────────── */ 81 - .prompt-line { 82 - @apply flex items-baseline; 83 - gap: 0.4rem; 84 - margin-bottom: 0.3rem; 85 - } 86 - 87 - .prompt-char { 88 - @apply font-bold select-none; 89 - color: var(--color-subtext-0); 90 - font-size: clamp(0.95em, 4vw, 1.15em); 91 - } 92 - 93 - .prompt-path { 94 - @apply font-bold select-none; 95 - color: var(--color-subtext-0); 96 - font-size: clamp(0.95em, 4vw, 1.15em); 97 - opacity: 0.6; 98 - } 99 - 100 - .user-marker { 101 - @apply font-bold break-all; 102 - color: var(--color-green); 103 - font-size: clamp(0.95em, 4vw, 1.15em); 104 - letter-spacing: -0.01em; 105 - } 106 - 107 - 108 - 109 - .tagline { 110 - color: var(--color-overlay-0); 111 - font-size: 0.82em; 112 - margin-top: 0.2rem; 113 - margin-bottom: 1.4rem; 114 - line-height: 1.5; 115 - } 116 - 117 - /* ─── key-value rows ─────────────────────────────────────────────────────── */ 118 - .kv-grid { 119 - display: grid; 120 - grid-template-columns: max-content 1fr; 121 - gap: 0.25rem 1.2rem; 122 - font-size: 0.88em; 123 - margin-bottom: 1.4rem; 124 - } 125 - 126 - .kv-key { color: var(--color-green); opacity: 0.6; white-space: nowrap; } 127 - .kv-val { color: var(--color-text); word-break: break-all; min-width: 0; } 128 - .kv-val.ok { color: var(--color-green); } 129 - .kv-val.warn { color: var(--color-yellow); } 130 - .kv-val.err { color: var(--color-red); } 131 - 132 - .loading { 133 - color: var(--color-surface-1); 134 - animation: pulse 1.2s ease-in-out infinite; 135 - } 136 - 137 - @keyframes pulse { 138 - 0%, 100% { opacity: 0.4; } 139 - 50% { opacity: 1; } 140 - } 141 - 142 - /* ─── divider ────────────────────────────────────────────────────────────── */ 143 - .divider { 144 - border: none; 145 - border-top: 1px solid color-mix(in srgb, var(--color-green) 12%, transparent); 146 - margin: 1.2rem 0; 147 - } 148 - 149 - /* ─── section label ──────────────────────────────────────────────────────── */ 150 - .section-label { 151 - @apply font-bold uppercase; 152 - font-size: 0.72em; 153 - letter-spacing: 0.12em; 154 - color: var(--color-green); 155 - margin-bottom: 0.7rem; 156 - } 157 - 158 - /* ─── links ──────────────────────────────────────────────────────────────── */ 159 - .link-list { 160 - @apply list-none flex flex-col; 161 - gap: 0.35rem; 162 - font-size: 0.88em; 163 - } 164 - 165 - .link-list li::before { 166 - content: '→ '; 167 - color: var(--color-green); 168 - } 169 - 170 - .link-list a { 171 - @apply no-underline; 172 - color: var(--color-green); 173 - opacity: 0.85; 174 - transition: opacity 0.15s; 175 - } 176 - 177 - .link-list a:hover { opacity: 1; } 178 - 179 - /* ─── notes ──────────────────────────────────────────────────────────────── */ 180 - .endpoints-note, 181 - .contact-note { 182 - font-size: 0.88em; 183 - color: var(--color-subtext-0); 184 - } 185 - 186 - .endpoints-note { margin-bottom: 0.7rem; } 187 - 188 - .highlight { color: var(--color-green); } 189 - 190 - .contact-note a { 191 - @apply no-underline; 192 - color: var(--color-green); 193 - } 194 - 195 - .contact-note a:hover { text-decoration: underline; } 196 - 197 - /* ─── records chart ──────────────────────────────────────────────────────── */ 198 - .chart-container { 199 - @apply overflow-hidden; 200 - margin-bottom: 1.4rem; 201 - } 202 - 203 - .records-chart { 204 - @apply block w-full h-auto; 205 - } 206 - 207 - .chart-empty { 208 - @apply italic; 209 - font-size: 0.82em; 210 - color: var(--color-overlay-0); 211 - margin-bottom: 0.4rem; 212 - } 213 - 214 - /* ─── footer ─────────────────────────────────────────────────────────────── */ 215 - footer { 216 - @apply text-center; 217 - color: color-mix(in srgb, var(--color-green) 35%, transparent); 218 - font-size: 0.75em; 219 - } 220 - 221 - footer a { 222 - @apply underline; 223 - color: color-mix(in srgb, var(--color-green) 35%, transparent); 224 - } 225 - 226 - footer a:hover { color: var(--color-green); } 227 - 228 - /* ─── responsive ─────────────────────────────────────────────────────────── */ 229 - @media (max-width: 440px) { 230 - .card-body { padding: 1.1rem; } 231 - .kv-grid { 232 - grid-template-columns: 1fr; 233 - gap: 0; 234 - } 235 - .kv-key { 236 - @apply uppercase opacity-100; 237 - font-size: 0.7em; 238 - letter-spacing: 0.1em; 239 - margin-top: 0.7rem; 240 - } 241 - .kv-key:first-child { margin-top: 0; } 242 - }
-5
modules/server/pds-landing/utils.js
··· 1 - export async function fetchJSON(path) { 2 - const r = await fetch(path); 3 - if (!r.ok) throw new Error(`HTTP ${r.status}`); 4 - return r.json(); 5 - }