sirius/storage/index.html
Local Dev 105a245b2f site-sirius-x: only paint .X acid; keep Sirius white
Brand button was wrapping the whole "Sirius.X" label in <span class="g">,
so the entire name rendered acid green. Consistency across the ecosystem
is Sirius (ink) + .X (acid), like Theseus.X and Hephaestus.X. Split the
span across every page (index, tld, portal, admin, brand, docs, market,
storage, theseus/) so only the .X tail carries the acid tint.
2026-09-22 01:28:25 +02:00

576 lines
30 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Storage — Sirius.X (wireframe)</title>
<meta name="description" content="Wireframe for the Sirius.X portal Storage tab. Per-user Sia storage on Hephaestus. Not wired to a backend.">
<link rel="icon" href="../assets/favicon.svg" type="image/svg+xml">
<link rel="mask-icon" href="../assets/favicon.svg" color="#d6ff3d">
<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&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; --forge:#ff8c00;
--ok:#4fd1a5; --taken:#f6768a; --warn:#ffc75f;
--serif:'Fraunces',Georgia,serif;
--sans:'DM Sans','Inter',system-ui,sans-serif;
--mono:'JetBrains Mono',ui-monospace,monospace;
}
*{box-sizing:border-box}
body{margin:0;
background:radial-gradient(ellipse 1200px 600px at 50% -5%,var(--sky-rim) 0%,transparent 60%),
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:15px/1.55 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)}
a{color:var(--acid);text-decoration:none}
/* topnav (matches portal) */
.topnav{position:sticky;top:0;z-index:20;display:flex;gap:2px;align-items:center;flex-wrap:wrap;
padding:10px 1.2rem;background:rgba(11,14,20,.82);backdrop-filter:blur(10px)}
.topnav .brand{font-weight:600;color:var(--ink);text-decoration:none;margin-right:14px;font-size:15px;letter-spacing:.2px}
.topnav .brand .g{color:var(--acid)}
.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 a.here{color:var(--acid);background:rgba(214,255,61,.08)}
.topnav a.portal{margin-left:auto;background:rgba(214,255,61,.10);color:var(--acid);font-weight:500}
/* dashboard shell */
.dash{display:grid;grid-template-columns:236px minmax(0,1fr);gap:26px;max-width:1200px;margin:0 auto;padding:1.4rem 1.2rem 3rem}
@media (min-width:1280px){
.dash{max-width:1720px;grid-template-columns:260px minmax(0,1fr);gap:34px;padding:1.6rem 2rem 3rem}
}
@media (max-width:800px){
.dash{grid-template-columns:1fr;gap:14px}
.side{position:static}
}
.side{position:sticky;top:68px;align-self:start;display:flex;flex-direction:column;gap:4px}
.side .who{padding:12px 14px;border-radius:12px;background:var(--panel);border:1px solid var(--line);margin-bottom:8px}
.side .who .lbl{font-family:var(--mono);font-size:10px;letter-spacing:.2em;text-transform:uppercase;color:var(--dim)}
.side .who .addr{font-family:var(--mono);font-size:12px;color:var(--ink);word-break:break-all;margin-top:4px}
.side .who .net{font-size:11.5px;color:var(--dim);margin-top:4px}
.side .grp{font-family:var(--mono);font-size:10px;letter-spacing:.2em;text-transform:uppercase;color:var(--dim);padding:12px 14px 4px}
.side .sep{height:1px;background:var(--line);margin:8px 6px}
.side button.item,.side a.item{display:flex;align-items:center;gap:10px;width:100%;text-align:left;padding:9px 12px;border-radius:10px;
background:transparent;border:1px solid transparent;color:var(--mut);font:inherit;font-size:14px;cursor:pointer;text-decoration:none;transition:all .12s}
.side .item .ic{width:20px;text-align:center;flex:none;opacity:.9}
.side .item .cnt{margin-left:auto;font-family:var(--mono);font-size:11px;padding:1px 7px;border-radius:999px;background:rgba(255,255,255,.08);color:var(--ink)}
.side .item:hover{color:var(--ink);background:rgba(255,255,255,.04)}
.side .item.active{color:var(--acid);background:rgba(214,255,61,.08);border-color:rgba(214,255,61,.18)}
.side .item.active .cnt{background:rgba(214,255,61,.2);color:var(--acid)}
/* content shared */
.content{min-width:0}
.ph{display:flex;flex-wrap:wrap;gap:14px;justify-content:space-between;align-items:flex-start;margin-bottom:14px}
.ph h2{font-size:1.35rem;margin:0}
.ph p{color:var(--mut);margin:.3rem 0 0;max-width:640px}
.card{background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:20px 22px}
.card + .card{margin-top:14px}
.btn{display:inline-flex;align-items:center;gap:6px;text-decoration:none;padding:9px 16px;border-radius:10px;background:#4b7bec;color:#fff;font-size:14px;border:none;cursor:pointer;font-family:inherit;line-height:1.2}
.btn.ghost{background:transparent;border:1px solid var(--line);color:var(--ink)}
.btn.acid{background:var(--acid);color:#0b0e14;font-weight:600}
.btn.danger{background:rgba(246,118,138,.14);color:var(--taken);border:1px solid rgba(246,118,138,.35)}
.btn.small{padding:6px 11px;font-size:12.5px}
.btn:hover{filter:brightness(1.12)}
.btn:disabled{opacity:.45;cursor:not-allowed;filter:none}
.badge{display:inline-block;font-size:11px;padding:2px 9px;border-radius:999px;font-weight:600;line-height:1.6}
.b-x{background:rgba(214,255,61,.14);color:var(--acid)}
.b-warn{background:rgba(255,199,95,.14);color:var(--warn)}
.b-ok{background:rgba(79,209,165,.14);color:var(--ok)}
.b-off{background:rgba(246,118,138,.14);color:var(--taken)}
.b-dim{background:rgba(255,255,255,.06);color:var(--mut)}
.b-native{background:rgba(10,193,142,.14);color:var(--chain-native)}
.b-forge{background:rgba(255,140,0,.14);color:var(--forge)}
.muted{color:var(--mut);font-size:13px}
.dim{color:var(--dim);font-size:12.5px}
.hidden{display:none !important}
/* ---------- storage layout ---------- */
.quota{display:flex;flex-direction:column;gap:14px}
.quota-hdr{display:flex;align-items:baseline;gap:14px;flex-wrap:wrap}
.quota-hdr .tier-name{font-family:var(--serif);font-size:1.35rem;font-weight:500}
.quota-hdr .quota-line{font-family:var(--mono);color:var(--mut);font-size:14px}
.quota-hdr .quota-line b{color:var(--ink);font-weight:500}
.quota-hdr .renew{font-size:12.5px;color:var(--dim);margin-left:auto}
.bar{width:100%;height:10px;border-radius:999px;background:rgba(255,255,255,.04);overflow:hidden;border:1px solid var(--line)}
.bar > .fill{height:100%;background:linear-gradient(90deg,var(--acid),#a8e600);border-radius:999px;transition:width .3s}
.bar.warn > .fill{background:linear-gradient(90deg,var(--warn),#f59d00)}
.bar.over > .fill{background:linear-gradient(90deg,var(--taken),#e04e4e)}
.quota-detail{display:flex;gap:20px;flex-wrap:wrap;font-size:12.5px;color:var(--dim)}
.quota-detail b{color:var(--mut);font-weight:500;font-family:var(--mono)}
/* file browser */
.browser{display:grid;grid-template-columns:180px minmax(0,1fr);gap:16px;margin-top:14px}
@media (max-width:640px){.browser{grid-template-columns:1fr}}
.folders{display:flex;flex-direction:column;gap:2px;padding:4px 0}
.folders .fh{font-family:var(--mono);font-size:10px;letter-spacing:.2em;text-transform:uppercase;color:var(--dim);padding:6px 10px 4px}
.folders button{display:flex;align-items:center;gap:8px;padding:7px 10px;border-radius:8px;background:transparent;border:1px solid transparent;color:var(--mut);font:inherit;font-size:13.5px;cursor:pointer;text-align:left;width:100%}
.folders button .ic{width:16px;text-align:center;opacity:.85}
.folders button:hover{color:var(--ink);background:rgba(255,255,255,.04)}
.folders button.active{color:var(--acid);background:rgba(214,255,61,.06);border-color:rgba(214,255,61,.15)}
.folders button .cnt{margin-left:auto;font-family:var(--mono);font-size:11px;color:var(--dim)}
.files{display:flex;flex-direction:column;gap:1px;background:var(--panel2);border:1px solid var(--line);border-radius:12px;overflow:hidden}
.files .toolbar{display:flex;gap:8px;padding:10px 14px;border-bottom:1px solid var(--line);align-items:center;background:rgba(255,255,255,.02)}
.files .toolbar .path{font-family:var(--mono);font-size:12.5px;color:var(--mut);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.files .toolbar .path .sep{color:var(--dim);margin:0 4px}
.files .toolbar .path .lock{color:var(--acid);margin-right:6px}
.files .toolbar .path .globe{color:var(--chain-native);margin-right:6px}
.files .row{display:grid;grid-template-columns:1fr 90px 110px 40px;gap:12px;align-items:center;padding:10px 14px;transition:background .1s}
.files .row:hover{background:rgba(255,255,255,.03)}
.files .row + .row{border-top:1px solid rgba(255,255,255,.03)}
.files .row.head{color:var(--dim);font-family:var(--mono);font-size:10.5px;letter-spacing:.15em;text-transform:uppercase;padding:8px 14px}
.files .row.head:hover{background:transparent}
.files .row .name{display:flex;gap:8px;align-items:center;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.files .row .name .ic{width:18px;text-align:center;flex:none}
.files .row .size{font-family:var(--mono);font-size:12px;color:var(--mut);text-align:right}
.files .row .date{font-family:var(--mono);font-size:12px;color:var(--dim)}
.files .row .more{color:var(--dim);cursor:pointer;text-align:right;padding:0 4px;user-select:none}
.files .row .more:hover{color:var(--ink)}
.drop{margin:12px 14px 14px;border:1.5px dashed rgba(255,255,255,.12);border-radius:10px;padding:22px;text-align:center;color:var(--mut);font-size:13.5px;transition:all .15s}
.drop.active{border-color:var(--acid);background:rgba(214,255,61,.03);color:var(--ink)}
.drop b{color:var(--acid);font-weight:500}
.drop .k{font-family:var(--mono);color:var(--dim);font-size:12px;display:block;margin-top:4px}
/* upgrade modal */
.modal-scrim{position:fixed;inset:0;z-index:100;background:rgba(4,6,11,.72);backdrop-filter:blur(6px);display:none;align-items:center;justify-content:center;padding:20px}
.modal-scrim.show{display:flex}
.modal{background:var(--panel);border:1px solid var(--line);border-radius:16px;max-width:640px;width:100%;max-height:calc(100vh - 40px);overflow:auto;padding:22px 26px}
.modal h2{margin:0 0 6px;font-size:1.4rem}
.modal .close{float:right;background:none;border:0;color:var(--dim);font-size:22px;cursor:pointer;line-height:1;padding:0 4px;margin:-4px -8px 0 0}
.modal .close:hover{color:var(--ink)}
.plans{display:flex;flex-direction:column;gap:10px;margin:14px 0 18px}
.plan{display:flex;align-items:center;gap:14px;padding:14px 16px;border-radius:12px;border:1px solid var(--line);cursor:pointer;transition:all .12s;background:var(--panel2)}
.plan:hover{border-color:rgba(214,255,61,.28)}
.plan.sel{border-color:var(--acid);background:rgba(214,255,61,.05)}
.plan .radio{width:16px;height:16px;border-radius:50%;border:1.5px solid var(--dim);flex:none;position:relative}
.plan.sel .radio{border-color:var(--acid)}
.plan.sel .radio::after{content:'';position:absolute;inset:3px;background:var(--acid);border-radius:50%}
.plan .nm{flex:1;font-family:var(--serif);font-size:1.05rem;font-weight:500}
.plan .size{font-family:var(--mono);font-size:12.5px;color:var(--mut);width:130px}
.plan .price{font-family:var(--mono);font-size:14px;color:var(--ink);font-weight:500;width:80px;text-align:right}
.plan.current{opacity:.6;cursor:default}
.plan.current .nm::after{content:' — current';color:var(--dim);font-family:var(--sans);font-size:12px;font-weight:400}
.plan .save{font-family:var(--mono);font-size:10.5px;color:var(--acid);letter-spacing:.1em;margin-left:8px}
.pay-box{padding:18px 20px;border-radius:12px;background:linear-gradient(135deg,rgba(214,255,61,.04),rgba(10,193,142,.02));border:1px solid rgba(214,255,61,.16)}
.pay-box .amt{font-family:var(--serif);font-size:2rem;color:var(--acid);font-weight:400;letter-spacing:-.02em}
.pay-box .amt sub{font-family:var(--mono);font-size:12px;color:var(--mut);letter-spacing:0;vertical-align:baseline;margin-left:6px}
.pay-box .oracle{font-family:var(--mono);font-size:11.5px;color:var(--dim);margin-top:4px;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.pay-box .dot{width:6px;height:6px;border-radius:50%;background:var(--ok);animation:pulse 2s infinite ease-in-out}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.35}}
.modal .actions{display:flex;gap:10px;margin-top:16px;flex-wrap:wrap;align-items:center}
.modal .actions .btn{flex:1;justify-content:center;padding:12px 18px;font-size:14.5px}
.modal .actions .btn.acid{flex:2}
.modal .alt{font-size:12px;color:var(--dim);margin-top:12px;text-align:center}
.modal .alt a{color:var(--mut)}
/* confirmation banner */
.banner{padding:14px 18px;border-radius:12px;background:rgba(79,209,165,.06);border:1px solid rgba(79,209,165,.24);color:var(--ink);margin-bottom:14px;display:flex;gap:12px;align-items:center}
.banner.warn{background:rgba(255,199,95,.06);border-color:rgba(255,199,95,.24)}
.banner.err{background:rgba(246,118,138,.06);border-color:rgba(246,118,138,.24)}
.banner .ic{font-size:20px;flex:none}
.banner .txt{flex:1;font-size:13.5px;color:var(--mut)}
.banner .txt b{color:var(--ink);font-weight:500}
.banner .close{background:none;border:0;color:var(--dim);font-size:18px;cursor:pointer;padding:0 4px}
.banner .close:hover{color:var(--ink)}
/* wireframe state picker (dev-only) */
.wf-picker{position:fixed;bottom:16px;right:16px;z-index:50;padding:8px 10px;border-radius:12px;background:rgba(20,26,36,.94);border:1px solid var(--line);font-family:var(--mono);font-size:11px;color:var(--dim);display:flex;flex-direction:column;gap:4px;backdrop-filter:blur(10px)}
.wf-picker .h{color:var(--acid);text-transform:uppercase;letter-spacing:.15em;font-size:9.5px;margin-bottom:2px}
.wf-picker button{background:transparent;border:0;color:var(--mut);cursor:pointer;font-family:inherit;font-size:11.5px;padding:3px 6px;border-radius:6px;text-align:left}
.wf-picker button:hover{color:var(--ink);background:rgba(255,255,255,.04)}
.wf-picker button.active{color:var(--acid);background:rgba(214,255,61,.08)}
</style>
</head>
<body>
<nav class="topnav">
<a class="brand" href="../"><svg width="18" height="18" viewBox="0 0 64 64" style="vertical-align:-4px;margin-right:6px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 4 L36.4 27.6 L60 32 L36.4 36.4 L32 60 L27.6 36.4 L4 32 L27.6 27.6 Z" fill="#d6ff3d"/></svg>Sirius<span class="g">.X</span></a>
<a href="../tld.html">🌐 TLD</a>
<a href="../market/">🛒 Market</a>
<a href="../portal.html" class="portal here">🔑 Storage</a>
</nav>
<div class="dash">
<!-- ============ sidebar ============ -->
<aside class="side" aria-label="Dashboard menu">
<div class="who">
<div class="lbl">Signed in</div>
<div class="addr">bitcoincash:qzrp…4qtx</div>
<div class="net">chipnet · 42,180 sat</div>
</div>
<button class="item"><span class="ic">🎨</span>Web-Builder</button>
<button class="item"><span class="ic">🌐</span>Domain names<span class="cnt">3</span></button>
<button class="item"><span class="ic">🏷</span>My TLDs<span class="cnt">1</span></button>
<button class="item active"><span class="ic">📦</span>Storage<span class="cnt" id="side-quota">120M</span></button>
<div class="sep"></div>
<button class="item"><span class="ic"></span>Settings</button>
<button class="item"><span class="ic">🚪</span>Sign out</button>
</aside>
<!-- ============ content ============ -->
<main class="content">
<!-- Confirmation banners appear here based on wireframe state -->
<div id="banner-confirming" class="banner warn hidden">
<div class="ic"></div>
<div class="txt"><b>Confirming payment.</b> 0.0462 BCH received — waiting for 1 confirmation (~10 min). Your plan activates automatically.</div>
<button class="close" onclick="dismissBanner(this)">×</button>
</div>
<div id="banner-upgraded" class="banner hidden">
<div class="ic"></div>
<div class="txt"><b>Silent Max is active.</b> Quota bumped to 50 GB private. Renews 2026-10-20.</div>
<button class="close" onclick="dismissBanner(this)">×</button>
</div>
<div id="banner-grace" class="banner err hidden">
<div class="ic"></div>
<div class="txt"><b>Over quota.</b> Files are read-only for 27 more days. Upgrade or delete files to restore write access.</div>
<button class="close" onclick="dismissBanner(this)">×</button>
</div>
<div class="ph">
<div>
<h2>Storage</h2>
<p id="hdr-sub">Files you host on Silent Mode's Sia backend. Public files get a URL anyone can open;
private files are encrypted in your browser with a key only your wallet can derive.</p>
</div>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<button class="btn ghost small">↻ Refresh</button>
<button class="btn acid small" id="upgrade-btn" onclick="openModal()">Upgrade</button>
</div>
</div>
<!-- Quota card -->
<section class="card">
<div class="quota">
<div class="quota-hdr">
<span class="tier-name" id="tier-name">Silent Free</span>
<span class="badge b-dim" id="tier-badge">Free</span>
<span class="quota-line"><b id="q-used">120 MB</b> / <span id="q-cap">500 MB</span> private</span>
<span class="renew" id="renew-line" style="display:none">renews <b id="renew-date">2026-10-20</b></span>
</div>
<div class="bar" id="q-bar"><div class="fill" id="q-fill" style="width:24%"></div></div>
<div class="quota-detail">
<span>Public: <b>2.4 GB</b> · unlimited</span>
<span>Files: <b>47</b></span>
<span>Encryption: <b>on</b> for private</span>
<span id="credit-line" style="display:none">Credit: <b>$0.00</b></span>
</div>
</div>
</section>
<!-- File browser -->
<section class="card" style="padding:14px 14px 0">
<div class="browser">
<nav class="folders">
<div class="fh">Folders</div>
<button class="active" data-folder="private">
<span class="ic" style="color:var(--acid)">🔒</span>Private
<span class="cnt">12</span>
</button>
<button data-folder="public">
<span class="ic" style="color:var(--chain-native)">🌐</span>Public
<span class="cnt">35</span>
</button>
<button data-folder="shared">
<span class="ic">🔗</span>Shared
<span class="cnt">2</span>
</button>
<div class="fh" style="margin-top:8px">Actions</div>
<button><span class="ic"></span>New folder</button>
<button><span class="ic"></span>Upload</button>
</nav>
<div class="files">
<div class="toolbar">
<div class="path">
<span class="lock">🔒</span>
<span>private</span>
<span class="sep">/</span>
<span>projects</span>
</div>
<button class="btn ghost small">Share</button>
</div>
<div class="row head">
<span>Name</span>
<span class="size" style="text-align:right">Size</span>
<span class="date">Modified</span>
<span></span>
</div>
<div class="row">
<span class="name"><span class="ic">📁</span>silentmode-notes</span>
<span class="size"></span>
<span class="date">3d ago</span>
<span class="more"></span>
</div>
<div class="row">
<span class="name"><span class="ic">📁</span>contracts</span>
<span class="size"></span>
<span class="date">1w ago</span>
<span class="more"></span>
</div>
<div class="row">
<span class="name"><span class="ic">📄</span>hestia-outline.md</span>
<span class="size">14 KB</span>
<span class="date">just now</span>
<span class="more"></span>
</div>
<div class="row">
<span class="name"><span class="ic">📄</span>oracle-notes.txt</span>
<span class="size">4 KB</span>
<span class="date">2h ago</span>
<span class="more"></span>
</div>
<div class="row">
<span class="name"><span class="ic">🗄</span>backup-2026-09-18.tar.gz</span>
<span class="size">86 MB</span>
<span class="date">2d ago</span>
<span class="more"></span>
</div>
<div class="row">
<span class="name"><span class="ic">🖼</span>diagram.png</span>
<span class="size">220 KB</span>
<span class="date">4d ago</span>
<span class="more"></span>
</div>
<div class="row">
<span class="name"><span class="ic">🔑</span>keys.json.gpg</span>
<span class="size">2 KB</span>
<span class="date">2w ago</span>
<span class="more"></span>
</div>
<div class="drop" id="drop">
Drag files here, or <b>click to browse</b>
<span class="k">Encrypted in your browser before upload · max 100 MB per file on Free</span>
</div>
</div>
</div>
</section>
<!-- CLI hint -->
<section class="card" style="margin-top:14px">
<div style="display:flex;gap:10px;align-items:baseline;justify-content:space-between;flex-wrap:wrap">
<div>
<h3 style="margin:0 0 4px">CLI access</h3>
<p class="muted" style="margin:0">Upload from a script or CI with your Silent Mode PAT.
Encryption still happens client-side.</p>
</div>
<span class="badge b-dim" id="cli-tier">Free — via gateway</span>
</div>
<pre style="margin:12px 0 0;padding:12px 14px;background:var(--panel2);border:1px solid var(--line);border-radius:10px;font-family:var(--mono);font-size:12.5px;color:var(--mut);overflow:auto;line-height:1.6"><span style="color:var(--dim)"># upload a private file</span>
curl -H "Authorization: token $HESTIA_PAT" \
--data-binary @notes.md \
https://storage.silentmode.st/u/48291/private/notes.md
<span style="color:var(--dim)"># list your files</span>
curl -H "Authorization: token $HESTIA_PAT" \
https://storage.silentmode.st/u/48291/list</pre>
<p class="muted" id="cli-hint" style="margin:10px 0 0;font-size:12.5px">
<span class="badge b-warn">Pro / Max</span> get their own S3 credentials for direct
<code>rclone</code> / <code>aws s3</code> use.
</p>
</section>
</main>
</div>
<!-- ============ upgrade modal ============ -->
<div class="modal-scrim" id="modal">
<div class="modal">
<button class="close" onclick="closeModal()">×</button>
<h2>Plans</h2>
<p class="muted">Priced in USD, paid in BCH at the live oracle rate. All plans include unlimited public storage. Encryption is client-side, wallet-derived, always on for private files.</p>
<div class="plans" id="plans">
<div class="plan current" data-tier="free">
<span class="radio"></span>
<span class="nm">Silent Free</span>
<span class="size">500 MB private</span>
<span class="price">$0</span>
</div>
<div class="plan" data-tier="pro" onclick="selectPlan('pro')">
<span class="radio"></span>
<span class="nm">Silent Pro</span>
<span class="size">2 GB private</span>
<span class="price">$3<span class="save">·25% off GH</span></span>
</div>
<div class="plan sel" data-tier="max" onclick="selectPlan('max')">
<span class="radio"></span>
<span class="nm">Silent Max</span>
<span class="size">50 GB private</span>
<span class="price">$16<span class="save">·24% off GH</span></span>
</div>
</div>
<div class="pay-box">
<div style="display:flex;justify-content:space-between;align-items:baseline">
<div>
<div class="dim" style="font-family:var(--mono);letter-spacing:.1em;text-transform:uppercase;font-size:10px">Pay for 1 month</div>
<div class="amt" id="pay-amt">0.0462 BCH<sub>≈ $16.00</sub></div>
</div>
<span class="badge b-native" id="pay-tier">Silent Max</span>
</div>
<div class="oracle">
<span class="dot"></span>
<span>live oracle at <b style="color:var(--mut)" id="oracle-rate">$346.40</b> / BCH</span>
<span>·</span>
<span id="oracle-age">quoted 00:14 ago</span>
<span>·</span>
<span>refreshes in <span id="oracle-refresh">04:46</span></span>
</div>
</div>
<div class="actions">
<button class="btn ghost" onclick="closeModal()">Cancel</button>
<button class="btn acid" onclick="mockPay()">Pay from wallet →</button>
</div>
<div class="alt">
also accepts: <a href="#">QR code</a> · <a href="#">external wallet</a> · <a href="#">on-chain address</a>
</div>
</div>
</div>
<!-- ============ wireframe state picker (dev only) ============ -->
<div class="wf-picker">
<div class="h">Wireframe state</div>
<button class="active" onclick="setState('free')">Free · default</button>
<button onclick="setState('confirming')">Free · confirming</button>
<button onclick="setState('max-fresh')">Max · just upgraded</button>
<button onclick="setState('max-half')">Max · half full</button>
<button onclick="setState('grace')">Free · over quota</button>
</div>
<script>
const PLAN = {
free: {name:'Silent Free', badge:'Free', badgeCls:'b-dim', used:120, cap:500, capUnit:'MB', price:0, bch:0},
pro: {name:'Silent Pro', badge:'Pro', badgeCls:'b-forge', used:1200, cap:2, capUnit:'GB', price:3, bch:0.00866},
max: {name:'Silent Max', badge:'Max', badgeCls:'b-native',used:24800, cap:50, capUnit:'GB', price:16, bch:0.04620},
};
let currentTier = 'free';
let selectedTier = 'max';
function selectPlan(t){
selectedTier = t;
document.querySelectorAll('.plan').forEach(p=>p.classList.toggle('sel', p.dataset.tier === t));
const p = PLAN[t];
document.getElementById('pay-amt').innerHTML = p.bch.toFixed(5) + ' BCH<sub>≈ $' + p.price.toFixed(2) + '</sub>';
document.getElementById('pay-tier').className = 'badge ' + p.badgeCls;
document.getElementById('pay-tier').textContent = p.name;
}
function openModal(){ document.getElementById('modal').classList.add('show'); }
function closeModal(){ document.getElementById('modal').classList.remove('show'); }
function dismissBanner(el){ el.parentElement.classList.add('hidden'); }
function mockPay(){
closeModal();
setState('confirming');
// fake the SSE flow: confirming → upgraded after 3s
setTimeout(() => setState('max-fresh'), 3000);
}
function fmtBytes(x, unit){
if (unit === 'MB') return x < 1024 ? x + ' MB' : (x/1024).toFixed(1) + ' GB';
if (unit === 'GB') return x < 1 ? Math.round(x*1024) + ' MB' : x + ' GB';
return x + ' ' + unit;
}
function setState(s){
// reset banners
['confirming','upgraded','grace'].forEach(k =>
document.getElementById('banner-' + k).classList.add('hidden'));
// reset state-picker button
document.querySelectorAll('.wf-picker button').forEach(b => b.classList.remove('active'));
(document.querySelector(`.wf-picker button[onclick*="${s}"]`) || {classList:{add:()=>{}}}).classList.add('active');
let tier='free', usedMB=120, capMB=500, capLabel='500 MB', tierLabel='Silent Free',
badgeCls='b-dim', badgeLbl='Free', renew=false, credit=false, cliBadge='Free — via gateway';
if (s === 'confirming') {
document.getElementById('banner-confirming').classList.remove('hidden');
}
if (s === 'max-fresh') {
tier='max'; usedMB=120; capMB=50*1024; capLabel='50 GB'; tierLabel='Silent Max';
badgeCls='b-native'; badgeLbl='Max'; renew=true;
cliBadge='Max — direct S3';
document.getElementById('banner-upgraded').classList.remove('hidden');
}
if (s === 'max-half') {
tier='max'; usedMB=25*1024; capMB=50*1024; capLabel='50 GB'; tierLabel='Silent Max';
badgeCls='b-native'; badgeLbl='Max'; renew=true;
cliBadge='Max — direct S3';
}
if (s === 'grace') {
tier='free'; usedMB=580; capMB=500; capLabel='500 MB'; tierLabel='Silent Free';
badgeCls='b-dim'; badgeLbl='Free (over quota)'; credit=true;
document.getElementById('banner-grace').classList.remove('hidden');
}
currentTier = tier;
document.getElementById('tier-name').textContent = tierLabel;
document.getElementById('tier-badge').className = 'badge ' + badgeCls;
document.getElementById('tier-badge').textContent = badgeLbl;
const pct = Math.min(100, Math.round((usedMB / capMB) * 100));
document.getElementById('q-used').textContent = usedMB < 1024 ? usedMB + ' MB' : (usedMB/1024).toFixed(1) + ' GB';
document.getElementById('q-cap').textContent = capLabel;
document.getElementById('q-fill').style.width = pct + '%';
document.getElementById('q-bar').className = 'bar' + (pct >= 100 ? ' over' : pct >= 85 ? ' warn' : '');
document.getElementById('side-quota').textContent = usedMB < 1024 ? usedMB + 'M' : (usedMB/1024).toFixed(1) + 'G';
document.getElementById('renew-line').style.display = renew ? '' : 'none';
document.getElementById('credit-line').style.display = credit ? '' : 'none';
document.getElementById('cli-tier').textContent = cliBadge;
document.getElementById('cli-tier').className = 'badge ' + (tier === 'free' ? 'b-dim' : tier === 'pro' ? 'b-forge' : 'b-native');
// update available plans in modal (highlight current, remove .current from others)
document.querySelectorAll('.plan').forEach(p => {
p.classList.toggle('current', p.dataset.tier === tier);
if (p.dataset.tier === tier) p.classList.remove('sel');
});
if (tier === 'free') selectPlan('max');
else if (tier === 'pro') selectPlan('max');
else selectPlan('max');
}
// fake oracle refresh countdown (visual only)
let refreshLeft = 286;
setInterval(() => {
refreshLeft = refreshLeft > 0 ? refreshLeft - 1 : 300;
const m = Math.floor(refreshLeft / 60);
const s = refreshLeft % 60;
const el = document.getElementById('oracle-refresh');
if (el) el.textContent = String(m).padStart(2,'0') + ':' + String(s).padStart(2,'0');
}, 1000);
setState('free');
</script>
</body>
</html>