Archipelago — open-source initial import
@@ -0,0 +1,899 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Archipelago — LoRa & Mesh Functionality Guide</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #000000;
|
||||
--glass-card: rgba(0, 0, 0, 0.65);
|
||||
--glass-dark: rgba(0, 0, 0, 0.35);
|
||||
--glass-darker: rgba(0, 0, 0, 0.6);
|
||||
--glass-border: rgba(255, 255, 255, 0.18);
|
||||
--glass-highlight: rgba(255, 255, 255, 0.22);
|
||||
--glass-blur: 18px;
|
||||
--glass-blur-strong: 24px;
|
||||
--shadow-glass: 0 8px 24px rgba(0, 0, 0, 0.45);
|
||||
--shadow-glass-inset: inset 0 1px 0 rgba(255, 255, 255, 0.22);
|
||||
--text: rgba(255, 255, 255, 0.9);
|
||||
--text-muted: rgba(255, 255, 255, 0.6);
|
||||
--accent: #fb923c;
|
||||
--accent-dim: rgba(251, 146, 60, 0.15);
|
||||
--green: #4ade80;
|
||||
--green-dim: rgba(74, 222, 128, 0.15);
|
||||
--red: #ef4444;
|
||||
--red-dim: rgba(239, 68, 68, 0.12);
|
||||
--blue: #3b82f6;
|
||||
--blue-dim: rgba(59, 130, 246, 0.12);
|
||||
--yellow: #facc15;
|
||||
--yellow-dim: rgba(250, 204, 21, 0.12);
|
||||
--purple: #a78bfa;
|
||||
--purple-dim: rgba(167, 139, 250, 0.12);
|
||||
--radius: 16px;
|
||||
--radius-sm: 12px;
|
||||
--transition: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
html { scroll-behavior: smooth; }
|
||||
|
||||
body {
|
||||
font-family: 'Avenir Next', system-ui, -apple-system, sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
line-height: 1.7;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
nav {
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 280px;
|
||||
height: 100vh;
|
||||
background: var(--glass-card);
|
||||
backdrop-filter: blur(var(--glass-blur-strong));
|
||||
-webkit-backdrop-filter: blur(var(--glass-blur-strong));
|
||||
border-right: 1px solid var(--glass-border);
|
||||
box-shadow: var(--shadow-glass);
|
||||
overflow-y: auto;
|
||||
padding: 24px 0;
|
||||
z-index: 100;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(255,255,255,0.15) transparent;
|
||||
}
|
||||
nav .logo { padding: 0 24px 20px; margin-bottom: 16px; }
|
||||
nav .logo h1 {
|
||||
font-family: 'Montserrat', 'Avenir Next', sans-serif;
|
||||
font-size: 18px; font-weight: 700;
|
||||
color: var(--accent); letter-spacing: -0.02em;
|
||||
}
|
||||
nav .logo p { font-size: 12px; color: var(--text-muted); margin-top: 4px; }
|
||||
nav .nav-section {
|
||||
padding: 12px 16px 4px;
|
||||
font-size: 10px; font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
nav a {
|
||||
display: block;
|
||||
padding: 6px 24px;
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
transition: all var(--transition);
|
||||
border-left: 2px solid transparent;
|
||||
}
|
||||
nav a:hover, nav a.active {
|
||||
color: var(--text);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border-left-color: var(--accent);
|
||||
}
|
||||
|
||||
main {
|
||||
margin-left: 280px;
|
||||
max-width: 960px;
|
||||
padding: 48px 48px 120px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: 'Montserrat', 'Avenir Next', sans-serif;
|
||||
font-size: 28px; font-weight: 700;
|
||||
margin: 64px 0 8px;
|
||||
padding-top: 24px;
|
||||
color: var(--text);
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
h2:first-of-type { margin-top: 0; }
|
||||
h3 {
|
||||
font-family: 'Montserrat', 'Avenir Next', sans-serif;
|
||||
font-size: 20px; font-weight: 600;
|
||||
margin: 40px 0 12px;
|
||||
color: var(--text);
|
||||
}
|
||||
h4 {
|
||||
font-size: 16px; font-weight: 600;
|
||||
margin: 24px 0 8px;
|
||||
color: var(--accent);
|
||||
}
|
||||
p { margin: 8px 0 16px; color: var(--text); }
|
||||
ul, ol { margin: 8px 0 16px 24px; color: var(--text); }
|
||||
li { margin: 4px 0; }
|
||||
|
||||
.subtitle {
|
||||
font-size: 15px;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.hero { text-align: center; padding: 48px 0 56px; margin-bottom: 24px; }
|
||||
.hero h1 {
|
||||
font-family: 'Montserrat', 'Avenir Next', sans-serif;
|
||||
font-size: 42px; font-weight: 800;
|
||||
background: linear-gradient(135deg, var(--accent), #f59e0b);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
.hero .tagline {
|
||||
font-size: 18px;
|
||||
color: var(--text-muted);
|
||||
margin: 12px auto 0;
|
||||
max-width: 640px;
|
||||
}
|
||||
.hero .meta {
|
||||
margin-top: 20px;
|
||||
display: flex; gap: 16px;
|
||||
justify-content: center; flex-wrap: wrap;
|
||||
}
|
||||
.hero .meta span {
|
||||
font-size: 12px;
|
||||
padding: 4px 12px;
|
||||
border-radius: 999px;
|
||||
background: var(--glass-dark);
|
||||
backdrop-filter: blur(var(--glass-blur));
|
||||
-webkit-backdrop-filter: blur(var(--glass-blur));
|
||||
border: 1px solid var(--glass-border);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.card {
|
||||
background: var(--glass-card);
|
||||
backdrop-filter: blur(var(--glass-blur));
|
||||
-webkit-backdrop-filter: blur(var(--glass-blur));
|
||||
border: 1px solid var(--glass-border);
|
||||
box-shadow: var(--shadow-glass), var(--shadow-glass-inset);
|
||||
border-radius: var(--radius);
|
||||
padding: 24px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
.card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 16px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
.card-sm {
|
||||
background: var(--glass-darker);
|
||||
backdrop-filter: blur(var(--glass-blur-strong));
|
||||
-webkit-backdrop-filter: blur(var(--glass-blur-strong));
|
||||
border: 1px solid var(--glass-border);
|
||||
box-shadow: var(--shadow-glass), var(--shadow-glass-inset);
|
||||
border-radius: var(--radius);
|
||||
padding: 16px 20px;
|
||||
transition: transform var(--transition), box-shadow var(--transition);
|
||||
}
|
||||
.card-sm:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
.card-sm h4 { margin: 0 0 6px; font-size: 14px; }
|
||||
.card-sm p { font-size: 13px; color: var(--text-muted); margin: 0; }
|
||||
|
||||
.badge {
|
||||
display: inline-block;
|
||||
font-size: 11px; font-weight: 600;
|
||||
padding: 2px 10px;
|
||||
border-radius: 999px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
.badge-green { background: var(--green-dim); color: var(--green); }
|
||||
.badge-red { background: var(--red-dim); color: var(--red); }
|
||||
.badge-yellow { background: var(--yellow-dim); color: var(--yellow); }
|
||||
.badge-blue { background: var(--blue-dim); color: var(--blue); }
|
||||
.badge-purple { background: var(--purple-dim); color: var(--purple); }
|
||||
.badge-accent { background: var(--accent-dim); color: var(--accent); }
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
margin: 16px 0;
|
||||
font-size: 13px;
|
||||
background: var(--glass-card);
|
||||
backdrop-filter: blur(var(--glass-blur));
|
||||
-webkit-backdrop-filter: blur(var(--glass-blur));
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: var(--radius-sm);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-glass);
|
||||
}
|
||||
th {
|
||||
text-align: left;
|
||||
padding: 10px 14px;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
color: var(--text-muted);
|
||||
font-weight: 600;
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
border-bottom: 1px solid var(--glass-border);
|
||||
}
|
||||
td {
|
||||
padding: 10px 14px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
|
||||
vertical-align: top;
|
||||
}
|
||||
tr:last-child td { border-bottom: none; }
|
||||
tr:hover td { background: rgba(255, 255, 255, 0.04); }
|
||||
|
||||
code {
|
||||
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
color: var(--accent);
|
||||
}
|
||||
pre {
|
||||
background: var(--glass-card);
|
||||
backdrop-filter: blur(var(--glass-blur));
|
||||
-webkit-backdrop-filter: blur(var(--glass-blur));
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 20px;
|
||||
overflow-x: auto;
|
||||
margin: 16px 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
box-shadow: var(--shadow-glass);
|
||||
}
|
||||
pre code { background: none; padding: 0; color: var(--text); }
|
||||
|
||||
.diagram {
|
||||
background: var(--glass-card);
|
||||
backdrop-filter: blur(var(--glass-blur-strong));
|
||||
-webkit-backdrop-filter: blur(var(--glass-blur-strong));
|
||||
border: 1px solid var(--glass-border);
|
||||
box-shadow: var(--shadow-glass), var(--shadow-glass-inset);
|
||||
border-radius: var(--radius);
|
||||
padding: 24px;
|
||||
margin: 20px 0;
|
||||
overflow-x: auto;
|
||||
font-family: 'Menlo', 'Monaco', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: var(--text-muted);
|
||||
white-space: pre;
|
||||
}
|
||||
.diagram .highlight { color: var(--accent); font-weight: 600; }
|
||||
.diagram .green { color: var(--green); }
|
||||
.diagram .blue { color: var(--blue); }
|
||||
.diagram .red { color: var(--red); }
|
||||
.diagram .purple { color: var(--purple); }
|
||||
|
||||
.callout {
|
||||
background: var(--glass-card);
|
||||
backdrop-filter: blur(var(--glass-blur));
|
||||
-webkit-backdrop-filter: blur(var(--glass-blur));
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 16px 20px;
|
||||
margin: 16px 0;
|
||||
font-size: 14px;
|
||||
border-left: 3px solid;
|
||||
box-shadow: var(--shadow-glass);
|
||||
}
|
||||
.callout-info { border-color: var(--blue); }
|
||||
.callout-warn { border-color: var(--yellow); }
|
||||
.callout-danger { border-color: var(--red); }
|
||||
.callout-success { border-color: var(--green); }
|
||||
.callout-learn {
|
||||
border-color: var(--purple);
|
||||
background: rgba(167, 139, 250, 0.06);
|
||||
position: relative;
|
||||
padding-top: 32px;
|
||||
}
|
||||
.callout-learn::before {
|
||||
content: 'Layman Analogy';
|
||||
position: absolute;
|
||||
top: 10px; left: 20px;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: var(--purple);
|
||||
}
|
||||
.callout strong { display: block; margin-bottom: 4px; }
|
||||
|
||||
.score-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
|
||||
gap: 12px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.score-card {
|
||||
background: var(--glass-darker);
|
||||
backdrop-filter: blur(var(--glass-blur));
|
||||
-webkit-backdrop-filter: blur(var(--glass-blur));
|
||||
border: 1px solid var(--glass-border);
|
||||
box-shadow: var(--shadow-glass), var(--shadow-glass-inset);
|
||||
border-radius: var(--radius);
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
transition: transform var(--transition);
|
||||
}
|
||||
.score-card:hover { transform: translateY(-2px); }
|
||||
.score-card .score {
|
||||
font-family: 'Montserrat', 'Avenir Next', sans-serif;
|
||||
font-size: 28px; font-weight: 800;
|
||||
margin: 4px 0;
|
||||
color: var(--accent);
|
||||
}
|
||||
.score-card .label {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--glass-border);
|
||||
margin: 48px 0;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
nav { display: none; }
|
||||
main { margin-left: 0; padding: 20px; }
|
||||
.hero h1 { font-size: 32px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav>
|
||||
<div class="logo">
|
||||
<h1>Archipelago</h1>
|
||||
<p>LoRa & Mesh Guide</p>
|
||||
</div>
|
||||
|
||||
<div class="nav-section">Overview</div>
|
||||
<a href="#intro">Introduction</a>
|
||||
<a href="#layman">What is LoRa?</a>
|
||||
<a href="#why">Why Archipelago uses it</a>
|
||||
|
||||
<div class="nav-section">Stack</div>
|
||||
<a href="#hardware">Hardware & Firmware</a>
|
||||
<a href="#serial">USB Serial Transport</a>
|
||||
<a href="#wire">Wire Format</a>
|
||||
<a href="#crypto">Encryption Layers</a>
|
||||
<a href="#fragmentation">Fragmentation</a>
|
||||
|
||||
<div class="nav-section">Routing</div>
|
||||
<a href="#dual-transport">Dual Transport</a>
|
||||
<a href="#addressing">Addressing</a>
|
||||
<a href="#synthetic">Federation Contacts</a>
|
||||
|
||||
<div class="nav-section">Messages</div>
|
||||
<a href="#msg-overview">All 23 Types</a>
|
||||
<a href="#msg-text">Text / Reply / Edit</a>
|
||||
<a href="#msg-social">Reactions & Receipts</a>
|
||||
<a href="#msg-content">Content / Files</a>
|
||||
<a href="#msg-bitcoin">Bitcoin & Lightning</a>
|
||||
<a href="#msg-safety">Alerts & Presence</a>
|
||||
<a href="#msg-identity">Identity & Keys</a>
|
||||
|
||||
<div class="nav-section">Operations</div>
|
||||
<a href="#rpc">RPC API</a>
|
||||
<a href="#ui">User Interface</a>
|
||||
<a href="#listener">Listener Loop</a>
|
||||
<a href="#files">File Map</a>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
|
||||
<section class="hero">
|
||||
<h1>LoRa & Mesh Functionality</h1>
|
||||
<p class="tagline">How Archipelago sends encrypted messages, Bitcoin transactions, and emergency alerts over long-range radio when the internet is gone.</p>
|
||||
<div class="meta">
|
||||
<span>Meshcore Companion USB</span>
|
||||
<span>Double Ratchet E2E</span>
|
||||
<span>23 Message Types</span>
|
||||
<span>160-byte LoRa Frame</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<h2 id="intro">Introduction</h2>
|
||||
<p>This document explains Archipelago's mesh subsystem — the code under <code>core/archipelago/src/mesh/</code> that lets nodes talk to each other over <strong>LoRa radio</strong> instead of (or alongside) the internet. It covers every message type, the transport layer that carries it, the cryptography that protects it, and the code paths that glue it all together.</p>
|
||||
<p>The goal: give you a mental model that works both ways. If you're an engineer, you can read this and know exactly which bytes get put on the wire for a given RPC call. If you're not, the purple "Layman Analogy" boxes translate each piece into familiar metaphors.</p>
|
||||
|
||||
<h2 id="layman">What is LoRa? <span class="badge badge-purple">Layman</span></h2>
|
||||
<div class="callout callout-learn">
|
||||
<strong>Think of LoRa as a whisper that travels 10 kilometers.</strong>
|
||||
Normal Wi-Fi is a shout: loud, fast, lots of data, but only a few rooms away. LoRa is the opposite — a tiny, slow whisper that can cross an entire city because it's so narrow and patient that it slips through walls, trees, and hills. The tradeoff: you can only whisper about <strong>160 bytes</strong> at a time, and each whisper takes a second or two to complete.
|
||||
</div>
|
||||
<p>Technically, LoRa (Long Range) is a proprietary radio modulation by Semtech that uses <em>chirp spread spectrum</em> (CSS). It operates in unlicensed ISM bands (915 MHz in the Americas, 868 MHz in Europe) and trades bandwidth for sensitivity, allowing receivers to decode signals below the noise floor. Typical line-of-sight range is 5–15 km with a simple antenna; data rates are 0.3–50 kbps.</p>
|
||||
<p>Archipelago does not talk to a LoRa chipset directly. Instead it delegates to a small USB-attached device running <strong>Meshcore firmware</strong>, which handles the radio, the mesh routing, and the store-and-forward queue. Archipelago speaks to that device over USB serial.</p>
|
||||
|
||||
<h2 id="why">Why Archipelago uses it</h2>
|
||||
<div class="card-grid">
|
||||
<div class="card-sm">
|
||||
<h4>Off-grid safety</h4>
|
||||
<p>Dead-man switch and emergency alerts reach family without cell coverage.</p>
|
||||
</div>
|
||||
<div class="card-sm">
|
||||
<h4>Censorship resistance</h4>
|
||||
<p>No ISP, no DNS, no TLS termination — just radio waves between nodes.</p>
|
||||
</div>
|
||||
<div class="card-sm">
|
||||
<h4>Bitcoin when internet is down</h4>
|
||||
<p>Relay signed transactions and Lightning payments through on-grid peers.</p>
|
||||
</div>
|
||||
<div class="card-sm">
|
||||
<h4>Truly peer-to-peer chat</h4>
|
||||
<p>Text, replies, reactions, read-receipts — Telegram-quality UX, zero servers.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2 id="hardware">Hardware & Firmware</h2>
|
||||
<p>Archipelago expects a Meshcore-compatible radio board plugged into USB. The firmware handles RF, mesh forwarding, and contact management; Archipelago handles encryption, message types, and UI.</p>
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Component</th><th>Role</th><th>Examples</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><strong>MCU</strong></td><td>Runs Meshcore firmware, talks USB serial</td><td>ESP32, nRF52840</td></tr>
|
||||
<tr><td><strong>Radio</strong></td><td>Semtech LoRa transceiver</td><td>SX1262, SX1276</td></tr>
|
||||
<tr><td><strong>Board</strong></td><td>MCU + radio + USB + antenna</td><td>Heltec V3, T-Beam, RAK WisBlock, Station G2</td></tr>
|
||||
<tr><td><strong>Firmware</strong></td><td>Mesh routing + Companion USB protocol</td><td>Meshcore</td></tr>
|
||||
<tr><td><strong>Connection</strong></td><td>USB CDC-ACM serial</td><td><code>/dev/mesh-radio</code> (udev symlink), <code>/dev/ttyUSB*</code>, <code>/dev/ttyACM*</code></td></tr>
|
||||
<tr><td><strong>Link params</strong></td><td>115200 baud, 8N1</td><td>Set in <code>mesh/serial.rs</code></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="callout callout-learn">
|
||||
<strong>It's a modem.</strong> Exactly like a 56k modem from the '90s plugged into your serial port, except the other end of the wire is a radio mesh network instead of a phone line. Archipelago tells it "send this to contact X", and it figures out which radios to hop through.
|
||||
</div>
|
||||
|
||||
<h2 id="serial">USB Serial Transport</h2>
|
||||
<p>Every byte in and out of the radio is wrapped in a framed serial protocol. The host speaks with <code>'<'</code> and listens for <code>'>'</code>.</p>
|
||||
|
||||
<div class="diagram">Host → Device: <span class="highlight">0x3C</span> '<' │ <span class="blue">len_lo len_hi</span> │ <span class="green">frame_bytes...</span>
|
||||
Device → Host: <span class="highlight">0x3E</span> '>' │ <span class="blue">len_lo len_hi</span> │ <span class="green">frame_bytes...</span>
|
||||
|
||||
Baud: 115200 Framing: 8N1 Source: mesh/serial.rs</div>
|
||||
|
||||
<p>The frame body is a Meshcore <em>Companion</em> command or response. Archipelago builds these in <code>mesh/protocol.rs</code> and parses replies in <code>mesh/listener/decode.rs</code>.</p>
|
||||
|
||||
<h3>Companion commands Archipelago uses</h3>
|
||||
<table>
|
||||
<thead><tr><th>Code</th><th>Name</th><th>Purpose</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>0x01</code></td><td>APP_START</td><td>Handshake; device returns its node_id and name</td></tr>
|
||||
<tr><td><code>0x02</code></td><td>SEND_TXT_MSG</td><td>Send payload to a contact (targeted by 6-byte pubkey prefix)</td></tr>
|
||||
<tr><td><code>0x03</code></td><td>SEND_CHANNEL_TXT_MSG</td><td>Broadcast on a channel (no specific recipient)</td></tr>
|
||||
<tr><td><code>0x04</code></td><td>GET_CONTACTS</td><td>Pull the device's contact table</td></tr>
|
||||
<tr><td><code>0x06</code></td><td>SET_DEVICE_TIME</td><td>Sync Unix timestamp for message dating</td></tr>
|
||||
<tr><td><code>0x07</code></td><td>SEND_SELF_ADVERT</td><td>Broadcast our identity onto the mesh</td></tr>
|
||||
<tr><td><code>0x08</code></td><td>SET_ADVERT_NAME</td><td>Set our display name</td></tr>
|
||||
<tr><td><code>0x0A</code></td><td>SYNC_NEXT_MESSAGE</td><td>Pop the next queued inbound message</td></tr>
|
||||
<tr><td><code>0x0B</code></td><td>SET_RADIO_PARAMS</td><td>Frequency, spreading factor, bandwidth</td></tr>
|
||||
<tr><td><code>0x0C</code></td><td>SET_RADIO_TX_POWER</td><td>Transmit power (dBm)</td></tr>
|
||||
<tr><td><code>0x38</code></td><td>GET_STATS</td><td>Device statistics</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Responses and push notifications</h3>
|
||||
<p>Responses begin with a status byte. Codes <code>< 0x80</code> are replies to a command we sent; codes <code>>= 0x80</code> are asynchronous push events from the device.</p>
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Code</th><th>Name</th><th>Meaning</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>0x00</code></td><td>RESP_OK</td><td>Command accepted</td></tr>
|
||||
<tr><td><code>0x01</code></td><td>RESP_ERR</td><td>Command failed + error code</td></tr>
|
||||
<tr><td><code>0x03</code></td><td>RESP_CONTACT</td><td>One contact entry (32-byte pubkey + metadata)</td></tr>
|
||||
<tr><td><code>0x05</code></td><td>RESP_SELF_INFO</td><td>Our node_id and name after APP_START</td></tr>
|
||||
<tr><td><code>0x10</code></td><td>RESP_CONTACT_MSG_V3</td><td>Direct inbound message (SNR + sender prefix + payload)</td></tr>
|
||||
<tr><td><code>0x11</code></td><td>RESP_CHANNEL_MSG_V3</td><td>Channel broadcast inbound</td></tr>
|
||||
<tr><td><code>0x83</code></td><td>PUSH_MESSAGES_WAITING</td><td>Async: new messages in queue, call SYNC_NEXT_MESSAGE</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="wire">Wire Format — the payload byte 0</h2>
|
||||
<p>Once a frame reaches the message payload, Archipelago looks at the <strong>first byte</strong> to decide what kind of thing it's dealing with. This single-byte marker is the master switch of the entire mesh protocol.</p>
|
||||
|
||||
<div class="diagram"><span class="highlight">0x00</span> Plain text (legacy, unencrypted)
|
||||
<span class="highlight">0x01</span> Identity broadcast (ARCHY:2 / ARCHY:3)
|
||||
<span class="highlight">0x02</span> Typed CBOR envelope (plaintext, used for debug or intra-LAN)
|
||||
<span class="highlight">0xEE</span> Encrypted typed — ChaCha20-Poly1305 w/ static shared secret
|
||||
<span class="highlight">0xDD</span> Ratcheted typed — Double Ratchet, forward-secure</div>
|
||||
|
||||
<p>Markers <code>0xEE</code> and <code>0xDD</code> are the interesting ones — they carry real production traffic. Everything else is either debug or identity bootstrap.</p>
|
||||
|
||||
<h3>0xEE — static-key encrypted envelope</h3>
|
||||
<pre><code>[0xEE] [nonce: 12 bytes] [ciphertext...] [auth tag: 16 bytes]</code></pre>
|
||||
<ul>
|
||||
<li>Key: X25519 ECDH between our Ed25519 identity (converted) and the peer's.</li>
|
||||
<li>Cipher: ChaCha20-Poly1305 AEAD.</li>
|
||||
<li>Max plaintext: <code>160 − 1 − 12 − 16 = 131</code> bytes (see <code>crypto::MAX_ENCRYPTED_PLAINTEXT</code>).</li>
|
||||
<li>Properties: confidential + authenticated, <em>but</em> compromise of a key decrypts all history.</li>
|
||||
</ul>
|
||||
|
||||
<h3>0xDD — Double Ratchet envelope</h3>
|
||||
<pre><code>[0xDD] [RatchetHeader: 40 bytes] [nonce: 12] [ciphertext] [tag: 16]</code></pre>
|
||||
<ul>
|
||||
<li>Per-message keys derived via DH ratchet + symmetric-key ratchet (HKDF-SHA256).</li>
|
||||
<li>Handles out-of-order delivery via a skipped-keys cache.</li>
|
||||
<li>Properties: forward secrecy + post-compromise recovery. Used for <code>mesh.*</code> chat once a session is established.</li>
|
||||
<li>Implementation: <code>mesh/ratchet.rs</code>, session load/save in <code>mesh/listener/session.rs</code>.</li>
|
||||
</ul>
|
||||
|
||||
<div class="callout callout-learn">
|
||||
<strong>Static key vs. ratchet = a safe vs. a self-shredding envelope.</strong>
|
||||
The <code>0xEE</code> lane is like a locked safe: one key opens everything. The <code>0xDD</code> lane is like handing your friend a new envelope each time, and burning the old one — so even if someone steals next week's key, they can't read last week's messages.
|
||||
</div>
|
||||
|
||||
<h2 id="crypto">Encryption Layers</h2>
|
||||
<p>Three cryptographic primitives combine to produce the <code>0xDD</code> ratchet flow:</p>
|
||||
|
||||
<div class="card-grid">
|
||||
<div class="card-sm">
|
||||
<h4>X25519 ECDH</h4>
|
||||
<p>Each Double Ratchet step generates a fresh keypair. Peers mix the new shared secret into the chain.</p>
|
||||
</div>
|
||||
<div class="card-sm">
|
||||
<h4>HKDF-SHA256</h4>
|
||||
<p>Derives root key, chain key, and message key at each ratchet step.</p>
|
||||
</div>
|
||||
<div class="card-sm">
|
||||
<h4>ChaCha20-Poly1305</h4>
|
||||
<p>Symmetric AEAD used for the actual payload encryption + authentication tag.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Session bootstrap — X3DH-like handshake</h3>
|
||||
<p>Before the ratchet can start, peers exchange a <strong>PrekeyBundle</strong> (type 5) and a <strong>SessionInit</strong> (type 6). Those two messages are carried by the <code>0xEE</code> static-key envelope, because the ratchet session doesn't exist yet. Once <code>SessionInit</code> is processed, subsequent traffic switches to <code>0xDD</code>. See <code>mesh/x3dh.rs</code>.</p>
|
||||
|
||||
<h2 id="fragmentation">Fragmentation — how a 500-byte message rides a 160-byte pipe</h2>
|
||||
<p>The LoRa frame budget is <strong>160 bytes</strong> (<code>protocol::MAX_MESSAGE_LEN</code>). Subtract the marker, nonce, ratchet header, and tag and you end up with ~90 usable plaintext bytes per frame. Anything bigger gets chunked.</p>
|
||||
|
||||
<div class="diagram"><span class="highlight">Chunk header</span> ┌──────────┬──────────┬────────────┐
|
||||
│ type (1) │ id (1) │ total (1) │
|
||||
└──────────┴──────────┴────────────┘
|
||||
<span class="highlight">Chunk body</span> Up to 140 bytes of Base64-encoded payload
|
||||
|
||||
Sender: compress → encrypt → split into 140-char chunks
|
||||
→ send with tiny inter-chunk delay
|
||||
Receiver: accumulate by (sender, chunk_id) → reassemble
|
||||
→ decrypt → decompress → dispatch</div>
|
||||
|
||||
<p>For chat messages shorter than 160 bytes, none of this kicks in — the whole thing fits in one frame. For larger payloads (long messages, forwarded content, PSBTs), the sender splits and the receiver joins.</p>
|
||||
|
||||
<div class="callout callout-info">
|
||||
<strong>Escape hatch: federation fallback.</strong> If a peer is a synthetic federation contact and the message is bigger than 160 bytes, Archipelago <em>skips LoRa entirely</em> and routes the message over Tor federation instead. See the <code>ContentRef</code> path in <code>rpc/mesh/typed_messages.rs</code>.
|
||||
</div>
|
||||
|
||||
<h2 id="dual-transport">Dual Transport — LoRa + Tor federation</h2>
|
||||
<p>Archipelago treats LoRa and Tor federation as <strong>two lanes of the same highway</strong>. A single chat window may receive some messages over radio and others over onion routing, and the UI doesn't distinguish. The mesh module picks the lane per-message based on the peer type and payload size.</p>
|
||||
|
||||
<div class="diagram"> ┌──────────────────┐
|
||||
│ mesh.send(...) │
|
||||
└────────┬─────────┘
|
||||
│
|
||||
┌──────────┴──────────┐
|
||||
│ Is peer synthetic? │
|
||||
└──────────┬──────────┘
|
||||
No │ Yes
|
||||
┌──────────┘ └──────────┐
|
||||
▼ ▼
|
||||
<span class="highlight">LoRa radio</span> <span class="blue">Tor federation</span>
|
||||
(160-byte frame) (unlimited, slower setup)
|
||||
│ │
|
||||
│ if > 160 B && synth ──────┘ (fallback)
|
||||
▼
|
||||
Chunked over LoRa
|
||||
or refused if no fallback</div>
|
||||
|
||||
<h2 id="addressing">Addressing</h2>
|
||||
<ul>
|
||||
<li><strong>Contact ID</strong> — 32-bit handle from Meshcore's contact table. Used by <code>SEND_TXT_MSG</code>.</li>
|
||||
<li><strong>Pubkey prefix</strong> — first 6 bytes of the peer's Ed25519 public key. Included on the wire so receivers can deduplicate and route replies.</li>
|
||||
<li><strong>DID / onion</strong> — used for federation peers; synthetic contacts carry the DID so the mesh layer can hand the message to the federation layer.</li>
|
||||
</ul>
|
||||
|
||||
<h2 id="synthetic">Synthetic federation contacts</h2>
|
||||
<p>To let the chat list show federation peers <em>before</em> any message arrives, Archipelago inserts <strong>synthetic contacts</strong> into the mesh peer list. Their contact IDs live in the upper half of the 32-bit space (<code>≥ 0x8000_0000</code>), derived deterministically from the federation node's Ed25519 pubkey. Collisions with real LoRa contact IDs are impossible by construction.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2 id="msg-overview">All 23 Message Types</h2>
|
||||
<p>Every typed message is a CBOR envelope identified by a single <code>MeshMessageType</code> byte. The <strong>Transport</strong> column shows which marker carries it on the wire and which Companion command is used.</p>
|
||||
|
||||
<table>
|
||||
<thead><tr>
|
||||
<th>ID</th><th>Type</th><th>Purpose</th><th>Marker</th><th>Cmd</th><th>Chunked?</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<tr><td>0</td><td>Text</td><td>Plain chat message</td><td>0xDD</td><td>0x02</td><td>If >160 B</td></tr>
|
||||
<tr><td>1</td><td>Alert</td><td>Emergency / dead-man heartbeat</td><td>0xDD</td><td>0x02/0x03</td><td>No (short)</td></tr>
|
||||
<tr><td>2</td><td>Invoice</td><td>Lightning / BOLT11 invoice</td><td>0xDD</td><td>0x02</td><td>Usually</td></tr>
|
||||
<tr><td>3</td><td>PsbtHash</td><td>Unsigned tx hash for co-signing</td><td>0xDD</td><td>0x02</td><td>No</td></tr>
|
||||
<tr><td>4</td><td>Coordinate</td><td>GPS location share</td><td>0xDD</td><td>0x02</td><td>No</td></tr>
|
||||
<tr><td>5</td><td>PrekeyBundle</td><td>X3DH bootstrap (pre-session)</td><td>0xEE</td><td>0x02</td><td>No</td></tr>
|
||||
<tr><td>6</td><td>SessionInit</td><td>Initial ratchet message</td><td>0xEE</td><td>0x02</td><td>No</td></tr>
|
||||
<tr><td>7</td><td>BlockHeader</td><td>Bitcoin block height/hash</td><td>0xDD</td><td>0x03</td><td>No</td></tr>
|
||||
<tr><td>8</td><td>TxRelay</td><td>Signed Bitcoin tx for on-grid peer to broadcast</td><td>0xDD</td><td>0x02</td><td>Yes</td></tr>
|
||||
<tr><td>9</td><td>TxRelayResponse</td><td>txid or error from the relay peer</td><td>0xDD</td><td>0x02</td><td>No</td></tr>
|
||||
<tr><td>10</td><td>LightningRelay</td><td>BOLT11 to pay via on-grid peer</td><td>0xDD</td><td>0x02</td><td>Yes</td></tr>
|
||||
<tr><td>11</td><td>LightningRelayResponse</td><td>payment_hash or error</td><td>0xDD</td><td>0x02</td><td>No</td></tr>
|
||||
<tr><td>12</td><td>TxConfirmation</td><td>Depth update (1/2/3 confs)</td><td>0xDD</td><td>0x02</td><td>No</td></tr>
|
||||
<tr><td>13</td><td>Reply</td><td>Quoted reply to a previous message</td><td>0xDD</td><td>0x02</td><td>If long</td></tr>
|
||||
<tr><td>14</td><td>Reaction</td><td>Emoji reaction on MessageKey</td><td>0xDD</td><td>0x02</td><td>No</td></tr>
|
||||
<tr><td>15</td><td>ReadReceipt</td><td>"Seen up to MessageKey X"</td><td>0xDD</td><td>0x02</td><td>No</td></tr>
|
||||
<tr><td>16</td><td>Forward</td><td>Re-forwarded original w/ provenance</td><td>0xDD</td><td>0x02</td><td>Yes</td></tr>
|
||||
<tr><td>17</td><td>Edit</td><td>In-place text replacement</td><td>0xDD</td><td>0x02</td><td>If long</td></tr>
|
||||
<tr><td>18</td><td>Delete</td><td>Tombstone for earlier message</td><td>0xDD</td><td>0x02</td><td>No</td></tr>
|
||||
<tr><td>19</td><td>ContentRef</td><td>CID of blob held by sender (file/image)</td><td>0xDD</td><td>0x02 or Tor</td><td>Federation fallback</td></tr>
|
||||
<tr><td>20</td><td>Presence</td><td>Heartbeat + last-activity epoch</td><td>0xDD</td><td>0x03</td><td>No</td></tr>
|
||||
<tr><td>21</td><td>ChannelInvite</td><td>Group membership announcement</td><td>0xDD</td><td>0x03</td><td>No</td></tr>
|
||||
<tr><td>22</td><td>ContactCard</td><td>Shareable federation node card</td><td>0xDD</td><td>0x02</td><td>Maybe</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>The remaining sections walk through each category and explain both the sender-side code path and what the bytes look like on the air.</p>
|
||||
|
||||
<h2 id="msg-text">Text, Reply, Edit, Delete, Forward</h2>
|
||||
|
||||
<h3>Text (type 0)</h3>
|
||||
<p><strong>Sender path.</strong> <code>rpc.mesh.send</code> → <code>typed_messages::send_text</code> → CBOR-encode the <code>Text{body}</code> variant → ratchet-encrypt → prefix <code>0xDD</code> → if under 160 B, send in one <code>SEND_TXT_MSG</code> frame; otherwise split into Base64 chunks and send sequentially with a small inter-frame sleep so the radio doesn't overflow its TX buffer.</p>
|
||||
|
||||
<h3>Reply (type 13)</h3>
|
||||
<p>Same as Text, but the CBOR envelope carries a <code>MessageKey</code> pointing at the parent message (sender pubkey prefix + timestamp). The UI renders a quote banner; the wire cost is ~12 extra bytes.</p>
|
||||
|
||||
<h3>Edit (type 17)</h3>
|
||||
<p>Envelope contains the original <code>MessageKey</code> plus the new body. Receiver updates its local store in-place and tags the entry "edited".</p>
|
||||
|
||||
<h3>Delete (type 18)</h3>
|
||||
<p>Tombstone only: <code>MessageKey</code> with no body. Receivers keep the original bytes but mark the row deleted. Costs ~20 bytes on the wire.</p>
|
||||
|
||||
<h3>Forward (type 16)</h3>
|
||||
<p>Wraps original <code>{sender_name, original_timestamp, body}</code> so the receiver can render "Forwarded from <name>". Because the body is nested, forwards are <em>almost always</em> chunked.</p>
|
||||
|
||||
<h2 id="msg-social">Reaction, ReadReceipt, Presence</h2>
|
||||
|
||||
<h3>Reaction (type 14)</h3>
|
||||
<p>Envelope: <code>{target: MessageKey, emoji: String}</code>. Single-frame, single-emoji. Receiver aggregates reactions per MessageKey and shows them as inline chips (see <code>MessageActions</code> in <code>neode-ui</code>).</p>
|
||||
|
||||
<h3>ReadReceipt (type 15)</h3>
|
||||
<p>Envelope: <code>{up_to: MessageKey}</code>. Semantically "I've seen everything up to and including this message." One receipt covers all prior unread, so traffic is O(1) per read burst rather than O(n).</p>
|
||||
|
||||
<h3>Presence (type 20)</h3>
|
||||
<p>Periodic heartbeat carrying <code>{last_activity_epoch}</code>. Broadcast on a channel (<code>SEND_CHANNEL_TXT_MSG</code>, cmd <code>0x03</code>) rather than to a specific peer, so every listener updates their "last seen" indicator in one shot.</p>
|
||||
|
||||
<div class="callout callout-learn">
|
||||
<strong>Like a lighthouse beacon.</strong> Presence doesn't go to anyone in particular — it's a flash that everyone in radio range can see. "I'm still here, last active two minutes ago." Cheap and unaddressed.
|
||||
</div>
|
||||
|
||||
<h2 id="msg-content">ContentRef — files and images without bloating the radio</h2>
|
||||
<p>LoRa cannot move a 500 KB image. The <code>ContentRef</code> type (19) solves this by sending only a <strong>pointer</strong> — a content ID (CID) plus a tiny thumbnail or description — and letting the receiver fetch the full blob out-of-band over Tor federation.</p>
|
||||
|
||||
<div class="diagram">Sender Receiver
|
||||
────── ────────
|
||||
store blob locally (CID)
|
||||
┌──────────────────────┐
|
||||
│ ContentRef {cid, │ ──ratchet──▶
|
||||
│ mime, size, │ 0xDD
|
||||
│ thumb_hash} │ over LoRa
|
||||
└──────────────────────┘
|
||||
see CID in chat
|
||||
click to fetch
|
||||
┌─────────────────┐
|
||||
│ rpc.mesh.fetch- │
|
||||
│ content(cid) │
|
||||
└────────┬────────┘
|
||||
▼
|
||||
federation (Tor)
|
||||
resolve DID → pull blob</div>
|
||||
|
||||
<div class="callout callout-info">
|
||||
<strong>Resolution bug fix note.</strong> An earlier revision of <code>ContentRef</code> routed the fetch via a name-match on the contact list, which broke when two peers had the same display name. The fix (see commit <code>5f7ebf14</code>) resolves the owning peer by DID and falls back to name-match only if DID lookup fails.
|
||||
</div>
|
||||
|
||||
<h2 id="msg-bitcoin">Bitcoin & Lightning over LoRa</h2>
|
||||
<p>Archipelago uses the mesh as a <strong>Bitcoin transport of last resort</strong>. Signed transactions travel from an offline signer, through the mesh, to a peer with internet, who then rebroadcasts them to the Bitcoin network and reports back.</p>
|
||||
|
||||
<h3>TxRelay (8) → TxRelayResponse (9) → TxConfirmation (12)</h3>
|
||||
<div class="diagram">Offline signer On-grid relay peer Bitcoin p2p
|
||||
────────────── ────────────────── ───────────
|
||||
sign tx
|
||||
┌─────────────┐
|
||||
│ TxRelay │ ─ratchet/LoRa▶ decrypt → validate
|
||||
│ {raw_tx} │ broadcast via bitcoind ───▶ mempool
|
||||
└─────────────┘ │
|
||||
▼
|
||||
┌────────────────────────┐
|
||||
◀─ratchet│ TxRelayResponse{txid} │
|
||||
└────────────────────────┘
|
||||
(or {error})
|
||||
|
||||
later, as blocks arrive:
|
||||
┌────────────────────────┐
|
||||
◀─ratchet│ TxConfirmation │
|
||||
│ {txid, depth: 1..3} │
|
||||
└────────────────────────┘</div>
|
||||
|
||||
<p>The binary framing in <code>mesh/bitcoin_relay.rs</code> is intentionally tight — raw binary, not CBOR — to keep a signed 1-input/1-output tx inside one or two 160-byte frames. Confirmation updates are tiny (txid + depth byte) and ride in a single frame.</p>
|
||||
|
||||
<h3>LightningRelay (10) → LightningRelayResponse (11)</h3>
|
||||
<p>Same shape but the payload is a BOLT11 invoice string. The relay peer pays the invoice from its own node and returns <code>payment_hash</code> or an error. Invoices are often long enough to chunk.</p>
|
||||
|
||||
<h3>Invoice (2) and PsbtHash (3)</h3>
|
||||
<p>These are <em>not</em> relays — they're peer-to-peer handoffs. <code>Invoice</code> delivers a BOLT11 to be paid by the recipient. <code>PsbtHash</code> carries just the hash of an unsigned PSBT so the recipient can retrieve the full PSBT out-of-band and co-sign.</p>
|
||||
|
||||
<h3>BlockHeader (7)</h3>
|
||||
<p>Off-grid nodes need a recent block height to avoid being fooled by stale data. A BlockHeader broadcast (sent via <code>SEND_CHANNEL_TXT_MSG</code>) lets anyone in range learn the latest height and hash from any peer with internet. Tiny payload: 4 bytes height + 32 bytes hash.</p>
|
||||
|
||||
<h2 id="msg-safety">Alerts, Coordinates, Dead-Man</h2>
|
||||
|
||||
<h3>Alert (type 1)</h3>
|
||||
<p>Envelope: <code>{kind, message, sender_contact_id}</code>. Kinds include <code>Emergency</code> and <code>Deadman</code>. Alerts can be sent direct-to-contact (for family) or channel-broadcast (for community).</p>
|
||||
|
||||
<h3>Dead-man switch</h3>
|
||||
<p>A background task in <code>mesh/alerts.rs</code> sends a <code>Deadman</code> alert on a configurable interval (default 6 hours). If the user doesn't touch the UI within that window, the alert fires automatically and asks chosen recipients to check in. Powered off? The next peer to receive your last heartbeat notices the gap.</p>
|
||||
|
||||
<h3>Coordinate (type 4)</h3>
|
||||
<p>Envelope: <code>{lat, lon, accuracy_m}</code> with lat/lon as fixed-point integers to stay under 16 bytes. Used for off-grid location sharing — hiking, sailing, field ops.</p>
|
||||
|
||||
<h3>ChannelInvite (type 21)</h3>
|
||||
<p>Phase 5 group chat primitive. Announces a new channel and its membership so other nodes can subscribe. Broadcast via <code>SEND_CHANNEL_TXT_MSG</code>.</p>
|
||||
|
||||
<h2 id="msg-identity">Identity, PrekeyBundle, ContactCard</h2>
|
||||
|
||||
<h3>Identity broadcast (marker 0x01, ARCHY:2/3)</h3>
|
||||
<p>The handshake. Before any ratchet session exists, a node advertises its Ed25519 public key on the mesh with an identity packet prefixed <code>0x01</code>. This is how peers discover each other. The payload encodes protocol version (<code>ARCHY:2</code> or <code>ARCHY:3</code>) and the raw pubkey. Carried by <code>CMD_SEND_SELF_ADVERT</code> (<code>0x07</code>).</p>
|
||||
|
||||
<h3>PrekeyBundle (type 5) and SessionInit (type 6)</h3>
|
||||
<p>X3DH handshake. <code>PrekeyBundle</code> advertises a signed prekey; <code>SessionInit</code> consumes it to derive the initial ratchet root key. Both ride on <code>0xEE</code> (static-key encryption), because the ratchet session they're creating doesn't yet exist.</p>
|
||||
|
||||
<h3>ContactCard (type 22)</h3>
|
||||
<p>A shareable card containing <code>{did, onion_address, pubkey, display_name}</code>. When a receiver taps "add" on the card, Archipelago one-click federates with that node over Tor. This is the bridge that lets LoRa-discovered peers become full federation contacts.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2 id="rpc">RPC API — what callers actually invoke</h2>
|
||||
<p>Every user-facing action goes through the RPC dispatcher (<code>api/rpc/dispatcher.rs</code>, lines 287+) and ends in <code>api/rpc/mesh/typed_messages.rs</code>. The tables below show the public surface.</p>
|
||||
|
||||
<h3>Core commands</h3>
|
||||
<table>
|
||||
<thead><tr><th>RPC</th><th>Effect</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>mesh.status</code></td><td>Device info, peer count, enabled state</td></tr>
|
||||
<tr><td><code>mesh.peers</code></td><td>List all discovered peers with RSSI / SNR / hop count</td></tr>
|
||||
<tr><td><code>mesh.messages</code></td><td>Retrieve stored mesh messages</td></tr>
|
||||
<tr><td><code>mesh.send</code></td><td>Send plain text to a specific peer</td></tr>
|
||||
<tr><td><code>mesh.send-channel</code></td><td>Broadcast on a channel</td></tr>
|
||||
<tr><td><code>mesh.broadcast</code></td><td>Mesh-wide announcement</td></tr>
|
||||
<tr><td><code>mesh.configure</code></td><td>Set device params (name, power, channel)</td></tr>
|
||||
<tr><td><code>mesh.debug-dump</code></td><td>Raw state for debugging</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Rich message commands</h3>
|
||||
<table>
|
||||
<thead><tr><th>RPC</th><th>Msg Type</th><th>Notes</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>mesh.send-invoice</code></td><td>Invoice (2)</td><td>Deliver BOLT11 to peer</td></tr>
|
||||
<tr><td><code>mesh.send-coordinate</code></td><td>Coordinate (4)</td><td>Single frame, fixed-point</td></tr>
|
||||
<tr><td><code>mesh.send-alert</code></td><td>Alert (1)</td><td>Emergency or deadman</td></tr>
|
||||
<tr><td><code>mesh.send-content</code></td><td>ContentRef (19)</td><td>Stores blob, sends CID</td></tr>
|
||||
<tr><td><code>mesh.fetch-content</code></td><td>—</td><td>Pulls blob via federation</td></tr>
|
||||
<tr><td><code>mesh.send-psbt</code></td><td>PsbtHash (3)</td><td>Hash only, full PSBT via fetch</td></tr>
|
||||
<tr><td><code>mesh.send-reply</code></td><td>Reply (13)</td><td>Quoted response</td></tr>
|
||||
<tr><td><code>mesh.send-reaction</code></td><td>Reaction (14)</td><td>Emoji</td></tr>
|
||||
<tr><td><code>mesh.send-read-receipt</code></td><td>ReadReceipt (15)</td><td>Cumulative "seen up to"</td></tr>
|
||||
<tr><td><code>mesh.forward-message</code></td><td>Forward (16)</td><td>Wraps original + provenance</td></tr>
|
||||
<tr><td><code>mesh.edit-message</code></td><td>Edit (17)</td><td>In-place text replacement</td></tr>
|
||||
<tr><td><code>mesh.delete-message</code></td><td>Delete (18)</td><td>Tombstone</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2 id="ui">User Interface</h2>
|
||||
<p>The Vue side lives under <code>neode-ui/src/views/mesh/</code> with state in <code>stores/mesh.ts</code>. Notable panels:</p>
|
||||
<div class="card-grid">
|
||||
<div class="card-sm">
|
||||
<h4>Mesh chat</h4>
|
||||
<p>Telegram-style UI with reply banners, inline reaction chips, forward/edit/delete action menu, read-receipts, outbox status.</p>
|
||||
</div>
|
||||
<div class="card-sm">
|
||||
<h4>MeshBitcoinPanel</h4>
|
||||
<p>UI for TxRelay / LightningRelay submission and confirmation tracking.</p>
|
||||
</div>
|
||||
<div class="card-sm">
|
||||
<h4>MeshDeadmanPanel</h4>
|
||||
<p>Configure dead-man interval, pick recipients, show last heartbeat time.</p>
|
||||
</div>
|
||||
<div class="card-sm">
|
||||
<h4>Unified inbox</h4>
|
||||
<p>Federation and mesh chats appear side-by-side; the transport is invisible to the user.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 id="listener">Listener loop — how inbound traffic is decoded</h2>
|
||||
<p>A long-running async task in <code>mesh/listener/mod.rs</code> owns the serial device and feeds events into the rest of the system.</p>
|
||||
|
||||
<div class="diagram">loop {
|
||||
event = await serial_read()
|
||||
match event {
|
||||
<span class="green">PUSH_MESSAGES_WAITING</span> → send SYNC_NEXT_MESSAGE until empty
|
||||
<span class="green">RESP_CONTACT_MSG_V3</span> → decode.rs extracts payload
|
||||
→ match first byte:
|
||||
<span class="highlight">0x00</span> plain text
|
||||
<span class="highlight">0x01</span> identity → frames::parse_identity
|
||||
<span class="highlight">0x02</span> typed CBOR plaintext
|
||||
<span class="highlight">0xEE</span> → crypto::decrypt_static
|
||||
<span class="highlight">0xDD</span> → session::load + ratchet::decrypt
|
||||
→ dispatch.rs routes typed msg
|
||||
to chat store / bitcoin relay /
|
||||
alerts / presence / ...
|
||||
<span class="green">RESP_CONTACT</span> → contact list update
|
||||
<span class="green">RESP_SELF_INFO</span> → record our node_id
|
||||
}
|
||||
}</div>
|
||||
|
||||
<p>Chunk reassembly happens in <code>listener/session.rs</code>, keyed by <code>(sender_pubkey_prefix, chunk_id)</code>. Incomplete chunks expire after a timeout so a lost frame doesn't leak memory.</p>
|
||||
|
||||
<h2 id="files">File Map</h2>
|
||||
<table>
|
||||
<thead><tr><th>File</th><th>Size</th><th>Role</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>mesh/mod.rs</code></td><td>52 KB</td><td>Public API, send paths, federation integration</td></tr>
|
||||
<tr><td><code>mesh/protocol.rs</code></td><td>26 KB</td><td>Frame encoding/decoding, command builders</td></tr>
|
||||
<tr><td><code>mesh/serial.rs</code></td><td>15 KB</td><td>USB driver, device detection, handshake</td></tr>
|
||||
<tr><td><code>mesh/crypto.rs</code></td><td>10 KB</td><td>X25519 ECDH, ChaCha20-Poly1305, HKDF</td></tr>
|
||||
<tr><td><code>mesh/ratchet.rs</code></td><td>16 KB</td><td>Double Ratchet implementation</td></tr>
|
||||
<tr><td><code>mesh/message_types.rs</code></td><td>23 KB</td><td>23 typed message discriminators + CBOR schemas</td></tr>
|
||||
<tr><td><code>mesh/bitcoin_relay.rs</code></td><td>17 KB</td><td>TxRelay / LightningRelay binary framing</td></tr>
|
||||
<tr><td><code>mesh/listener/dispatch.rs</code></td><td>29 KB</td><td>Typed-message routing into chat/relay/alerts</td></tr>
|
||||
<tr><td><code>mesh/listener/session.rs</code></td><td>14 KB</td><td>Ratchet session persistence + chunk reassembly</td></tr>
|
||||
<tr><td><code>mesh/x3dh.rs</code></td><td>—</td><td>Prekey / SessionInit bootstrap</td></tr>
|
||||
<tr><td><code>mesh/outbox.rs</code></td><td>—</td><td>Retry queue for unacked sends</td></tr>
|
||||
<tr><td><code>mesh/steganography.rs</code></td><td>—</td><td>Weather/sensor framing for deniable traffic</td></tr>
|
||||
<tr><td><code>api/rpc/mesh/typed_messages.rs</code></td><td>—</td><td>All <code>mesh.*</code> RPC handlers</td></tr>
|
||||
<tr><td><code>neode-ui/src/stores/mesh.ts</code></td><td>14 KB</td><td>Pinia store consumed by all mesh Vue views</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Summary scoreboard</h2>
|
||||
<div class="score-grid">
|
||||
<div class="score-card"><div class="score">23</div><div class="label">Message types</div></div>
|
||||
<div class="score-card"><div class="score">160</div><div class="label">Bytes / frame</div></div>
|
||||
<div class="score-card"><div class="score">2</div><div class="label">Transports</div></div>
|
||||
<div class="score-card"><div class="score">5</div><div class="label">Wire markers</div></div>
|
||||
<div class="score-card"><div class="score">~6k</div><div class="label">LoC in mesh/</div></div>
|
||||
<div class="score-card"><div class="score">FS</div><div class="label">Forward-secure</div></div>
|
||||
</div>
|
||||
|
||||
<div class="callout callout-success">
|
||||
<strong>Bottom line.</strong> Archipelago's mesh isn't a chat toy. It's a complete off-grid transport with forward-secure end-to-end encryption, 23 typed message kinds, Bitcoin and Lightning relay, fragmentation, store-and-forward, and a seamless Tor federation fallback. From the user's perspective it looks like iMessage; from the wire's perspective it's a carefully budgeted 160 bytes of ChaCha20 ciphertext riding on a sub-kbps radio link.
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,84 @@
|
||||
# Replace Intro & Dashboard Backgrounds
|
||||
|
||||
To change the intro splash and dashboard tab backgrounds **without touching any code**, overwrite these files with your own assets. Use the exact names and locations below.
|
||||
|
||||
**Location:** All images go in `neode-ui/public/assets/img/`
|
||||
**Format:** JPG recommended. Portrait or landscape; they use `background-size: cover` and `center center`.
|
||||
|
||||
---
|
||||
|
||||
## Intro Background
|
||||
|
||||
| Filename | Used for |
|
||||
|----------|----------|
|
||||
| **`bg-intro.jpg`** | Intro splash (alien typing + video poster + fallback), Dashboard default |
|
||||
|
||||
---
|
||||
|
||||
## Intro Video
|
||||
|
||||
| Filename | Where | Used for |
|
||||
|----------|-------|----------|
|
||||
| **`video-intro.mp4`** | `neode-ui/public/assets/video/` | Welcome Noderunner + logo, onboarding, login |
|
||||
|
||||
**Format:** MP4 (H.264). Keep under ~5MB for web. See `VIDEO_COMPRESSION_GUIDE.md` for optimization.
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Dashboard Tab Backgrounds
|
||||
|
||||
| Filename | Tab |
|
||||
|----------|-----|
|
||||
| **`bg-home.webp`** | Home |
|
||||
| **`bg-web5.jpg`** | Web5 |
|
||||
| **`bg-network.jpg`** | Server / Network |
|
||||
| **`bg-settings.webp`** | Settings |
|
||||
| **`bg-myapps.webp`** | My Apps |
|
||||
| **`bg-appstore.webp`** | App Store / Marketplace |
|
||||
| **`bg-cloud.webp`** | Cloud |
|
||||
| **`bg-intro.jpg`** | Default (also intro) |
|
||||
| **`bg-intro-3.jpg`** | Alternate layer during transitions |
|
||||
|
||||
---
|
||||
|
||||
## Intro Flow Backgrounds (onboarding)
|
||||
|
||||
| Filename | Used for |
|
||||
|----------|----------|
|
||||
| **`bg-intro-1.webp`** | Onboarding done, login |
|
||||
| **`bg-intro-2.jpg`** | Onboarding verify |
|
||||
| **`bg-intro-3.jpg`** | Onboarding path, dashboard transition layer |
|
||||
| **`bg-intro-4.webp`** | Onboarding options |
|
||||
| **`bg-intro-5.webp`** | Onboarding did |
|
||||
| **`bg-intro-6.webp`** | Onboarding backup |
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Asset | Full path |
|
||||
|-------|-----------|
|
||||
| Intro image | `neode-ui/public/assets/img/bg-intro.jpg` |
|
||||
| Intro video | `neode-ui/public/assets/video/video-intro.mp4` |
|
||||
| Home | `neode-ui/public/assets/img/bg-home.webp` |
|
||||
| Web5 | `neode-ui/public/assets/img/bg-web5.jpg` |
|
||||
| Network | `neode-ui/public/assets/img/bg-network.jpg` |
|
||||
| Settings | `neode-ui/public/assets/img/bg-settings.webp` |
|
||||
| My Apps | `neode-ui/public/assets/img/bg-myapps.webp` |
|
||||
| App Store | `neode-ui/public/assets/img/bg-appstore.webp` |
|
||||
| Cloud | `neode-ui/public/assets/img/bg-cloud.webp` |
|
||||
| Default | `neode-ui/public/assets/img/bg-intro.jpg` |
|
||||
| Transition | `neode-ui/public/assets/img/bg-intro-3.jpg` |
|
||||
| Intro 1–6 | `neode-ui/public/assets/img/bg-intro-1.webp` … `bg-intro-6.webp` (intro-2 and intro-3 remain `.jpg` — WebP came out larger for those) |
|
||||
|
||||
---
|
||||
|
||||
## Steps to Replace
|
||||
|
||||
1. Put your images in `neode-ui/public/assets/img/` with the exact filenames above.
|
||||
2. Put your video in `neode-ui/public/assets/video/video-intro.mp4`.
|
||||
3. Run `npm run build` (or deploy) so the new assets are included.
|
||||
|
||||
No code changes required.
|
||||
@@ -0,0 +1,23 @@
|
||||
# Welcome Noderunner Speech
|
||||
|
||||
The intro plays a sci-fi female voice saying "Welcome Noderunner" as the text types in.
|
||||
|
||||
## Generate the audio (ElevenLabs)
|
||||
|
||||
1. Get a free API key at [elevenlabs.io](https://elevenlabs.io) (free tier: 10k chars/month)
|
||||
2. Run:
|
||||
```bash
|
||||
cd neode-ui
|
||||
ELEVENLABS_API_KEY=your_key npm run generate-welcome-speech
|
||||
```
|
||||
3. Commit `welcome-noderunner.mp3` to the repo
|
||||
|
||||
## Custom sci-fi voice
|
||||
|
||||
Browse [ElevenLabs Voice Library](https://elevenlabs.io/voice-library) and search for "sci-fi", "AI", "robot", or "character". Copy the voice ID from the URL or voice settings, then:
|
||||
|
||||
```bash
|
||||
ELEVENLABS_API_KEY=your_key ELEVENLABS_VOICE_ID=voice_id npm run generate-welcome-speech
|
||||
```
|
||||
|
||||
Recommended: "The Digital Oracle", "The Friendly AI Assistant", or similar character voices from the Synthetic/Character categories.
|
||||
@@ -0,0 +1,93 @@
|
||||
Copyright 2011 The Montserrat Project Authors (https://github.com/JulietaUla/Montserrat)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,11 @@
|
||||
# Icon Attribution
|
||||
|
||||
- `barbarian.svg`, `batteries.svg` — from **game-icons.net**
|
||||
(https://game-icons.net), by Lorc, Delapouite & contributors,
|
||||
licensed under CC BY 3.0 (https://creativecommons.org/licenses/by/3.0/).
|
||||
- Pixel-style icons (`save.svg`, `paint-bucket.svg`, `fill-half.svg`,
|
||||
`cloud-moon.svg`, `debug-off.svg`, `cloud-done.svg`) — from
|
||||
**pixelarticons** by Gerrit Halfmann (https://github.com/halfmage/pixelarticons),
|
||||
MIT License.
|
||||
- All other icons are original Archipelago artwork (MIT, see repository
|
||||
LICENSE).
|
||||
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 5.9 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 512 512"><title xmlns="">batteries</title><path fill="currentColor" d="M168.063 21.844c-25.008 0-47.713 5.09-64.97 13.968c-16.938 8.716-29.722 21.962-30.187 38.626h-.03v93.625C59.258 180.325 46.9 197.92 37.75 219.032c-9.94 22.934-14.284 45.82-13 65.187c1.25 18.84 8.173 35.74 23 42.905l.344.156c.302.143.598.274.906.408l23.875 10.343v108.814h.03c.675 16.458 13.396 29.547 30.19 38.187c17.257 8.88 39.97 13.97 64.968 13.97c24.996 0 47.71-5.09 64.968-13.97c16.794-8.64 29.515-21.728 30.19-38.186h.03V420.56l42.844 18.563l.22.094l74.56 32.31l7.72 3.345l.844.375v-.03c15.48 6.212 32.73-.264 47.468-12.345c15.01-12.302 28.71-31.118 38.656-54.063c9.946-22.944 14.315-45.823 13.032-65.187c-1.26-19.01-8.33-36.01-23.438-43.063v-.03l-8.562-3.72L382.03 264.5l-118.78-51.47V76.97c.025-.53.03-1.06.03-1.595c0-.315-.02-.625-.03-.938c-.465-16.663-13.248-29.91-30.188-38.624c-17.256-8.88-39.992-13.97-65-13.97zM140.25 43.062c.03-.005.064.006.094 0c-6.743 3.237-10.906 7.637-10.906 12.5c0 9.93 17.292 17.97 38.625 17.97s38.625-8.04 38.625-17.97c0-4.863-4.164-9.263-10.907-12.5c11.11 2.093 20.927 5.366 28.72 9.376c13.818 7.11 20.094 15.646 20.094 22.937c0 7.29-6.276 15.797-20.094 22.906c-13.818 7.11-34.028 11.907-56.438 11.907s-42.62-4.797-56.437-11.906c-13.818-7.108-20.063-15.614-20.063-22.905c0-7.29 6.245-15.828 20.063-22.938c7.772-3.998 17.554-7.28 28.625-9.374zM72.875 195.656v122l-16.438-7.125c-6.678-2.894-12.003-12.02-13.03-27.53c-1.03-15.51 2.593-35.983 11.5-56.53c5.082-11.73 11.316-22.17 17.968-30.814zm171.688 1.75V445.47c0 7.278-6.24 15.825-20.063 22.936s-34.042 11.906-56.438 11.906c-22.395 0-42.615-4.794-56.437-11.906c-13.822-7.11-20.063-15.658-20.063-22.937V200.31L145.375 280l-36.844 12.875L229.75 420.25l-51.72-105.78l23.907-11.845l-40.156-84.188c2.082.073 4.168.125 6.282.125c24.997 0 47.71-5.09 64.97-13.968c4.134-2.128 8.008-4.537 11.53-7.188m18.687 36l79 34.25l-79 14.78zm187.97 82.78c2.094.035 3.983.433 5.655 1.158c6.69 2.9 12.035 12.026 13.063 27.53c.577 8.715-.333 18.995-2.813 29.97c-.305-7.425-2.682-12.95-7.125-14.875c-9.11-3.95-23.39 8.707-31.875 28.28s-7.953 38.645 1.156 42.594c4.45 1.928 10.12-.104 15.75-4.97c-6.316 9.33-13.207 17.023-19.967 22.563c-12.02 9.85-22.342 12.18-29.032 9.282c-6.688-2.9-12.034-12.027-13.06-27.533c-1.03-15.505 2.618-35.94 11.53-56.5s21.357-37.21 33.375-47.062c9.014-7.388 17.058-10.537 23.344-10.438zm-153.5 8.658c-.9 1.89-1.78 3.8-2.626 5.75c-9.94 22.935-14.315 45.79-13.03 65.156c.307 4.65.966 9.173 1.998 13.47l-20.812-9v-74.126z"/></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><title xmlns="">bitcoin</title><path fill="currentColor" d="M13 3h2v2h2v2H9v4h8v2H9v4h8v2h-2v2h-2v-2h-2v2H9v-2H5v-2h2v-4H5v-2h2V7H5V5h4V3h2v2h2zm4 14v-4h2v4zm0-6V7h2v4z"/></svg>
|
||||
|
After Width: | Height: | Size: 262 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><title xmlns="">cloud-done</title><path fill="currentColor" d="M16 4h-6v2H8v2H4v2H2v2H0v6h2v2h20v-2h2v-6h-2v-2h-2V8h-2V6h-2zm0 2v2h2v4h4v6H2v-6h2v-2h4V8h2V6zm-6 6H8v2h2v2h2v-2h2v-2h2v-2h-2v2h-2v2h-2z"/></svg>
|
||||
|
After Width: | Height: | Size: 293 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><title xmlns="">cloud-moon</title><path fill="currentColor" d="M18 2h-8v2H8v2H6v4h2V6h2V4h4v2h-2v4h2v2h4v-2h2v4h-2v2h2v-2h2V6h-2v2h-2v2h-4V6h2V4h2zM8 14v-2h4v2zm0 2v-2H4v2H2v4h2v2h10v-2h2v-4h-2v-2h-2v2h2v4H4v-4zm0 0h2v2H8z"/></svg>
|
||||
|
After Width: | Height: | Size: 316 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><title xmlns="">debug-off</title><path fill="currentColor" d="M16 2h2v2h-2zm4 7h-2V6h-2V4h-2v2h-2v2h4v5h2v2h4v-2h-4v-2h2zm0 0V7h2v2zM8 20v-9H6V9H4V7H2v2h2v2h2v2H2v2h4v2H4v2H2v2h2v-2h2v3h10v-2zm2-5h2v2h-2zM2 2h2v2H2zm4 4H4V4h2zm2 2H6V6h2zm2 2H8V8h2zm0 0v2h2v2h2v2h2v2h2v2h2v2h2v-2h-2v-2h-2v-2h-2v-2h-2v-2h-2v-2z"/></svg>
|
||||
|
After Width: | Height: | Size: 404 B |
@@ -0,0 +1,31 @@
|
||||
<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<radialGradient id="bgGrad" cx="50%" cy="45%" r="55%">
|
||||
<stop offset="0%" stop-color="#1a1a1a"/>
|
||||
<stop offset="100%" stop-color="#050505"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="borderGrad" x1="0" y1="0" x2="1024" y2="1024" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0%" stop-color="#ffffff" stop-opacity="0.55"/>
|
||||
<stop offset="50%" stop-color="#888888" stop-opacity="0.25"/>
|
||||
<stop offset="100%" stop-color="#000000" stop-opacity="0.7"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="glassGlow" cx="35%" cy="30%" r="50%">
|
||||
<stop offset="0%" stop-color="#ffffff" stop-opacity="0.06"/>
|
||||
<stop offset="100%" stop-color="#000000" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background circle -->
|
||||
<circle cx="512" cy="512" r="484" fill="url(#bgGrad)"/>
|
||||
|
||||
<!-- Glass glow highlight (top-left) -->
|
||||
<circle cx="512" cy="512" r="460" fill="url(#glassGlow)"/>
|
||||
|
||||
<!-- Gradient border ring (white-to-dark, like logo-gradient-border CSS) -->
|
||||
<circle cx="512" cy="512" r="484" fill="none" stroke="url(#borderGrad)" stroke-width="16"/>
|
||||
|
||||
<!-- The "A" logo — pixel-art style, centered and scaled -->
|
||||
<g transform="translate(160, 168) scale(0.69)">
|
||||
<path d="M357.614 388.936V318H428.621V388.936H357.614ZM436.152 388.936V318H508.234V388.936H436.152ZM515.766 388.936V318H587.848V388.936H515.766ZM595.379 388.936V318H666.386V388.936H595.379ZM595.379 468.471V396.46H666.386V468.471H595.379ZM673.917 468.471V396.46H746V468.471H673.917ZM278 548.006V475.994H350.083V548.006H278ZM357.614 548.006V475.994H428.621V548.006H357.614ZM436.152 548.006V475.994H508.234V548.006H436.152ZM515.766 548.006V475.994H587.848V548.006H515.766ZM595.379 548.006V475.994H666.386V548.006H595.379ZM673.917 548.006V475.994H746V548.006H673.917ZM278 626.465V555.529H350.083V626.465H278ZM357.614 626.465V555.529H428.621V626.465H357.614ZM595.379 626.465V555.529H666.386V626.465H595.379ZM673.917 626.465V555.529H746V626.465H673.917ZM357.614 706V633.989H428.621V706H357.614ZM436.152 706V633.989H508.234V706H436.152ZM515.766 706V633.989H587.848V706H515.766ZM595.379 706V633.989H666.386V706H595.379Z" fill="white"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><title xmlns="">fill-half</title><path fill="currentColor" d="M9 2h2v2H9zm4 4V4h-2v2H9v2H7v2H5v2H3v2h2v2h2v2h2v2h2v2h2v-2h2v-2h2v-2h2v6h2V12h-2v-2h-2V8h-2V6zm0 0v2h2v2h2v2h2v2H5v-2h2v-2h2V8h2V6z"/></svg>
|
||||
|
After Width: | Height: | Size: 288 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><title xmlns="">github</title><path fill="currentColor" d="M5 2h4v2H7v2H5zm0 10H3V6h2zm2 2H5v-2h2zm2 2v-2H7v2H3v-2H1v2h2v2h4v4h2v-4h2v-2zm0 0v2H7v-2zm6-12v2H9V4zm4 2h-2V4h-2V2h4zm0 6V6h2v6zm-2 2v-2h2v2zm-2 2v-2h2v2zm0 2h-2v-2h2zm0 0h2v4h-2z"/></svg>
|
||||
|
After Width: | Height: | Size: 334 B |
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><title xmlns="">paint-bucket</title><path fill="currentColor" d="M8 3h8v2H8zm0 2H6v4H4v12h16V9h-2V5h-2v4H8zm8 6h2v8H6v-8h2v6h2v-4h2v2h2v-2h2z"/></svg>
|
||||
|
After Width: | Height: | Size: 235 B |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><title xmlns="">save</title><path fill="currentColor" d="M4 2h14v2H4v16h2v-6h12v6h2V6h2v16H2V2zm4 18h8v-4H8zM20 6h-2V4h2zM6 6h9v4H6z"/></svg>
|
||||
|
After Width: | Height: | Size: 226 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="1631" height="1624" viewBox="0 0 1631 1624" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M914.932 359.228H916.229V715.252H1630.47V1088.98H1451.41V1267.98H1274.33V1445H1093.31V1624H715.534V1264.77H714.237V908.748H0V535.02H179.051V356.025H356.135V178.996H537.154V0H914.932V359.228ZM916.229 1425.33H1073.64V1248.31H1254.66V1071.28H1431.74V913.918H916.229V1425.33ZM556.83 375.695H375.811V552.723H198.727V710.082H714.237V198.666H556.83V375.695Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 534 B |
@@ -0,0 +1,11 @@
|
||||
# App Icons – Canonical Source
|
||||
|
||||
**This is the single source of truth for all app icons.**
|
||||
|
||||
- **Path**: `neode-ui/public/assets/img/app-icons/`
|
||||
- **Naming**: `{app-id}.{ext}` (e.g. `fedimint.png`, `mempool.webp`, `lnd.svg`)
|
||||
- **Formats**: PNG, WebP, or SVG (prefer WebP for size, SVG for scalability)
|
||||
|
||||
All references in the codebase use `/assets/img/app-icons/{filename}`. Build outputs (web/dist, image-recipe) copy from here.
|
||||
|
||||
To add an icon: place the file here with the app-id as the filename. Run `npm run build` to update deployed assets.
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18.9 21.8V15.8H24.9V21.8H18.9ZM25.5 21.8V15.8H31.6V21.8H25.5ZM32.2 21.8V15.8H38.3V21.8H32.2ZM39 21.8V15.8H44.9V21.8H39ZM39 28.5V22.5H44.9V28.5H39ZM45.6 28.5V22.5H51.7V28.5H45.6ZM12.2 35.2V29.2H18.3V35.2H12.2ZM18.9 35.2V29.2H24.9V35.2H18.9ZM25.5 35.2V29.2H31.6V35.2H25.5ZM32.2 35.2V29.2H38.3V35.2H32.2ZM39 35.2V29.2H44.9V35.2H39ZM45.6 35.2V29.2H51.7V35.2H45.6ZM12.2 41.8V35.8H18.3V41.8H12.2ZM18.9 41.8V35.8H24.9V41.8H18.9ZM39 41.8V35.8H44.9V41.8H39ZM45.6 41.8V35.8H51.7V41.8H45.6ZM18.9 48.5V42.5H24.9V48.5H18.9ZM25.5 48.5V42.5H31.6V48.5H25.5ZM32.2 48.5V42.5H38.3V48.5H32.2ZM39 48.5V42.5H44.9V48.5H39Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 731 B |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 435 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,97 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#1a1a2e"/>
|
||||
<stop offset="100%" stop-color="#0d0d1a"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="btc" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#f7931a"/>
|
||||
<stop offset="100%" stop-color="#e88a0e"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="mask" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#f5f0e8"/>
|
||||
<stop offset="100%" stop-color="#d4c9b8"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="hood" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="#2d2d3d"/>
|
||||
<stop offset="100%" stop-color="#1a1a28"/>
|
||||
</linearGradient>
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="4" result="blur"/>
|
||||
<feMerge>
|
||||
<feMergeNode in="blur"/>
|
||||
<feMergeNode in="SourceGraphic"/>
|
||||
</feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="512" height="512" rx="80" fill="url(#bg)"/>
|
||||
|
||||
<!-- Matrix rain effect (subtle) -->
|
||||
<g opacity="0.08" fill="#00ff41" font-family="monospace" font-size="10">
|
||||
<text x="40" y="60">01101</text>
|
||||
<text x="120" y="90">10010</text>
|
||||
<text x="380" y="70">11001</text>
|
||||
<text x="430" y="120">01010</text>
|
||||
<text x="60" y="440">10110</text>
|
||||
<text x="400" y="460">01101</text>
|
||||
<text x="180" y="480">11010</text>
|
||||
<text x="320" y="50">10101</text>
|
||||
</g>
|
||||
|
||||
<!-- Hood / Cloak -->
|
||||
<path d="M256 100 C180 100 130 160 120 220 L110 380 C110 420 160 460 256 460 C352 460 402 420 402 380 L392 220 C382 160 332 100 256 100Z" fill="url(#hood)" stroke="#3d3d55" stroke-width="2"/>
|
||||
|
||||
<!-- Hood top -->
|
||||
<path d="M256 80 C200 80 155 120 140 170 L160 170 C170 130 210 100 256 100 C302 100 342 130 352 170 L372 170 C357 120 312 80 256 80Z" fill="#252535"/>
|
||||
|
||||
<!-- Guy Fawkes Mask -->
|
||||
<ellipse cx="256" cy="230" rx="80" ry="95" fill="url(#mask)"/>
|
||||
<!-- Pointed chin -->
|
||||
<path d="M200 280 Q256 360 312 280" fill="url(#mask)"/>
|
||||
|
||||
<!-- Eyes - dark, narrow -->
|
||||
<ellipse cx="226" cy="210" rx="18" ry="10" fill="#1a1a1a"/>
|
||||
<ellipse cx="286" cy="210" rx="18" ry="10" fill="#1a1a1a"/>
|
||||
<!-- Eye shine -->
|
||||
<circle cx="230" cy="208" r="3" fill="#4ade80" opacity="0.8"/>
|
||||
<circle cx="290" cy="208" r="3" fill="#4ade80" opacity="0.8"/>
|
||||
|
||||
<!-- Arched brows -->
|
||||
<path d="M205 192 Q226 178 248 192" fill="none" stroke="#2a2a2a" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M264 192 Q286 178 307 192" fill="none" stroke="#2a2a2a" stroke-width="3" stroke-linecap="round"/>
|
||||
|
||||
<!-- Mustache (curled) -->
|
||||
<path d="M232 248 Q256 260 280 248" fill="none" stroke="#2a2a2a" stroke-width="3" stroke-linecap="round"/>
|
||||
<path d="M228 248 Q218 252 212 244" fill="none" stroke="#2a2a2a" stroke-width="2.5" stroke-linecap="round"/>
|
||||
<path d="M284 248 Q294 252 300 244" fill="none" stroke="#2a2a2a" stroke-width="2.5" stroke-linecap="round"/>
|
||||
|
||||
<!-- Smile -->
|
||||
<path d="M222 268 Q256 290 290 268" fill="none" stroke="#c44" stroke-width="2.5" stroke-linecap="round"/>
|
||||
|
||||
<!-- Goatee -->
|
||||
<path d="M248 295 L256 310 L264 295" fill="none" stroke="#2a2a2a" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
|
||||
<!-- Bitcoin symbol on chest -->
|
||||
<g filter="url(#glow)" transform="translate(256, 400)">
|
||||
<circle r="28" fill="url(#btc)" opacity="0.9"/>
|
||||
<text x="0" y="10" text-anchor="middle" fill="white" font-size="32" font-weight="bold" font-family="Arial, sans-serif">₿</text>
|
||||
</g>
|
||||
|
||||
<!-- Laptop in left hand -->
|
||||
<g transform="translate(155, 370) rotate(-15)">
|
||||
<rect x="0" y="0" width="45" height="30" rx="3" fill="#333" stroke="#555" stroke-width="1.5"/>
|
||||
<rect x="3" y="3" width="39" height="20" rx="1" fill="#0a2a0a"/>
|
||||
<!-- Terminal text -->
|
||||
<text x="7" y="13" fill="#00ff41" font-size="6" font-family="monospace">$_</text>
|
||||
<text x="7" y="20" fill="#00ff41" font-size="5" font-family="monospace" opacity="0.6">run</text>
|
||||
<!-- Keyboard -->
|
||||
<rect x="2" y="30" width="41" height="3" rx="1" fill="#444"/>
|
||||
</g>
|
||||
|
||||
<!-- Orbiting particles -->
|
||||
<circle cx="140" cy="280" r="4" fill="#f7931a" opacity="0.7" filter="url(#glow)"/>
|
||||
<circle cx="375" cy="310" r="3" fill="#f7931a" opacity="0.5" filter="url(#glow)"/>
|
||||
<circle cx="350" cy="180" r="3.5" fill="#f7931a" opacity="0.6" filter="url(#glow)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,56 @@
|
||||
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_670_3179)">
|
||||
<path d="M255.871 0.125H0.128906V255.868H255.871V0.125Z" fill="url(#paint0_linear_670_3179)"/>
|
||||
<g opacity="0.2" filter="url(#filter0_f_670_3179)">
|
||||
<circle cx="128" cy="-68.0615" r="82.7188" fill="#F0D003"/>
|
||||
</g>
|
||||
<g opacity="0.2" filter="url(#filter1_f_670_3179)">
|
||||
<circle cx="128" cy="323.065" r="82.7188" fill="black"/>
|
||||
</g>
|
||||
<g filter="url(#filter2_d_670_3179)">
|
||||
<path d="M158.533 37L88.7227 130.934L168.984 110.063L120.9 110.096L158.533 37Z" fill="#F0D003"/>
|
||||
</g>
|
||||
<g filter="url(#filter3_d_670_3179)">
|
||||
<path d="M98.4517 218.004L168.228 124.07L88 144.941L136.084 144.908L98.4517 218.004Z" fill="#FFFAE6"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_670_3179" x="-52.7188" y="-248.78" width="361.438" height="361.438" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="49" result="effect1_foregroundBlur_670_3179"/>
|
||||
</filter>
|
||||
<filter id="filter1_f_670_3179" x="-52.7188" y="142.347" width="361.438" height="361.438" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="49" result="effect1_foregroundBlur_670_3179"/>
|
||||
</filter>
|
||||
<filter id="filter2_d_670_3179" x="69.7227" y="20" width="112.262" height="125.934" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="-3" dy="-1"/>
|
||||
<feGaussianBlur stdDeviation="8"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.941176 0 0 0 0 0.815686 0 0 0 0 0.0117647 0 0 0 0.16 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_670_3179"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_670_3179" result="shape"/>
|
||||
</filter>
|
||||
<filter id="filter3_d_670_3179" x="71" y="111.07" width="114.229" height="127.934" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dy="4"/>
|
||||
<feGaussianBlur stdDeviation="8.5"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 0.980392 0 0 0 0 0.901961 0 0 0 0.14 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_670_3179"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_670_3179" result="shape"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_670_3179" x1="128" y1="0.125" x2="128" y2="255.868" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#161616"/>
|
||||
<stop offset="1"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_670_3179">
|
||||
<rect width="256" height="256" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
|
||||
<rect width="64" height="64" rx="14" fill="#1a1a2e"/>
|
||||
<circle cx="32" cy="22" r="6" stroke="#4fc3f7" stroke-width="2.5" fill="none"/>
|
||||
<circle cx="18" cy="42" r="6" stroke="#4fc3f7" stroke-width="2.5" fill="none"/>
|
||||
<circle cx="46" cy="42" r="6" stroke="#4fc3f7" stroke-width="2.5" fill="none"/>
|
||||
<line x1="28" y1="27" x2="21" y2="37" stroke="#4fc3f7" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="36" y1="27" x2="43" y2="37" stroke="#4fc3f7" stroke-width="2" stroke-linecap="round"/>
|
||||
<line x1="24" y1="42" x2="40" y2="42" stroke="#4fc3f7" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 679 B |
|
After Width: | Height: | Size: 408 KiB |
|
After Width: | Height: | Size: 408 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,12 @@
|
||||
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H256V256H0V0Z" fill="url(#paint0_linear)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M60.7921 75.0001C46.339 74.9589 26.9714 85.292 28.0426 111.19C29.7163 151.656 66.5827 155.41 81.3211 155.735C82.9381 163.325 100.279 189.504 113.117 190.884H169.365C203.092 188.359 228.349 76.1915 209.625 75.7653C178.663 77.4046 160.307 78.2293 144.568 78.3679V113.38L139.662 110.943L139.63 78.389C122.634 78.3826 107.561 77.5457 80.7054 76.0546L80.7014 76.0544C79.0119 75.9606 77.2757 75.8642 75.4894 75.7653C73.8441 75.7477 71.9167 75.6034 69.7976 75.4447C67.0518 75.239 63.9842 75.0092 60.7921 75.0001ZM64.5638 89.3204C66.6097 110.05 69.9472 122.179 76.703 140.709C59.4699 138.431 44.8043 132.8 42.1069 111.778C40.6672 100.558 45.7259 88.8146 64.5638 89.3204ZM133.366 110.539L165.771 126.284C169.894 128.288 171.612 133.254 169.609 137.378L153.864 169.782C151.86 173.905 146.894 175.624 142.77 173.62L110.366 157.875C106.242 155.872 104.524 150.905 106.527 146.782L122.273 114.377C124.276 110.254 129.243 108.536 133.366 110.539Z" fill="white"/>
|
||||
<path d="M142.897 115.105L139.261 113.33L135.027 122.001C134.401 121.988 133.762 122.088 133.137 122.312C130.404 123.296 128.985 126.31 129.969 129.043C130.176 129.618 130.473 130.134 130.837 130.582L123.595 145.415C123.015 145.419 122.425 145.52 121.848 145.728C119.114 146.712 117.696 149.725 118.68 152.459C119.664 155.192 122.677 156.61 125.411 155.626C128.144 154.643 129.563 151.629 128.579 148.896C128.307 148.14 127.88 147.485 127.347 146.954L134.405 132.498C135.158 132.573 135.94 132.485 136.7 132.211C137.333 131.983 137.896 131.647 138.375 131.23C138.699 131.383 139.015 131.533 139.323 131.678C141.612 132.76 143.442 133.625 144.933 134.455C147.461 135.862 148.23 136.777 148.465 137.665C148.738 138.694 148.497 140.358 147.071 143.785C146.036 146.271 144.519 149.382 142.527 153.467L142.527 153.468C142.48 153.563 142.433 153.659 142.386 153.755C141.734 153.731 141.066 153.829 140.414 154.064C137.681 155.048 136.263 158.062 137.246 160.795C138.23 163.528 141.244 164.947 143.977 163.963C146.711 162.979 148.129 159.965 147.145 157.232C146.901 156.553 146.531 155.955 146.072 155.455C146.089 155.421 146.105 155.387 146.122 155.352C148.137 151.22 149.731 147.951 150.817 145.344C152.224 141.964 153.048 139.12 152.387 136.626C151.69 133.992 149.53 132.369 146.906 130.909C145.32 130.026 143.277 129.06 140.856 127.914C140.631 127.807 140.402 127.699 140.17 127.589C140.214 126.894 140.119 126.178 139.868 125.48C139.613 124.771 139.22 124.149 138.732 123.636L142.897 115.105Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="128" y1="0" x2="128" y2="256" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#8AB11A"/>
|
||||
<stop offset="1" stop-color="#609926"/>
|
||||
<stop offset="1" stop-color="#58921D"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 241 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
@@ -0,0 +1,28 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" role="img" aria-labelledby="title desc">
|
||||
<title id="title">Meshtastic</title>
|
||||
<desc id="desc">LoRa mesh radio nodes connected by signal links</desc>
|
||||
<defs>
|
||||
<linearGradient id="radioBody" x1="27" y1="28" x2="93" y2="107" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#f8fafc"/>
|
||||
<stop offset="1" stop-color="#94a3b8"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="signal" x1="16" y1="14" x2="112" y2="114" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#38bdf8"/>
|
||||
<stop offset="0.52" stop-color="#22c55e"/>
|
||||
<stop offset="1" stop-color="#f59e0b"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="128" height="128" rx="26" fill="#0f172a"/>
|
||||
<path d="M36 31 50 78" stroke="#64748b" stroke-width="6" stroke-linecap="round"/>
|
||||
<path d="M32 28c7-5 16-5 23 0" stroke="#38bdf8" stroke-width="5" stroke-linecap="round" fill="none"/>
|
||||
<path d="M24 18c12-9 28-9 40 0" stroke="#38bdf8" stroke-opacity=".55" stroke-width="5" stroke-linecap="round" fill="none"/>
|
||||
<rect x="42" y="54" width="50" height="55" rx="11" fill="url(#radioBody)"/>
|
||||
<rect x="51" y="64" width="32" height="15" rx="4" fill="#0f172a"/>
|
||||
<circle cx="57" cy="94" r="5" fill="#0f172a"/>
|
||||
<circle cx="77" cy="94" r="5" fill="#0f172a"/>
|
||||
<circle cx="26" cy="84" r="8" fill="#38bdf8"/>
|
||||
<circle cx="102" cy="43" r="8" fill="#22c55e"/>
|
||||
<circle cx="103" cy="99" r="8" fill="#f59e0b"/>
|
||||
<path d="M33 81 51 72M82 65l15-17M88 93l8 3" stroke="url(#signal)" stroke-width="5" stroke-linecap="round"/>
|
||||
<path d="M22 103c25-16 55-16 84 0" stroke="#22c55e" stroke-opacity=".32" stroke-width="5" stroke-linecap="round" fill="none"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 19 KiB |
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" role="img" aria-label="NetBird">
|
||||
<defs>
|
||||
<linearGradient id="g" x1="18" y1="14" x2="110" y2="116" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#24c8ff"/>
|
||||
<stop offset="1" stop-color="#3157ff"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="128" height="128" rx="28" fill="#071422"/>
|
||||
<path d="M28 72c16-30 39-46 72-50-11 13-18 26-21 40 10-1 19 1 29 5-19 4-35 13-48 27-8 8-18 12-30 12 7-7 12-14 15-22-7 0-13-4-17-12Z" fill="url(#g)"/>
|
||||
<circle cx="82" cy="43" r="6" fill="#fff" opacity=".95"/>
|
||||
<path d="M36 72c10 3 20 4 30 2" fill="none" stroke="#fff" stroke-width="6" stroke-linecap="round" opacity=".8"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 702 B |
|
After Width: | Height: | Size: 4.3 KiB |
@@ -0,0 +1,94 @@
|
||||
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="256" height="256" fill="white"/>
|
||||
<g clip-path="url(#clip0_1414_247)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M55.6017 170.3V169.701L55 169.4V170.001L55.6017 170.3Z" fill="#FAA42F"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M55 149.1L55.6017 148.699V138.299L55 138.002V149.1Z" fill="#DDB02A"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M55 169.4L55.6017 169.701L62.8258 165.5V152.9L55.6017 148.699L55 149.1V169.4Z" fill="#C47728"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M73.1622 180.498L73.764 180.102L82.7949 185.4V186.003L73.1622 180.498Z" fill="#F15933"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M73.1623 180.498L73.764 180.102V171.801L62.8258 165.5L55.6017 169.701V170.3L73.1623 180.498Z" fill="#F37132"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M55 117.703L55.6017 117.402V106.902L55 106.6V117.703Z" fill="#D6E042"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M55 138.003L55.5999 138.3L61.9214 134.701C61.6365 132.479 61.503 130.241 61.5221 128.001C61.5221 125.698 61.7226 123.298 61.9214 121L55.5999 117.396L55 117.697V138.003Z" fill="#FCEA3B"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M55.5999 148.8L62.8258 152.901L73.764 146.699V138.297L68.4435 135.201C68.5456 136.703 68.8464 138.201 69.1473 139.701L63.3272 143.097C62.6158 140.343 62.1454 137.531 61.9214 134.695L55.5999 138.294V148.8Z" fill="#FAA42F"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M73.764 180.102V171.802L76.6722 170.102L76.5737 170.003C77.9777 171.702 79.482 173.3 80.988 174.801C83.5103 177.311 86.2277 179.618 89.1147 181.7L82.7932 185.4L73.764 180.102ZM84.6001 165.502L91.9244 169.701V175.801C89.6606 174.155 87.5453 172.314 85.6029 170.3C84.4224 169.209 83.3162 168.04 82.2917 166.802L84.6001 165.502Z" fill="#F15933"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M110.088 201.702V201.001L100.957 195.803L100.456 196.103L110.088 201.702Z" fill="#E22574"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M100.456 196.103L100.957 195.803V188.5C96.7762 186.668 92.8057 184.39 89.1147 181.707L82.7932 185.407V186.01L100.456 196.103Z" fill="#EF3839"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M55 86.3L55.6017 85.9997V85.7012L55 85.9997V86.3Z" fill="#BBD64B"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M55 106.599L55.6017 106.901L64.6327 101.701V91.2021L55.6017 86L55 86.3003V106.599Z" fill="#D6E042"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M55.5999 117.397L61.9214 120.995C62.6159 114.747 64.17 108.625 66.5399 102.801L64.6327 101.703L55.5999 106.902V117.397ZM68.4435 120.397L73.764 117.397V106.902L72.1522 105.999C70.3068 110.623 69.0587 115.463 68.4381 120.402L68.4435 120.397Z" fill="#FFCF29"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M68.4435 135.201L73.764 138.3L82.7931 133.101V122.6L73.764 117.396L68.4435 120.396C68.1248 122.917 67.958 125.455 67.9439 127.995C67.9579 130.404 68.1248 132.809 68.4435 135.196V135.201Z" fill="#FAA42F"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M84.6001 152.901L90.9216 149.299L100.957 155.1V164.399L91.9262 169.701L84.6001 165.5V152.901Z" fill="#F15933"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M100.957 195.803L110.09 201.001L119.223 195.803V193.702C112.8 192.808 106.679 191.103 100.957 188.5V195.803ZM119.223 187.2V185.4L110.09 180.102L104.77 183.201C109.39 185.163 114.249 186.508 119.222 187.2H119.223Z" fill="#EE438D"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M84.6001 152.902L90.9216 149.299V137.701L82.7949 133.102L73.764 138.3V146.7L84.6001 152.902Z" fill="#F37132"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M127.649 211.802L128.252 211.502V201.001L119.22 195.803L110.088 201.001V201.702L127.649 211.802Z" fill="#CC3192"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M104.768 183.201L110.088 180.101V169.701L100.955 164.398L91.9226 169.701V175.8C95.8595 178.82 100.178 181.308 104.766 183.201H104.768Z" fill="#BD2025"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M128.55 211.702L128.252 211.502L127.649 211.802L128.05 212.001L128.55 211.702Z" fill="#9B3995"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M55.6017 85.9992L64.6327 91.2013L73.764 85.9992V75.4985L73.4631 75.3984L55.6017 85.7007V85.9992Z" fill="#F1EB59"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M64.6327 101.703L66.5399 102.8C68.8464 97.0977 71.957 91.8957 75.6729 87.1012L73.7657 86L64.6327 91.2021V101.703ZM72.154 105.993L73.7657 106.896L82.7949 101.697V91.2021L81.2907 90.4012C77.4984 95.1263 74.4212 100.381 72.1576 105.998L72.154 105.993Z" fill="#FCEA3B"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M73.764 117.402L82.7949 122.601L90.9216 117.901V106.703L91.2242 106.501L82.7949 101.701L73.764 106.901V117.402Z" fill="#FAA42F"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M110.088 180.102L119.22 185.401L128.252 180.102V170.503L128.05 170.601L118.217 165.002L110.088 169.702V180.102Z" fill="#E22574"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M82.7949 133.101L90.9216 137.7V117.9L82.7949 122.6V133.101Z" fill="#F7975F"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M119.22 195.803L128.251 201.001L137.384 195.803V193.602C134.289 194.001 131.171 194.201 128.05 194.201C125.101 194.19 122.154 194.023 119.222 193.702L119.22 195.803ZM137.382 187.1V185.4L128.249 180.102L119.218 185.4V187.2C122.143 187.605 125.093 187.806 128.047 187.801C131.171 187.816 134.293 187.581 137.38 187.1H137.382Z" fill="#CC3192"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M100.957 164.399L110.088 169.701L118.217 165.001L100.957 155.1V164.399Z" fill="#D31F26"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M128.253 211.502L128.55 211.702L146.411 201.402V201.001L137.384 195.803L128.253 201.001V211.502Z" fill="#73206A"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M73.4631 75.3985L73.764 75.4987L82.7949 70.3002V69.998L73.4631 75.3985Z" fill="#D6E042"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M73.764 86.0001L75.6712 87.1013C77.3742 85.0008 79.1829 83.1023 80.988 81.2021C83.5859 78.5954 86.4063 76.2195 89.4173 74.1015L82.7914 70.3027L73.7622 75.5012L73.764 86.0001ZM81.2853 90.4013L82.7896 91.2022L91.9226 86.0001V80.2028C89.6571 81.846 87.5417 83.6861 85.6011 85.7016C84.0969 87.1996 82.6929 88.6977 81.2871 90.4013H81.2853Z" fill="#FFCF29"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M82.7949 101.701L91.2242 106.501L100.957 100.902V91.2021L91.9262 86L82.7949 91.2021V101.701Z" fill="#FAA42F"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M155.845 195.999L155.542 195.803L146.411 201.001V201.402L155.845 195.999Z" fill="#853A96"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M128.253 180.101L137.384 185.4L146.411 180.101V169.701L138.086 164.799L128.253 170.501V180.101Z" fill="#CC3192"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M137.382 195.803L146.411 201.001L155.544 195.803V188.294C149.774 190.917 143.658 192.704 137.382 193.602V195.803ZM151.529 183.101L146.411 180.102L137.382 185.4V187.1C142.299 186.299 147.113 185 151.529 183.101Z" fill="#9B3995"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M82.7932 70.3L89.4191 74.0988C93.0328 71.5158 96.8992 69.3045 100.959 67.4987V59.7993L100.757 59.6992L82.7949 69.9979L82.7932 70.3Z" fill="#FFCF29"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M91.9244 85.9998L100.957 91.2019L110.09 85.9998V75.4992L105.166 72.7051C100.348 74.6071 95.934 77.2064 91.9137 80.2132L91.9244 85.9998Z" fill="#D98A26"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M155.542 195.803L155.843 196.001L173.705 185.702V185.4L167.079 181.602C163.456 184.199 159.592 186.443 155.539 188.302L155.542 195.803Z" fill="#8F4F9F"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M118.518 90.7981L110.088 86L100.957 91.2021V100.902L118.518 90.7981Z" fill="#F37132"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M138.086 164.799L146.411 169.701L155.542 164.399V154.799L138.086 164.799Z" fill="#D24B9B"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M146.411 180.101L151.529 183.101C156.203 181.129 160.591 178.539 164.573 175.4V169.701L155.542 164.398L146.409 169.701L146.411 180.101Z" fill="#B263A7"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M100.755 59.6993L100.957 59.7994L110.088 54.5956V54.2988L100.755 59.6993Z" fill="#FFCF29"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M105.166 72.7048L110.083 75.506L117.406 71.4052V69.0991L117.809 69.0008C113.47 69.7628 109.229 71.0052 105.166 72.7048ZM117.406 58.804L110.088 54.5977L100.955 59.7997V67.4991C106.216 65.1929 111.743 63.5475 117.409 62.601L117.406 58.804Z" fill="#FAA42F"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M167.082 181.602L173.708 185.4L182.841 180.102V171.802L171.9 165.502L164.573 169.701V175.4C165.78 174.599 166.781 173.7 167.888 172.799L173.705 176.101C171.614 178.071 169.401 179.908 167.079 181.602H167.082Z" fill="#6B297A"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M183.139 180.3L182.838 180.102L173.706 185.4V185.7L183.139 180.3Z" fill="#782A8F"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M110.088 86L118.518 90.798L128.05 85.4011L128.252 85.4994V77.598L117.413 71.3984L110.088 75.4993V86Z" fill="#F37132"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M155.542 164.399L164.575 169.701L171.9 165.5V152.901L165.078 149.002V149.299L155.542 154.799V164.399Z" fill="#9B3995"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M110.088 54.5954L117.413 58.7964L128.252 52.4967V44.1984L128.05 44L110.088 54.2987V54.5954Z" fill="#FAA42F"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M128.253 85.4994V77.598L139.189 71.3984L146.411 75.4993V86L137.783 91L128.253 85.4994Z" fill="#F15933"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M182.838 180.102L183.139 180.3L201 170.001V169.701L193.674 165.5L182.838 171.801V180.102Z" fill="#782A8F"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M165.078 138.002L173.706 133.1V122.6L165.078 117.6V138.002Z" fill="#A01664"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M155.542 101.199V91.2021L146.411 86L137.783 91.0001L155.542 101.199Z" fill="#C02026"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M165.078 149.003L171.9 152.902L182.838 146.7V138.3L173.706 133.102L165.078 138.003V149.003Z" fill="#9B3995"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M139.191 58.7969L146.413 54.5977L155.546 59.7997V67.6994C152.817 66.4584 150 65.4217 147.117 64.5978V71.3015C148.521 71.7105 149.897 72.2109 151.236 72.7995L146.418 75.4989L139.191 71.398V58.7969Z" fill="#F15933"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M184.643 147.992C186.081 143.949 187.058 139.756 187.555 135.494L182.836 138.292V146.694L184.745 147.795L184.643 147.992ZM193.674 152.894L201 148.693V138.299L194.168 134.4C193.593 140.146 192.278 145.794 190.255 151.204L190.359 151.002L193.674 152.894Z" fill="#9051A0"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M164.976 106.703L173.706 101.701V91.2021L164.575 86L155.542 91.2021V101.199L164.976 106.703Z" fill="#B01B4A"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M139.189 58.7972L146.411 54.5962L128.253 44.1992V52.4975L139.189 58.7972Z" fill="#C54627"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M165.078 117.601L173.706 122.601L182.838 117.402V106.901L173.706 101.701L164.976 106.703H165.078V117.601Z" fill="#A41768"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M146.411 85.9989L155.544 91.2009L164.575 85.9989V80.5984C160.514 77.3892 156.022 74.7639 151.23 72.7988L146.413 75.4982L146.411 85.9989Z" fill="#EF3839"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M173.705 133.101L182.838 138.3L187.556 135.502C187.878 133.012 188.045 130.504 188.056 127.994C188.041 125.353 187.874 122.714 187.556 120.092L182.836 117.396L173.703 122.595L173.705 133.101Z" fill="#9B3995"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M194.168 134.401L200.995 138.3V117.396L194.07 121.295C194.356 123.517 194.49 125.756 194.471 127.995C194.471 130.194 194.37 132.295 194.168 134.395V134.401Z" fill="#6B297A"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M164.573 85.9999L173.706 91.202L174.81 90.5996C173.48 88.8745 172.039 87.2383 170.496 85.7014C168.617 83.8938 166.641 82.1905 164.575 80.5994L164.573 85.9999ZM180.427 87.3031L182.836 85.9999V75.4993L173.703 70.3008L166.882 74.298C169.772 76.3758 172.49 78.684 175.007 81.2001C176.916 83.1402 178.724 85.1773 180.424 87.3031H180.427Z" fill="#E22574"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M173.705 101.703L182.838 106.901L183.941 106.299C181.757 100.612 178.674 95.3115 174.808 90.5996L173.705 91.2021V101.703ZM189.559 103.002L191.869 101.703V91.2021L182.836 86L180.427 87.3032C184.16 92.1058 187.232 97.3857 189.56 103.002H189.559Z" fill="#CC3192"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M166.884 74.2986L173.705 70.3015L155.542 59.8008V67.7004C159.538 69.5091 163.338 71.72 166.884 74.2986Z" fill="#EF3839"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M182.836 117.397L187.555 120.094C186.884 115.371 185.671 110.74 183.939 106.294L182.836 106.896V117.397ZM194.075 121.296L201 117.397V106.902L191.867 101.703L189.557 103.003C191.867 108.804 193.47 114.901 194.073 121.301L194.075 121.296Z" fill="#B263A7"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M182.838 86.0007L191.869 91.2027L201 86.0007L182.838 75.5V86.0007Z" fill="#D1499A"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M191.869 101.701L201 106.901V86L191.869 91.2021V101.701Z" fill="#9B3995"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M193.674 165.5L201 169.701V148.799V148.699L193.674 152.9V165.5Z" fill="#85459A"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M109.186 137.801L110.088 138.3V139.302L109.186 138.798V137.801Z" fill="#F15933"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M109.186 117.901L110.088 117.402L119.22 122.601V133.102L110.088 138.3L109.186 137.801V117.901Z" fill="#F15933"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M128.954 149.1L128.252 148.699L127.348 149.298L128.05 149.699L128.954 149.1Z" fill="#E22574"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M127.348 149.299L128.252 148.701V138.3L119.22 133.102L110.088 138.3V139.303L127.348 149.299Z" fill="#EF3839"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M128.253 148.701L128.954 149.101L146.411 139.001V138.3L137.384 133.102L128.253 138.3V148.701Z" fill="#EE3F8A"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M119.22 133.102L128.253 138.3L137.384 133.102V122.601L128.253 117.402L119.22 122.601V133.102Z" fill="#EF3839"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M137.384 133.102L146.411 138.3L146.816 138.102V117.601L146.411 117.402L137.384 122.601V133.102Z" fill="#E22574"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M146.816 138.102L146.411 138.3V139.001L146.816 138.799V138.102Z" fill="#CC3192"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M109.186 117.901L110.088 117.402V116.699L109.186 117.2V117.901Z" fill="#F37132"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M110.088 117.402V116.7L127.649 106.6L128.252 106.902V117.402L119.22 122.601L110.088 117.402Z" fill="#F15933"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M128.654 106.703L128.252 106.901L127.649 106.599L128.05 106.4L128.654 106.703Z" fill="#F4805F"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M128.253 117.402L137.384 122.601L146.411 117.402V117.002L128.654 106.703L128.253 106.902V117.402Z" fill="#EF3839"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M146.411 117.402L146.816 117.601V117.2L146.411 117.002V117.402Z" fill="#E22574"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M122.029 68.6998L128.252 72.3019L134.47 68.6998V61.5009L128.252 57.9023L122.029 61.5009V68.6998Z" fill="#F37132"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M176.514 162.799L182.838 166.401L189.059 162.799V155.599L182.838 152.002L176.514 155.599V162.799Z" fill="#552263"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M67.5427 162.799L73.764 166.401L79.9852 162.799V155.599L73.764 152.002L67.5427 155.599V162.799Z" fill="#F37132"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1414_247">
|
||||
<rect width="146" height="168" fill="white" transform="translate(55 44)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="none">
|
||||
<rect width="512" height="512" rx="96" fill="#7B68EE"/>
|
||||
<text x="256" y="300" font-family="Arial, sans-serif" font-size="200" font-weight="bold" fill="white" text-anchor="middle">nS</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 275 B |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,10 @@
|
||||
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H256V256H0V0Z" fill="url(#paint0_linear)"/>
|
||||
<path d="M127.873 59L144.779 70.3746M205.981 175.231L202.56 195.889L205.981 175.231ZM50.1067 176.077L62.7712 188.032L50.1067 176.077ZM144.659 70.4857L62.6857 187.87C62.6756 187.881 62.6691 187.895 62.6669 187.91C62.6647 187.925 62.6669 187.94 62.6733 187.954C62.6797 187.967 62.6899 187.979 62.7028 187.987C62.7156 187.995 62.7305 187.999 62.7456 187.998L202.483 196C202.497 196.001 202.511 195.999 202.523 195.993C202.536 195.987 202.547 195.978 202.555 195.967C202.563 195.956 202.567 195.942 202.568 195.928C202.569 195.915 202.566 195.901 202.56 195.889L144.779 70.5027C144.765 70.4846 144.745 70.4726 144.722 70.4694C144.7 70.4662 144.677 70.4721 144.659 70.4857V70.4857ZM127.779 59.1366L50.0126 175.915C50.0039 175.931 49.9996 175.95 50 175.968C50.0004 175.987 50.0056 176.005 50.015 176.022C50.0243 176.038 50.0377 176.051 50.0537 176.061C50.0697 176.071 50.0879 176.076 50.1067 176.077L205.895 175.394C205.914 175.394 205.933 175.389 205.949 175.38C205.966 175.37 205.979 175.356 205.988 175.339C205.997 175.322 206.001 175.303 206 175.284C205.998 175.265 205.992 175.247 205.981 175.231L127.958 59.1366C127.949 59.1206 127.936 59.1073 127.921 59.098C127.905 59.0887 127.887 59.0838 127.868 59.0838C127.85 59.0838 127.832 59.0887 127.816 59.098C127.8 59.1073 127.787 59.1206 127.779 59.1366Z" stroke="#1D1D1B" stroke-width="5" stroke-miterlimit="10"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear" x1="37.4885" y1="218.512" x2="218.512" y2="37.4885" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#C2FDE4"/>
|
||||
<stop offset="1" stop-color="#CDC6E9"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Pine">
|
||||
<rect width="512" height="512" fill="#ffffff"/>
|
||||
<g fill="#000000">
|
||||
<!-- trunk -->
|
||||
<rect x="236" y="396" width="40" height="72" rx="6"/>
|
||||
<!-- three tiers of the evergreen, top to bottom -->
|
||||
<path d="M256 44 L336 168 L296 168 L256 108 L216 168 L176 168 Z"/>
|
||||
<path d="M256 150 L360 300 L300 300 L256 236 L212 300 L152 300 Z"/>
|
||||
<path d="M256 262 L392 430 L120 430 L256 262 Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 508 B |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 208 KiB |