use crate::land::FLAG_SVG;
use crate::nav::CyberiaNav;
use crate::orders::support_href;
use leptos::prelude::*;
use leptos_router::hooks::use_params_map;
use serde::Deserialize;
const LIBRARY_JSON: &str = include_str!("docs_library.json");
pub const GATEWAY: &str = "https://gateway.pinata.cloud/ipfs/";
#[derive(Clone, Debug, Deserialize)]
pub struct Section {
#[serde(default)]
pub key: String,
pub section: String,
#[serde(default)]
pub line: String,
#[serde(default)]
pub color: String,
pub items: Vec<Item>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct Item {
pub title: String,
#[serde(default)]
pub what: String,
#[serde(default)]
pub url: String,
#[serde(default)]
pub cid: String,
#[serde(default)]
pub thumb: String,
#[serde(default)]
pub cover: String,
#[serde(default)]
pub pages: u32,
#[serde(default)]
pub kb: u32,
#[serde(default)]
pub lang: String,
#[serde(default)]
pub note: String,
#[serde(default)]
pub key: String,
#[serde(default)]
pub title_en: String,
#[serde(default)]
pub langs: std::collections::BTreeMap<String, LangFile>,
}
#[derive(Clone, Debug, Default, Deserialize)]
pub struct LangFile {
#[serde(default)]
pub cid: String,
#[serde(default)]
pub pages: u32,
#[serde(default)]
pub kb: u32,
}
impl Item {
pub fn href(&self) -> Option<String> {
if !self.cid.is_empty() {
Some(format!("{GATEWAY}{}", self.cid))
} else if !self.url.is_empty() {
Some(self.url.clone())
} else {
None
}
}
pub fn meta(&self) -> String {
if !self.cid.is_empty() {
format!("ipfs · {}…", &self.cid[..self.cid.len().min(12)])
} else if !self.url.is_empty() {
self.url.trim_start_matches("https://").trim_end_matches('/').to_string()
} else if !self.note.is_empty() {
self.note.clone()
} else {
"arrives before signing".to_string()
}
}
fn facts(&self) -> String {
let mut f = Vec::new();
if self.pages > 0 {
f.push(format!("{} {}", self.pages, if self.pages == 1 { "page" } else { "pages" }));
}
if self.kb > 0 {
f.push(if self.kb >= 1024 { format!("{:.1} MB", self.kb as f64 / 1024.0) } else { format!("{} KB", self.kb) });
}
if !self.lang.is_empty() {
f.push(self.lang.clone());
}
f.join(" · ")
}
fn hash_chip(&self) -> String {
let c = &self.cid;
if c.len() > 14 {
format!("{}…{}", &c[..8], &c[c.len() - 6..])
} else {
c.clone()
}
}
}
pub fn library() -> &'static [Section] {
static LIB: std::sync::OnceLock<Vec<Section>> = std::sync::OnceLock::new();
LIB.get_or_init(|| serde_json::from_str(LIBRARY_JSON).unwrap_or_default())
}
fn dossier(s: &Section, _support: &str) -> impl IntoView {
let color = if s.color.is_empty() { "#ffffff".to_string() } else { s.color.clone() };
let pinned = s.items.iter().filter(|i| !i.cid.is_empty()).count();
let pending = s.items.len() - pinned;
let id = s.key.clone();
view! {
<section class="dossier" id=id style:--ink=color.clone()>
<div class="dossier-head">
<div>
<div class="dossier-kicker">{s.section.to_uppercase()}</div>
<div class="dossier-line">{s.line.clone()}</div>
</div>
<div class="dossier-count">
{(pinned > 0).then(|| view! { <span>{format!("{pinned} on IPFS")}</span> })}
{(pending > 0).then(|| view! { <span class="dossier-pending">{format!("{pending} before signing")}</span> })}
</div>
</div>
<div class="cover-grid">
{s.items.iter().map(|it| {
let ready = !it.cid.is_empty() && !it.key.is_empty();
let href = if ready { format!("/docs/{}", it.key) } else { it.href().unwrap_or_else(|| "/docs".into()) };
let cover = (!it.cover.is_empty() && ready).then(|| format!("{}?v={}", it.cover, &it.cid[it.cid.len().saturating_sub(8)..]));
view! {
<a class=if ready { "cover-card" } else { "cover-card is-pending" } href=href>
<div class="cover">
{match cover {
Some(t) => view! { <img src=t loading="lazy" alt=it.title_en.clone() /> }.into_any(),
None => view! { <div class="cover-blank"></div> }.into_any(),
}}
{(!ready).then(|| view! { <span class="cover-open">"BEFORE SIGNING"</span> })}
</div>
<div class="cover-title">{it.title_en.clone()}</div>
<div class="cover-title-id">{it.title.clone()}</div>
</a>
}
}).collect_view()}
</div>
</section>
}
}
pub const LANGS: &[(&str, &str)] = &[
("en", "English"), ("ru", "Русский"), ("zh", "中文"), ("de", "Deutsch"), ("fr", "Français"), ("es", "Español"),
("ja", "日本語"), ("ko", "한국어"), ("ar", "العربية"), ("pt", "Português"), ("it", "Italiano"), ("tr", "Türkçe"),
("uk", "Українська"), ("hi", "हिन्दी"),
];
fn find(key: &str) -> Option<(&'static Section, &'static Item)> {
library().iter().find_map(|s| s.items.iter().find(|i| i.key == key).map(|i| (s, i)))
}
#[component]
pub fn DocPage() -> impl IntoView {
let params = use_params_map();
let key = params.get_untracked().get("key").unwrap_or_default();
let support = support_href();
let Some((s, it)) = find(&key) else {
document().set_title("Cyberia — no such document");
return view! {
<div class="page-shell" style="padding:40px;">
<h1 style="color: var(--cyber-red);">"404"</h1>
<p style="color:#777777; margin-top:12px;">{format!("no document called {key} in the library")}</p>
<a href="/docs" style="color: var(--cyber-green);">"← back to the library"</a>
</div>
}
.into_any();
};
document().set_title(&format!("Cyberia — {}", it.title_en));
let color = if s.color.is_empty() { "#ffffff".to_string() } else { s.color.clone() };
let cover = (!it.cover.is_empty()).then(|| format!("{}?v={}", it.cover, &it.cid[it.cid.len().saturating_sub(8)..]));
let title_id = if it.title == it.title_en { String::new() } else { it.title.clone() };
let signable = s.key == "sign";
let id_href = format!("{GATEWAY}{}", it.cid);
let back = format!("/docs#{}", s.key);
let gen_title = if it.title_en.is_empty() { it.title.clone() } else { it.title_en.clone() };
view! {
<div class="page-shell cities-shell plot-shell">
<div class="site-chrome cyberia-chrome">
<div class="chrome-inner">
<div class="header-row1">
<div class="logo-zone">
<h1 class="logo">
<a href="/cities" class="brand-flag" title="home" inner_html=FLAG_SVG></a>
<span style="color: var(--cyber-green);">"cyber"</span>
<span style="color: var(--cyber-green); margin: 0 1px;">"•"</span>
<span style="color: #fff;">"ia"</span>
</h1>
</div>
<CyberiaNav active="cabinet" />
</div>
</div>
</div>
<div class="cities-stage plot-stage-wrap">
<div class="doc-page" style:--ink=color>
<div class="doc-cover">
{match cover {
Some(t) => view! { <img src=t alt=it.title_en.clone() /> }.into_any(),
None => view! { <div class="cover-blank"></div> }.into_any(),
}}
</div>
<div class="doc-side">
<a class="doc-back" href=back>{format!("← {}", s.section.split(" ·").next().unwrap_or("library"))}</a>
<h2 class="doc-title">{it.title_en.clone()}</h2>
{(!title_id.is_empty()).then(|| view! { <div class="doc-title-id">{title_id.clone()}</div> })}
<p class="doc-what">{it.what.clone()}</p>
{if signable {
view! {
<div class="doc-open">
<div class="doc-open-k">"OPEN IN"</div>
<a class="doc-lang is-main" href=id_href.clone() target="_blank" rel="noopener">
<span class="doc-lang-name">"Bahasa Indonesia"</span>
<span class="doc-lang-note">"the text that binds"</span>
<span class="doc-lang-act">"OPEN"</span>
</a>
<div class="doc-open-k">"BESIDE A SECOND LANGUAGE"</div>
{LANGS.iter().map(|(code, name)| {
match it.langs.get(*code) {
Some(f) if !f.cid.is_empty() => view! {
<a class="doc-lang is-ready" href=format!("{GATEWAY}{}", f.cid) target="_blank" rel="noopener">
<span class="doc-lang-name">{*name}</span>
<span class="doc-lang-note">{format!("{} pages", f.pages)}</span>
<span class="doc-lang-act">"OPEN"</span>
</a>
}.into_any(),
_ => {
let text = format!("GENERATE {gen_title} — Bahasa Indonesia + {name}");
let href = format!("{}?text={}", support, String::from(js_sys::encode_uri_component(&text)));
view! {
<a class="doc-lang is-ask" href=href target="_blank" rel="noopener">
<span class="doc-lang-name">{*name}</span>
<span class="doc-lang-note">"made on request"</span>
<span class="doc-lang-act">"ASK"</span>
</a>
}.into_any()
}
}
}).collect_view()}
<a class="doc-lang is-ask" href=format!("{}?text={}", support, String::from(js_sys::encode_uri_component(&format!("GENERATE {gen_title} — Bahasa Indonesia + …")))) target="_blank" rel="noopener">
<span class="doc-lang-name">"another language"</span>
<span class="doc-lang-note">"made on request"</span>
<span class="doc-lang-act">"ASK"</span>
</a>
</div>
}.into_any()
} else {
view! {
<div class="doc-open">
<a class="doc-lang is-main" href=id_href.clone() target="_blank" rel="noopener">
<span class="doc-lang-name">{if it.lang.is_empty() { "the copy".to_string() } else { format!("the copy · {}", it.lang) }}</span>
<span class="doc-lang-note">{format!("{} pages", it.pages)}</span>
<span class="doc-lang-act">"OPEN"</span>
</a>
</div>
}.into_any()
}}
<div class="doc-facts">
<span>{it.facts()}</span>
<a class="doc-qr" href=id_href.clone() target="_blank" rel="noopener" title=format!("ipfs · {} · the content hash, the file's name on IPFS, the same on every gateway", it.cid) inner_html=qr_svg(&id_href)></a>
</div>
</div>
</div>
</div>
</div>
}
.into_any()
}
fn qr_svg(url: &str) -> String {
use qrcode::render::svg;
match qrcode::QrCode::new(url.as_bytes()) {
Ok(code) => code
.render::<svg::Color>()
.min_dimensions(96, 96)
.quiet_zone(false)
.dark_color(svg::Color("#fff"))
.light_color(svg::Color("transparent"))
.build(),
Err(_) => String::new(),
}
}
fn chips(s: &Section) -> impl IntoView {
let color = if s.color.is_empty() { "#ffffff".to_string() } else { s.color.clone() };
let id = s.key.clone();
view! {
<section class="dossier chips-section" id=id style:--ink=color>
<div class="dossier-head">
<div>
<div class="dossier-kicker">{s.section.to_uppercase()}</div>
<div class="dossier-line">{s.line.clone()}</div>
</div>
</div>
<div class="chips">
{s.items.iter().map(|it| {
let href = it.href().unwrap_or_default();
view! {
<a class="chip" href=href target="_blank" rel="noopener">
<span class="chip-title">{it.title_en.clone()}</span>
<span class="chip-what">{it.what.clone()}</span>
</a>
}
}).collect_view()}
</div>
</section>
}
}
#[component]
pub fn DocsPage() -> impl IntoView {
Effect::new(move |_| {
document().set_title("Cyberia — leasehold documents");
});
let support = support_href();
let lib = library();
let files: Vec<&Item> = lib.iter().filter(|s| s.key != "rules" && s.key != "index").flat_map(|s| s.items.iter()).collect();
let on_ipfs = files.iter().filter(|i| !i.cid.is_empty()).count();
let pending = files.len() - on_ipfs;
let signable = lib.iter().find(|s| s.key == "sign").map(|s| s.items.len()).unwrap_or(0);
let pages: u32 = files.iter().map(|i| i.pages).sum();
view! {
<div class="page-shell cities-shell plot-shell">
<div class="site-chrome cyberia-chrome">
<div class="chrome-inner">
<div class="header-row1">
<div class="logo-zone">
<h1 class="logo">
<a href="/cities" class="brand-flag" title="home" inner_html=FLAG_SVG></a>
<span style="color: var(--cyber-green);">"cyber"</span>
<span style="color: var(--cyber-green); margin: 0 1px;">"•"</span>
<span style="color: #fff;">"ia"</span>
</h1>
</div>
<CyberiaNav active="cabinet" />
</div>
</div>
</div>
<div class="cities-stage plot-stage-wrap docs-stage">
<div class="cities-hero plot-hero">
<div>
<div class="cities-kicker">"LEASEHOLD · DOCUMENTS"</div>
<h2 class="cities-title">"Everything you sign and receive"</h2>
<p class="cities-lead">
"The instruments on the Company's letterhead, the land's papers, the Company's registry — each file by its content hash, nothing on our servers."
</p>
</div>
<div class="docs-counters">
<div class="counter"><b>{signable}</b><span>"to sign"</span></div>
<div class="counter"><b>{on_ipfs}</b><span>"on IPFS"</span></div>
<div class="counter"><b>{pages}</b><span>"pages"</span></div>
{(pending > 0).then(|| view! { <div class="counter is-dim"><b>{pending}</b><span>"before signing"</span></div> })}
</div>
</div>
<nav class="docs-jump">
{lib.iter().map(|s| {
let href = format!("#{}", s.key);
let ink = s.color.clone();
view! { <a href=href style:--ink=ink>{s.section.split(" —").next().unwrap_or("").split(" ·").next().unwrap_or("").to_uppercase()}</a> }
}).collect_view()}
</nav>
{lib.iter().map(|s| if s.key == "rules" || s.key == "index" { chips(s).into_any() } else { dossier(s, &support).into_any() }).collect_view()}
</div>
</div>
}
}