Initial commit
This commit is contained in:
commit
1812b2612f
5 changed files with 482 additions and 0 deletions
6
.dockerignore
Normal file
6
.dockerignore
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
Dockerfile
|
||||
.dockerignore
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
*.md
|
||||
42
Dockerfile
Normal file
42
Dockerfile
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# fly.x landing — static site served by nginx-alpine.
|
||||
# Small (~25 MB), no build step, no state.
|
||||
#
|
||||
# Build:
|
||||
# docker build -t code.silentmode.st/silentmode/fly:latest .
|
||||
#
|
||||
# Push (after `docker login code.silentmode.st`):
|
||||
# docker push code.silentmode.st/silentmode/fly:latest
|
||||
#
|
||||
# Deploy on Flux/Orbit: image = code.silentmode.st/silentmode/fly:latest, port 80.
|
||||
|
||||
FROM nginx:1.27-alpine
|
||||
|
||||
# Drop the default nginx welcome page.
|
||||
RUN rm -rf /usr/share/nginx/html/*
|
||||
|
||||
# Ship the site.
|
||||
COPY index.html /usr/share/nginx/html/
|
||||
COPY js/ /usr/share/nginx/html/js/
|
||||
|
||||
# Tighten nginx defaults for a static-only workload:
|
||||
# - long cache on the fonts + js
|
||||
# - short cache on the HTML so we can ship a redeploy fast
|
||||
# - gzip on for text/css/js
|
||||
RUN printf '%s\n' \
|
||||
'server {' \
|
||||
' listen 80 default_server;' \
|
||||
' root /usr/share/nginx/html;' \
|
||||
' index index.html;' \
|
||||
' gzip on;' \
|
||||
' gzip_types text/plain text/css text/html application/javascript application/json image/svg+xml;' \
|
||||
' location = / { add_header Cache-Control "no-cache, must-revalidate"; try_files /index.html =404; }' \
|
||||
' location = /index.html { add_header Cache-Control "no-cache, must-revalidate"; }' \
|
||||
' location /js/ { add_header Cache-Control "public, max-age=86400"; }' \
|
||||
' location /favicon.svg { access_log off; try_files $uri =404; }' \
|
||||
' location / { try_files $uri $uri/ =404; }' \
|
||||
'}' > /etc/nginx/conf.d/default.conf
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=2 \
|
||||
CMD wget -qO- --tries=1 http://localhost/ >/dev/null || exit 1
|
||||
57
README.md
Normal file
57
README.md
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# fly.x
|
||||
|
||||
Static landing page for `fly.x` — a decentralized flight-search entry point on
|
||||
Bitcoin Cash. This repo holds the landing HTML + the Dockerfile that packages
|
||||
it for Flux (or any container host).
|
||||
|
||||
## Development
|
||||
|
||||
Just open `index.html` in a browser. Nothing to build.
|
||||
|
||||
## Build the container
|
||||
|
||||
```bash
|
||||
docker build -t code.silentmode.st/silentmode/fly:latest .
|
||||
```
|
||||
|
||||
Verify locally:
|
||||
|
||||
```bash
|
||||
docker run --rm -p 8080:80 code.silentmode.st/silentmode/fly:latest
|
||||
# → open http://localhost:8080
|
||||
```
|
||||
|
||||
## Publish to Hephaestus registry
|
||||
|
||||
Hephaestus (Forgejo 10) exposes an OCI registry at `code.silentmode.st`. First
|
||||
time you push, log in with a personal access token that has the `write:package`
|
||||
scope — see Settings → Applications on your Hephaestus account.
|
||||
|
||||
```bash
|
||||
docker login code.silentmode.st -u <your-username>
|
||||
docker push code.silentmode.st/silentmode/fly:latest
|
||||
```
|
||||
|
||||
## Deploy on Flux (Orbit)
|
||||
|
||||
1. Go to <https://orbit.runonflux.com/dashboard/deploy>.
|
||||
2. Image reference: `code.silentmode.st/silentmode/fly:latest`.
|
||||
3. Port: `80` (HTTP).
|
||||
4. Resources: minimum — the container is stateless and idle-cheap.
|
||||
5. Deploy. Flux gives you a URL back plus the assigned IP.
|
||||
|
||||
## Wire fly.x to the deployment
|
||||
|
||||
Once Flux hands you an IP, update the fly.x records on Sirius:
|
||||
|
||||
- `ip` → the Flux-assigned IP
|
||||
- `s3` → Sia bucket key for the static-mirror backup (optional)
|
||||
- `tls` → Silent Mode CA fingerprint (already set on mint)
|
||||
|
||||
Verify:
|
||||
|
||||
```bash
|
||||
curl -s https://silentmode.st/api/dns/fly.x | jq
|
||||
curl -sk https://fly.x/ | head -5 # via Theseus/Ariadne
|
||||
curl -sk https://navigate.st/bns/fly.x | head -5 # via gateway
|
||||
```
|
||||
314
index.html
Normal file
314
index.html
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Fly.X — flights, priced in Bitcoin Cash</title>
|
||||
<meta name="description" content="Find a flight, pay from your wallet. Fly.X is a decentralized flight-search entry point on Bitcoin Cash — no account, no email, one signature to book.">
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
|
||||
<link rel="mask-icon" href="/favicon.svg" color="#d6ff3d">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="Fly.X">
|
||||
<meta property="og:title" content="Fly.X — flights, priced in Bitcoin Cash">
|
||||
<meta property="og:description" content="Find a flight, pay from your wallet. Decentralized flight-search entry point on Bitcoin Cash.">
|
||||
<meta property="og:url" content="https://fly.x/">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="Fly.X — flights, priced in Bitcoin Cash">
|
||||
<meta name="twitter:description" content="Find a flight, pay from your wallet. One signature to book.">
|
||||
<!-- Silent Mode family fonts — Fraunces editorial + DM Sans body + JetBrains
|
||||
Mono for code/hashes + Ubuntu as the Bitcoin Cash brand alt. -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300;9..144,400;9..144,500;9..144,600;9..144,700&family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&family=Ubuntu:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root{
|
||||
--bg:#050810; --panel:#141a24; --panel2:#0a0f1c; --line:rgba(255,255,255,.06);
|
||||
--sky-mid:#0a0f1c; --sky-rim:#131a2a;
|
||||
--ink:#f1f4fa; --mut:#b8c2d4; --dim:#6a7488; --acid:#d6ff3d;
|
||||
--chain-native:#0AC18E;
|
||||
--sky-blue:#4b7bec;
|
||||
--sunrise:#ffb86c;
|
||||
--serif:'Fraunces',Georgia,serif;
|
||||
--sans:'DM Sans','Inter',system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
|
||||
--mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
|
||||
}
|
||||
*{box-sizing:border-box}
|
||||
html{scroll-behavior:smooth}
|
||||
body{margin:0;
|
||||
background:radial-gradient(ellipse 1400px 700px at 50% -8%,var(--sky-rim) 0%,transparent 62%),
|
||||
radial-gradient(ellipse 900px 500px at 100% 100%,var(--sky-mid) 0%,transparent 55%),
|
||||
radial-gradient(ellipse 900px 500px at 0% 100%,#0c1424 0%,transparent 55%),
|
||||
var(--bg);
|
||||
background-attachment:fixed;
|
||||
color:var(--ink);font:16px/1.6 var(--sans);min-height:100vh;
|
||||
font-feature-settings:"ss01","ss02"}
|
||||
h1,h2,h3{font-family:var(--serif);font-feature-settings:"ss01","ss02"}
|
||||
code,.mono{font-family:var(--mono)}
|
||||
html[data-font-cmp="ubuntu"] body,
|
||||
html[data-font-cmp="ubuntu"] h1, html[data-font-cmp="ubuntu"] h2, html[data-font-cmp="ubuntu"] h3,
|
||||
html[data-font-cmp="ubuntu"] p, html[data-font-cmp="ubuntu"] a, html[data-font-cmp="ubuntu"] button,
|
||||
html[data-font-cmp="ubuntu"] span, html[data-font-cmp="ubuntu"] div,
|
||||
html[data-font-cmp="ubuntu"] label, html[data-font-cmp="ubuntu"] input, html[data-font-cmp="ubuntu"] li{
|
||||
font-family:'Ubuntu',system-ui,sans-serif !important
|
||||
}
|
||||
html[data-font-cmp="ubuntu"] code, html[data-font-cmp="ubuntu"] .mono{
|
||||
font-family:var(--mono) !important
|
||||
}
|
||||
.font-picker{display:inline-flex;align-items:center;gap:2px;padding:2px;margin:0 10px;
|
||||
background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);border-radius:999px;
|
||||
font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase}
|
||||
.font-picker button{background:transparent;border:none;color:var(--dim);
|
||||
padding:4px 10px;border-radius:999px;cursor:pointer;font-family:inherit;font-size:inherit;
|
||||
letter-spacing:inherit;text-transform:inherit;transition:color .12s}
|
||||
.font-picker button:hover{color:var(--ink)}
|
||||
.font-picker button.active{background:var(--acid);color:var(--bg);font-weight:600}
|
||||
a{color:var(--acid)}
|
||||
.wrap{max-width:1000px;margin:0 auto;padding:0 1.2rem}
|
||||
.topnav{position:sticky;top:0;z-index:20;display:flex;gap:2px;align-items:center;flex-wrap:wrap;
|
||||
padding:10px 1.2rem;background:rgba(5,8,16,.82);backdrop-filter:blur(10px);
|
||||
border-bottom:1px solid var(--line)}
|
||||
.topnav .brand{font-weight:600;color:var(--ink);text-decoration:none;margin-right:14px;font-size:15px;letter-spacing:.2px;display:inline-flex;align-items:center;gap:6px}
|
||||
.topnav .brand .g{color:var(--sunrise)}
|
||||
.topnav a{color:var(--mut);text-decoration:none;padding:6px 12px;border-radius:8px;font-size:14px}
|
||||
.topnav a:hover{color:var(--ink);background:var(--panel)}
|
||||
.topnav .spacer{margin-left:auto}
|
||||
header.hero{text-align:center;padding:4.5rem 1.2rem 1rem}
|
||||
.mark{font-size:56px;line-height:1}
|
||||
h1{font-size:clamp(2.2rem,6vw,3.2rem);margin:.4rem 0 .3rem;line-height:1.05}
|
||||
h1 .g{color:var(--sunrise)}
|
||||
h1 .b{color:var(--chain-native)}
|
||||
.tag{color:var(--mut);font-size:1.05rem;max-width:640px;margin:0 auto;line-height:1.55}
|
||||
.cta{margin-top:1.8rem;display:flex;gap:10px;justify-content:center;flex-wrap:wrap}
|
||||
.btn{display:inline-block;text-decoration:none;padding:11px 20px;border-radius:10px;background:var(--sky-blue);color:#fff;font-size:14px;border:none;cursor:pointer;font-family:inherit;transition:filter .1s}
|
||||
.btn.ghost{background:transparent;border:1px solid var(--line);color:var(--ink)}
|
||||
.btn.acid{background:var(--acid);color:#0b0e14;font-weight:600}
|
||||
.btn:hover{filter:brightness(1.12)}
|
||||
|
||||
/* Search widget */
|
||||
.search{
|
||||
margin:1.6rem auto 0;max-width:820px;background:var(--panel);border:1px solid var(--line);
|
||||
border-radius:16px;padding:14px;box-shadow:0 20px 60px rgba(0,0,0,.35);
|
||||
}
|
||||
.search .row{display:grid;grid-template-columns:1fr 1fr 1fr 1fr auto;gap:8px;align-items:end}
|
||||
.search .field{display:flex;flex-direction:column;gap:4px;text-align:left;padding:8px 12px;
|
||||
background:var(--panel2);border:1px solid transparent;border-radius:10px;
|
||||
transition:border-color .12s}
|
||||
.search .field:focus-within{border-color:var(--sunrise)}
|
||||
.search label{font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--dim)}
|
||||
.search input{background:transparent;border:0;outline:none;color:var(--ink);font-family:inherit;font-size:15px;padding:2px 0;width:100%}
|
||||
.search input::placeholder{color:#4a5567}
|
||||
.search .go{padding:14px 22px;border-radius:12px;background:var(--acid);color:#0b0e14;font-weight:700;font-size:15px;border:0;cursor:pointer;font-family:inherit;align-self:stretch;letter-spacing:.01em}
|
||||
.search .go:hover{filter:brightness(1.06)}
|
||||
.search .kind{display:flex;gap:2px;padding:2px;margin-bottom:10px;background:var(--panel2);border:1px solid var(--line);border-radius:999px;width:fit-content;font-family:var(--mono);font-size:11px;letter-spacing:.12em;text-transform:uppercase}
|
||||
.search .kind button{background:transparent;border:0;color:var(--dim);padding:5px 12px;border-radius:999px;cursor:pointer;font-family:inherit;font-size:inherit;letter-spacing:inherit;text-transform:inherit}
|
||||
.search .kind button.active{background:var(--sunrise);color:var(--bg);font-weight:700}
|
||||
@media (max-width:760px){
|
||||
.search .row{grid-template-columns:1fr 1fr;gap:6px}
|
||||
.search .go{grid-column:1/-1;padding:12px}
|
||||
}
|
||||
|
||||
section{padding:2.6rem 0;border-top:1px solid var(--line);margin-top:2.5rem}
|
||||
section:first-of-type{border-top:none;margin-top:1.5rem}
|
||||
h2{font-size:1.5rem;margin:0 0 .4rem}
|
||||
h3{font-size:1.1rem;margin:0 0 .35rem}
|
||||
.lede{color:var(--mut);margin:0 0 1.6rem;max-width:720px}
|
||||
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px}
|
||||
.card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:18px 20px}
|
||||
.card h3{margin:0 0 .5rem;display:flex;align-items:center;gap:8px}
|
||||
.card p{margin:0;color:var(--mut);font-size:14px;line-height:1.55}
|
||||
.card b{color:var(--ink)}
|
||||
.dot{width:9px;height:9px;border-radius:50%;display:inline-block}
|
||||
.d-chain{background:var(--chain-native)} .d-sky{background:var(--sky-blue)} .d-sun{background:var(--sunrise)}
|
||||
.note{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--sunrise);
|
||||
border-radius:10px;padding:14px 18px;color:var(--mut);font-size:14px;margin-top:1.4rem}
|
||||
.note b{color:var(--ink)}
|
||||
ol.steps{counter-reset:s;list-style:none;padding:0;margin:1.2rem 0 0;display:grid;gap:10px}
|
||||
ol.steps li{counter-increment:s;display:grid;grid-template-columns:30px 1fr;gap:14px;align-items:start;
|
||||
background:var(--panel2);border:1px solid var(--line);border-radius:12px;padding:13px 16px}
|
||||
ol.steps li::before{content:counter(s);grid-column:1;grid-row:1;
|
||||
display:grid;place-items:center;width:24px;height:24px;border-radius:50%;
|
||||
background:rgba(255,184,108,.18);color:var(--sunrise);font-size:12.5px;font-weight:700}
|
||||
ol.steps li > *{grid-column:2;min-width:0}
|
||||
ol.steps b{color:var(--ink)} ol.steps span{color:var(--mut);font-size:14px}
|
||||
.pill{display:inline-block;font-size:11px;padding:2px 8px;border-radius:999px;background:rgba(255,255,255,.07);color:var(--dim);margin-left:8px;vertical-align:middle;font-weight:500}
|
||||
.pill.soon{background:rgba(255,184,108,.16);color:var(--sunrise)}
|
||||
.pill.here{background:rgba(214,255,61,.13);color:var(--acid)}
|
||||
dl.spec{display:grid;grid-template-columns:auto 1fr;gap:8px 22px;margin:0;font-size:13.5px}
|
||||
dl.spec dt{color:var(--dim);white-space:nowrap;font-family:var(--mono)}
|
||||
dl.spec dd{margin:0;color:var(--mut)}
|
||||
dl.spec dd b{color:var(--ink)}
|
||||
footer{border-top:1px solid var(--line);padding:2.4rem 0 3.5rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2.5rem}
|
||||
footer a{color:var(--mut)} footer a:hover{color:var(--ink)}
|
||||
@media (max-width:640px){
|
||||
.topnav{padding:8px 1rem;gap:0}
|
||||
.topnav a{padding:6px 8px;font-size:13px}
|
||||
header.hero{padding:3rem 1rem 1rem}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<nav class="topnav">
|
||||
<a class="brand" href="./">✈️ <span class="g">Fly</span>.X</a>
|
||||
<a href="#how">How it works</a>
|
||||
<a href="#why">Why fly.x</a>
|
||||
<a href="#pantheon">Pantheon</a>
|
||||
<span class="spacer"></span>
|
||||
<a href="#search" class="btn ghost" style="padding:6px 14px;font-size:13px">Search flights</a>
|
||||
</nav>
|
||||
|
||||
<div class="wrap">
|
||||
|
||||
<header class="hero">
|
||||
<div class="mark">✈️</div>
|
||||
<h1>Flights, priced in <span class="b">Bitcoin Cash</span>.</h1>
|
||||
<p class="tag">Find a flight, pay from your wallet. No account to create, no email to hand over — one
|
||||
signature books the seat and the receipt lives on the chain.</p>
|
||||
|
||||
<div class="search" id="search">
|
||||
<div class="kind" role="tablist" aria-label="Trip kind">
|
||||
<button type="button" class="active" data-kind="return">Return</button>
|
||||
<button type="button" data-kind="oneway">One-way</button>
|
||||
<button type="button" data-kind="multi">Multi-city</button>
|
||||
</div>
|
||||
<form class="row" id="searchForm" onsubmit="event.preventDefault(); document.getElementById('waitlist').scrollIntoView({behavior:'smooth'});">
|
||||
<div class="field">
|
||||
<label for="from">From</label>
|
||||
<input id="from" type="text" placeholder="City or airport" autocomplete="off" spellcheck="false">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="to">To</label>
|
||||
<input id="to" type="text" placeholder="Anywhere" autocomplete="off" spellcheck="false">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="depart">Depart</label>
|
||||
<input id="depart" type="date">
|
||||
</div>
|
||||
<div class="field" id="returnField">
|
||||
<label for="return">Return</label>
|
||||
<input id="return" type="date">
|
||||
</div>
|
||||
<button type="submit" class="go">Search →</button>
|
||||
</form>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section id="why">
|
||||
<h2>Why fly.x</h2>
|
||||
<p class="lede">Four things a regular flight search does not do.</p>
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<h3><span class="dot d-chain"></span> Pay from your wallet</h3>
|
||||
<p>Book with Bitcoin Cash. The provider is paid in BCH; the exchange happens under the hood,
|
||||
but <b>you never hand a card number to anyone</b>. Refunds go back to the same wallet.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><span class="dot d-sun"></span> No account, no email</h3>
|
||||
<p>Sign in with your wallet. Your itineraries are yours to keep, encrypted with a passphrase you
|
||||
set in your browser. <b>No fly.x database of who's flying where.</b></p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><span class="dot d-sky"></span> On-chain receipt</h3>
|
||||
<p>Every booking hash is written to Bitcoin Cash as an <code>OP_RETURN</code>. If the airline
|
||||
and fly.x both vanished tomorrow, the seat you paid for is still provably yours.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><span class="dot d-chain"></span> Just a name on the chain</h3>
|
||||
<p><code>fly.x</code> is a BCNR name — an on-chain certificate you can look up without asking
|
||||
DNS or ICANN. Any BCNR resolver reaches this page directly; the gateway is a fallback.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="how">
|
||||
<h2>How it works</h2>
|
||||
<p class="lede">Three steps to book a seat.</p>
|
||||
<ol class="steps">
|
||||
<li><b>Search a route.</b> <span>Type your origin and destination. Fly.x queries a network of
|
||||
travel-provider APIs and returns fares in BCH — priced live at the fill rate we hit at book
|
||||
time, not marked up.</span></li>
|
||||
<li><b>Pick a fare, sign the intent.</b> <span>Your wallet signs a booking intent that names the
|
||||
exact fare and expiry. The intent is broadcast to the fly.x relay; a settlement covenant on
|
||||
BCH atomically pays the provider and mints your receipt NFT to the same address.</span></li>
|
||||
<li><b>Show the receipt.</b> <span>At the airport, present the QR code from the receipt NFT.
|
||||
The airline's fly.x plugin resolves it against the on-chain record. <b>The seat is yours from
|
||||
any wallet that holds the NFT.</b></span></li>
|
||||
</ol>
|
||||
<div class="note"><b>Alpha:</b> route search + booking intent are wired end-to-end on chipnet; the
|
||||
provider network and the settlement covenant are in review. Join the waitlist below and we'll
|
||||
ping you the day mainnet opens.</div>
|
||||
</section>
|
||||
|
||||
<section id="waitlist">
|
||||
<h2>Join the alpha</h2>
|
||||
<p class="lede">One tap. No email needed — your wallet address is the identifier.</p>
|
||||
<div class="cta" style="justify-content:flex-start">
|
||||
<a class="btn acid" href="https://silentmode.st/bns/sirius.x/portal.html">Sign in with wallet →</a>
|
||||
<a class="btn ghost" href="https://silentmode.st/bns/hephaestus.x/">Follow the build</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="pantheon">
|
||||
<h2>The Silent Mode pantheon</h2>
|
||||
<p class="lede">Fly.X is a sibling under Silent Mode's <b>.x</b> umbrella. Each project is its own
|
||||
on-chain certificate; each site is reachable through any BCNR resolver.</p>
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<h3><span class="dot d-chain"></span> ⭐ Sirius.X</h3>
|
||||
<p>Names on Bitcoin Cash — where <code>fly.x</code> was minted. Register a name, edit records,
|
||||
mint a TLD.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><span class="dot d-chain"></span> 🧭 Theseus.X</h3>
|
||||
<p>The browser that speaks BCNR natively. Type <code>fly.x</code> in the address bar; it just
|
||||
resolves.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><span class="dot d-sky"></span> ⚒️ Hephaestus.X</h3>
|
||||
<p>The forge. Fly.X's source and container image live here — open source, self-hostable, no
|
||||
middleman.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3><span class="dot d-sun"></span> ✈️ Fly.X <span class="pill here">you're here</span></h3>
|
||||
<p>Flight search on Bitcoin Cash. This site.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="verify">
|
||||
<h2>Where it lives</h2>
|
||||
<dl class="spec">
|
||||
<dt>this name</dt> <dd><b>fly.x</b> — BCNR certificate on Bitcoin Cash chipnet, owner-controlled</dd>
|
||||
<dt>hosting</dt> <dd>Container image on <a href="https://code.silentmode.st/silentmode/fly">silentmode/fly</a>, deployed to <b>Flux</b> (decentralized compute — decommission-resistant)</dd>
|
||||
<dt>tls</dt> <dd>Silent Mode Argonautica CA — trusted by Theseus/Ariadne users; other browsers can reach fly.x through <a href="https://navigate.st/bns/fly.x">navigate.st</a></dd>
|
||||
<dt>source</dt> <dd><a href="https://code.silentmode.st/silentmode/fly">silentmode/fly</a> — this landing + the Dockerfile</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p>A sibling of <a href="https://silentmode.st/">Silent Mode</a> · alpha on chipnet · one wallet, all of it</p>
|
||||
<p style="margin-top:.6rem;font-size:12px">
|
||||
<a href="https://code.silentmode.st/silentmode/fly" rel="noopener">Source</a> ·
|
||||
<a href="https://silentmode.st/bns/sirius.x/">Get a .x name</a> ·
|
||||
<a href="https://silentmode.st/bns/hephaestus.x/">Hephaestus</a>
|
||||
</p>
|
||||
</footer>
|
||||
|
||||
<script defer src="./js/theme.js"></script>
|
||||
<script>
|
||||
// Trip-kind picker — hide/show the return date depending on selection
|
||||
(function(){
|
||||
const buttons = document.querySelectorAll('.kind button');
|
||||
const returnField = document.getElementById('returnField');
|
||||
buttons.forEach(b => b.addEventListener('click', () => {
|
||||
buttons.forEach(x => x.classList.toggle('active', x === b));
|
||||
returnField.style.opacity = b.dataset.kind === 'return' ? '' : '.35';
|
||||
returnField.style.pointerEvents = b.dataset.kind === 'return' ? '' : 'none';
|
||||
}));
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
63
js/theme.js
Normal file
63
js/theme.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// Sirius.X font picker — reader chooses between Fraunces (Silent Mode
|
||||
// family — editorial serif, default) and Ubuntu (Bitcoin Cash brand
|
||||
// font — humanist sans). Persisted in localStorage under 'sirius-font'.
|
||||
//
|
||||
// The runtime attribute goes on <html> — pages that want to opt out
|
||||
// simply omit the include. The actual font-swap CSS is defined per
|
||||
// page (in each page's <style> block) so it can override the page's
|
||||
// own font-family declarations at high specificity; this script only
|
||||
// toggles the attribute and paints the picker UI.
|
||||
|
||||
(function () {
|
||||
var KEY = "sirius-font";
|
||||
var saved = null;
|
||||
try { saved = localStorage.getItem(KEY); } catch (e) { /* private mode */ }
|
||||
var initial = saved === "ubuntu" ? "ubuntu" : "fraunces";
|
||||
if (initial === "ubuntu") document.documentElement.setAttribute("data-font-cmp", "ubuntu");
|
||||
|
||||
// Inject the picker into the topnav's right side when one exists.
|
||||
// Falls back to a fixed-position pill (bottom-right) for pages without
|
||||
// a topnav, so the choice is always available.
|
||||
function paintPicker() {
|
||||
var nav = document.querySelector(".topnav");
|
||||
var host = nav || document.body;
|
||||
var mount = document.createElement("div");
|
||||
mount.className = "font-picker";
|
||||
mount.setAttribute("role", "group");
|
||||
mount.setAttribute("aria-label", "Reading font");
|
||||
mount.innerHTML =
|
||||
'<button data-font="fraunces" title="Silent Mode family (Fraunces)">Fraunces</button>' +
|
||||
'<button data-font="ubuntu" title="Bitcoin Cash brand font (Ubuntu)">Ubuntu</button>';
|
||||
if (nav) {
|
||||
// Put it just before the sign-in link if present, else append.
|
||||
var portalLink = nav.querySelector("a.portal");
|
||||
if (portalLink) nav.insertBefore(mount, portalLink);
|
||||
else nav.appendChild(mount);
|
||||
} else {
|
||||
mount.classList.add("font-picker-float");
|
||||
host.appendChild(mount);
|
||||
}
|
||||
var btns = mount.querySelectorAll("button");
|
||||
function refresh(v) {
|
||||
for (var i = 0; i < btns.length; i++) {
|
||||
btns[i].classList.toggle("active", btns[i].dataset.font === v);
|
||||
}
|
||||
}
|
||||
refresh(initial);
|
||||
for (var i = 0; i < btns.length; i++) {
|
||||
btns[i].addEventListener("click", function (e) {
|
||||
var v = e.currentTarget.dataset.font;
|
||||
if (v === "ubuntu") document.documentElement.setAttribute("data-font-cmp", "ubuntu");
|
||||
else document.documentElement.removeAttribute("data-font-cmp");
|
||||
try { localStorage.setItem(KEY, v); } catch (e2) { /* ignore */ }
|
||||
refresh(v);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", paintPicker);
|
||||
} else {
|
||||
paintPicker();
|
||||
}
|
||||
})();
|
||||
Loading…
Add table
Reference in a new issue