1<!doctype html> 2<html lang="en" class="dark"> 3 <head> 4 <meta charset="UTF-8" /> 5 <!-- Content Security Policy, delivered as a meta tag so it applies on every 6 platform. The hosted web build also gets it as a response header from 7 nginx.conf; the two INTERSECT per directive, so a directive loosened 8 here alone still has no effect there. Native has no header at all â 9 Capacitor serves the bundle from https://localhost (Android) and 10 capacitor://localhost (iOS), and Electron from app://armada â so this 11 tag is the whole policy in those builds. 12 Notes on individual directives: 13 - 'unsafe-inline' in style-src is required, not incidental: the boot 14 splash below is built out of style= attributes, and fontLoader.ts 15 injects a <style> element for theme @font-face rules at runtime. 16 - https://www.youtube.com in script-src is the iframe player API 17 (src/lib/youtubeApi.ts); the embed itself is a frame-src entry. 18 - blob: in worker-src/media-src carries bundled web workers and 19 decrypted attachment playback respectively. 20 - frame-ancestors is ignored in a meta-delivered policy, so the 21 clickjacking defense lives in nginx.conf only. --> 22 <meta http-equiv="content-security-policy" content="default-src 'none'; script-src 'self' 'wasm-unsafe-eval' https://www.youtube.com; worker-src 'self' blob:; child-src 'self' blob:; style-src 'self' 'unsafe-inline'; frame-src 'self' https://www.youtube-nocookie.com https://open.spotify.com https://platform.twitter.com https://www.instagram.com https://streamable.com https://*.iframe.diy; object-src 'none'; font-src 'self' https: data:; base-uri 'self'; manifest-src 'self'; connect-src 'self' blob: https: wss:; img-src 'self' data: blob: https:; media-src 'self' blob: https:" /> 23 <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> 24 <link rel="icon" type="image/png" sizes="256x256" href="/favicon.png" /> 25 <link rel="apple-touch-icon" href="/apple-touch-icon.png" /> 26 <link rel="manifest" href="/manifest.webmanifest" /> 27 <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover, interactive-widget=resizes-content" /> 28 <!-- YouTube requires an HTTP Referer for embedded playback. The browser 29 supplies this deployment's own origin, including on self-hosts. --> 30 <meta name="referrer" content="strict-origin-when-cross-origin" /> 31 <meta name="theme-color" content="#100b15" media="(prefers-color-scheme: dark)" /> 32 <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)" /> 33 <!-- Standalone/home-screen behavior comes from manifest.webmanifest alone 34 (display: standalone). Deliberately NO legacy apple-mobile-web-app-* 35 meta tags: with apple-mobile-web-app-capable + black-translucent 36 present, iOS (observed on iOS 26 / iPhone 16 Pro) takes the legacy 37 home-screen layout path and sizes the standalone webview to 38 screen-minus-status-bar while anchoring it at the top, leaving a 39 dead ~62pt letterbox band below the page that no in-page CSS can 40 remove. Manifest-only (Ditto's config) sizes the window correctly. 41 apple-mobile-web-app-capable was also tried as a fix for iOS Web Push 42 being unavailable in the installed PWA (window.Notification undefined); 43 it made no difference on iOS 26.2.1, so it stays out. --> 44 <title>Armada</title> 45 <!-- Stamped per build (armada-build-stamp Vite plugin); lets a device's 46 running bundle be identified when debugging stale-PWA issues. --> 47 <meta name="build" content="2026-10-01 20:23:55Z" /> 48 <meta name="description" content="The open-source, encrypted Discord alternative. Unbannable communities with channels, voice, and DMs â no host required. Your keys, your fleet." /> 49 50 <!-- Open Graph / social. og:image/twitter:image MUST be absolute URLs â 51 most crawlers (X, Discord, Slack, iMessage) do not resolve relative 52 ones and the card renders with no image. https://armada.buzz is stamped 53 at build time from PUBLIC_WEB_ORIGIN so a build points its card at 54 the host it is actually served from: an absolute URL on some OTHER
55 origin is only as good as that origin, and when it can't be fetched 56 the preview degrades to the platform's generic placeholder icon. --> 57 <meta property="og:type" content="website" /> 58 <meta property="og:title" content="Armada" /> 59 <meta property="og:description" content="The open-source, encrypted Discord alternative. Unbannable communities with channels, voice, and DMs â no host required. Your keys, your fleet." /> 60 <meta property="og:image" content="https://armada.buzz/og.png" /> 61 <meta property="og:image:type" content="image/png" /> 62 <meta property="og:image:width" content="1200" /> 63 <meta property="og:image:height" content="630" /> 64 <meta property="og:url" content="https://armada.buzz" /> 65 <meta property="og:site_name" content="Armada" /> 66 <meta name="twitter:card" content="summary_large_image" /> 67 <meta name="twitter:title" content="Armada" /> 68 <meta name="twitter:description" content="The open-source, encrypted Discord alternative. Unbannable communities with channels, voice, and DMs â no host required. Your keys, your fleet." /> 69 <meta name="twitter:image" content="https://armada.buzz/og.png" /> 70 71 <!-- Marks home-screen (standalone PWA) launches on <html> before first 72 paint so CSS can scroll-lock the shell (see index.css). External 73 because the policy above permits no inline script. --> 74
74<script src="/standalone.js"></script>
74 75
75<script src="/theme.js"></script>
75 76
76<script type="module" crossorigin src="/assets/index-D6uL_aXl.js"></script>
76 77 <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-QTnfLwEv.js"> 78 <link rel="modulepreload" crossorigin href="/assets/lucide-icons-GmG7O7nz.js"> 79 <link rel="modulepreload" crossorigin href="/assets/vendor-nostr-B8luZNPL.js"> 80 <link rel="modulepreload" crossorigin href="/assets/vendor-radix-BdOCIdXO.js"> 81 <link rel="modulepreload" crossorigin href="/assets/vendor-react-Cj-tKOwh.js"> 82 <link rel="modulepreload" crossorigin href="/assets/dist-BcvA-GRa.js"> 83 <link rel="modulepreload" crossorigin href="/assets/kvCache-DmzGwgBm.js"> 84 <link rel="modulepreload" crossorigin href="/assets/vendor-tanstack-CZLUN3pe.js"> 85 <link rel="modulepreload" crossorigin href="/assets/useToast-BaojZlzQ.js"> 86 <link rel="modulepreload" crossorigin href="/assets/platform-CX72QBWD.js"> 87 <link rel="modulepreload" crossorigin href="/assets/useCurrentUser-D4QnF4TO.js"> 88 <link rel="modulepreload" crossorigin href="/assets/protocol-Cbby3CJU.js"> 89 <link rel="modulepreload" crossorigin href="/assets/useAppContext-Dheo3zbF.js"> 90 <link rel="modulepreload" crossorigin href="/assets/publishOutbox-eA4L7GmU.js"> 91 <link rel="modulepreload" crossorigin href="/assets/useNostrPublish-DaUWpAsr.js"> 92 <link rel="modulepreload" crossorigin href="/assets/useMuteList-DSGedOi-.js"> 93 <link rel="modulepreload" crossorigin href="/assets/mediaPolicy-D2mJJ8_m.js"> 94 <link rel="modulepreload" crossorigin href="/assets/useBlossomServers-BenlNkdZ.js"> 95 <link rel="modulepreload" crossorigin href="/assets/useRemoveRailKey-BCWvLUGJ.js"> 96 <link rel="modulepreload" crossorigin href="/assets/silentPaymentsCore-BQkvFdYF.js"> 97 <link rel="modulepreload" crossorigin href="/assets/nostrId-CuDCNeUw.js"> 98 <link rel="modulepreload" crossorigin href="/assets/resolvePubkey-ObgiCMT-.js"> 99 <link rel="modulepreload" crossorigin href="/assets/shareOrigin-CkUxYn2H.js"> 100 <link rel="stylesheet" crossorigin href="/assets/index-BwiPh5Lt.css"> 101
101<script type="module" async src="/_npanel/script.js?v=52264416a644aff8"></script>
101</head> 102 <body> 103 <!-- Static boot splash: the same animated Armada crest as BootSplash.tsx, 104 inlined so it paints on the very first frame â before the JS bundle 105 parses, the provider tree mounts, and the first lazy route chunk loads. 106 Without it, an APK cold start (logged in) showed a blank themed 107 background for a few seconds until React could render its own splash. 108 React's createRoot(#root).render() replaces these children on mount, 109 so it disappears the moment the app paints. Uses the CSS vars theme.js 110 set on :root before this markup, so it matches the persisted theme. --> 111 <div id="root"> 112 <div id="boot-splash" role="status" aria-label="Loading" style="position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:hsl(var(--background, 260 22% 9%));"> 113 <!-- Hidden until the script below decides this launch isn't headed for 114 /welcome. A display:none element doesn't animate, so revealing it 115 starts the draw at frame one rather than mid-stroke. --> 116 <svg id="boot-mark" width="96" height="96" viewBox="0 0 128 128" fill="none" role="img" aria-label="Armada logo" style="display:none;filter:drop-shadow(0 8px 24px hsl(var(--primary, 330 90% 62%) / 0.25));"> 117 <!-- Wake: each line sweeps open from the centerline (x=64, also every 118 line's own midpoint), then scans slowly in and out on its own 119 loop. Both are scaleX about that centerline, split across the 120 wrapper and the path so they don't fight over one transform. --> 121 <g fill="hsl(var(--accent2, 180 90% 55%))"> 122 <g style="transform-origin:64px 0;animation:armada-wake 6s ease-in-out 0.9s infinite;"> 123 <path d="M95.4 104.865H32.59c-1.18 0-2.14-.96-2.14-2.14s.96-2.14 2.14-2.14H95.4c1.18 0 2.14.96 2.14 2.14s-.96 2.14-2.14 2.14z" style="transform-origin:64px 0;animation:armada-sweep 0.52s cubic-bezier(0.22,1,0.36,1) both;"></path> 124 </g> 125 <g style="transform-origin:64px 0;animation:armada-wake 6s ease-in-out 1.5s infinite;"> 126 <path d="M83.98 115.565H44.01a1.43 1.43 0 1 1 0-2.86h39.97a1.43 1.43 0 1 1 0 2.86z" style="transform-origin:64px 0;animation:armada-sweep 0.52s cubic-bezier(0.22,1,0.36,1) 0.07s both;"></path> 127 </g> 128 <g style="transform-origin:64px 0;animation:armada-wake 6s ease-in-out 2.1s infinite;"> 129 <path d="M73.99 123.775H54.01a1.071 1.071 0 0 1 0-2.14h19.98a1.071 1.071 0 0 1 0 2.14z" style="transform-origin:64px 0;animation:armada-sweep 0.52s cubic-bezier(0.22,1,0.36,1) 0.14s both;"></path> 130 </g> 131 </g> 132 <!-- Sail, drawn in three stacked copies: halo sheen, fill, and the 133 hairline trace on top (pathLength="1" normalizes the dash). --> 134 <path d="M64 4.225l-39.97 88.5h17.13l2.31-5.2 2.76-6.22-2.22-1.43-1.42-12.84h9.99l4.89-11.01L64 41.335l11.42 25.7h9.99l-1.43 12.84-2.22 1.43 2.77 6.22 2.31 5.2h17.13z" fill="hsl(var(--primary, 330 90% 62%))" style="opacity:0;filter:drop-shadow(0 0 10px hsl(var(--primary, 330 90% 62%) / 0.6));animation:armada-sheen 0.9s ease-in-out 0.64s both;"></path> 135 <path d="M64 4.225l-39.97 88.5h17.13l2.31-5.2 2.76-6.22-2.22-1.43-1.42-12.84h9.99l4.89-11.01L64 41.335l11.42 25.7h9.99l-1.43 12.84-2.22 1.43 2.77 6.22 2.31 5.2h17.13z" fill="hsl(var(--primary, 330 90% 62%))" style="animation:armada-ink 0.42s ease-out 0.4s both;"></path> 136 <path d="M64 4.225l-39.97 88.5h17.13l2.31-5.2 2.76-6.22-2.22-1.43-1.42-12.84h9.99l4.89-11.01L64 41.335l11.42 25.7h9.99l-1.43 12.84-2.22 1.43 2.77 6.22 2.31 5.2h17.13z" pathLength="1" fill="none" stroke="hsl(var(--primary, 330 90% 62%))" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="opacity:0;stroke-dasharray:1;animation:armada-trace 0.78s cubic-bezier(0.65,0,0.35,1) 0.08s both;"></path> 137 <!-- Waterline caret, inked in last. --> 138 <path d="M76.84 79.165c-.15 0-.31-.05-.44-.15l-12.41-9.65-12.41 9.65c-.31.24-.76.19-1-.13a.706.706 0 0 1 .13-1l12.85-9.99c.26-.2.62-.2.88 0l12.85 9.99a.715.715 0 0 1-.43 1.28z" fill="hsl(var(--primary, 330 90% 62%))" fill-opacity="0.85" style="animation:armada-ink 0.32s ease-out 0.86s both;"></path> 139 </svg> 140 </div> 141 </div> 142 <!-- Reveals the mark above unless this launch is headed for /welcome (see 143 the file for why). Must stay HERE, after the splash markup it reads. 144 External because the policy above permits no inline script. --> 145
145<script src="/boot-splash.js"></script>
145 146 <style> 147 /* Keep in sync with ArmadaCrestKeyframes in src/components/brand/ArmadaCrest.tsx. 148 Every element's base style is its final resting state and the keyframes 149 supply only the entrance, so killing them below lands on the finished 150 mark rather than a half-drawn one. */ 151 @keyframes armada-trace { 0% { opacity: 1; stroke-dashoffset: 1; } 76% { opacity: 1; stroke-dashoffset: 0; } 100% { opacity: 0; stroke-dashoffset: 0; } } 152 @keyframes armada-ink { from { opacity: 0; } to { opacity: 1; } } 153 @keyframes armada-sheen { 0%, 100% { opacity: 0; } 45% { opacity: 1; } } 154 @keyframes armada-sweep { from { opacity: 0; transform: scaleX(0.15); } to { opacity: 1; transform: scaleX(1); } } 155 @keyframes armada-wake { 0%, 100% { transform: scaleX(1); } 50% { transform: scaleX(0.78); } } 156 @media (prefers-reduced-motion: reduce) { #boot-splash [style*="animation"] { animation: none !important; } } 157 </style> 158 </body> 159</html>
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.