feat(site): go.dev-style homepage + shared nav/footer chrome
- wo-html grows generic nav_bar/card/btn_link + the utility classes they need (sticky nav, footer, 2-col grid, hero sizes); library stays content-free - home: hero (tagline + Get started/View source CTAs), real actor+ table code showcase, four why-cards, chapters strip (gate's 'Learn writeonce' anchor kept); every page shares nav + footer - site gate 11/0 unchanged; loopback bind untouched Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
9661c08696
commit
b19042bca0
3 changed files with 91 additions and 9 deletions
|
|
@ -7,6 +7,20 @@
|
|||
-- site that teaches it.
|
||||
use html
|
||||
|
||||
-- The homepage's code showcase: a real flavor of the language — an
|
||||
-- actor per chat room, rows in the built-in database, one binary.
|
||||
fn home_snippet() -> Text {
|
||||
let s = "@table\nclass Message {\n room: Text\n body: Text\n}\n\n";
|
||||
s = s .. "class Room {\n name: Text\n fn receive(msg: Post) {\n";
|
||||
s = s .. " insert Message { room: self.name, body: msg.body };\n";
|
||||
s = s .. " print(\"[\${self.name}] \${msg.body}\");\n }\n}\n\n";
|
||||
s = s .. "fn main() -> Int {\n";
|
||||
s = s .. " let general: actor Post = spawn Room { name: \"general\" };\n";
|
||||
s = s .. " send(general, Post { body: \"hello, writeonce\" });\n";
|
||||
s = s .. " time.sleep(50);\n return 0;\n}";
|
||||
return code_block(s);
|
||||
}
|
||||
|
||||
fn ch_hello() -> Text {
|
||||
let b = el("p", "leading-relaxed mb-4",
|
||||
"A writeonce program is one directory of <code>.wo</code> files and one entry: a free " .. "function named <code>main</code>. It returns the process exit code. There is no " .. "runtime to install separately and no build pipeline — <code>woc build</code> produces " .. "ONE self-contained binary with the VM and your bytecode inside.");
|
||||
|
|
|
|||
|
|
@ -43,14 +43,26 @@ fn ok_html(body: Text) -> Resp {
|
|||
return Resp { status: 200, headers: h, body: body };
|
||||
}
|
||||
|
||||
fn site_header() -> Text {
|
||||
let brand = link("/", "text-xl font-bold text-gray-900 no-underline", "writeonce.de");
|
||||
let tag = el("span", "text-sm text-gray-500", "one language, one runtime, one database, one binary");
|
||||
return el("div", "flex items-center justify-between mb-8", brand .. tag);
|
||||
fn site_nav() -> Text {
|
||||
let links = link("/ch/hello", "text-gray-700", "Tutorial");
|
||||
links = links .. link("/health", "text-gray-700", "Health");
|
||||
links = links .. link("https://github.com/shoneyj", "text-gray-700", "GitHub");
|
||||
return nav_bar("/", "writeonce.de", links);
|
||||
}
|
||||
|
||||
fn site_footer() -> Text {
|
||||
return el("div", "footer", "writeonce.de — served by the language it teaches. " .. "One binary: compiler, runtime, database, this page.");
|
||||
}
|
||||
|
||||
fn shell(title: Text, inner: Text) -> Text {
|
||||
let body = el("div", "mx-auto max-w-3xl px-4 py-8", site_header() .. inner);
|
||||
let body = site_nav() .. el("div", "mx-auto max-w-3xl px-4 py-8", inner) .. site_footer();
|
||||
return page(title, body);
|
||||
}
|
||||
|
||||
-- The homepage uses the wider container (go.dev shape); chapter pages
|
||||
-- keep the reading width above.
|
||||
fn shell_wide(title: Text, inner: Text) -> Text {
|
||||
let body = site_nav() .. el("div", "mx-auto max-w-5xl px-4", inner) .. site_footer();
|
||||
return page(title, body);
|
||||
}
|
||||
|
||||
|
|
@ -72,10 +84,30 @@ fn chapter_nav(current: Int) -> Text {
|
|||
class Home {
|
||||
pad: Int
|
||||
fn handle(req: Req) -> Resp {
|
||||
let hero = el("h1", "text-3xl font-bold mb-4", "Learn writeonce");
|
||||
let intro = el("p", "leading-relaxed mb-4", "A language where the runtime, the database and the web server are one thing. " .. "This site is written in it — every page you read here is a " .. "<code>Resp</code> built by <code>.wo</code> code, stored in the language's own " .. "tables, served by its own framework. Work through the chapters in order:");
|
||||
let body = el("div", "bg-white rounded-lg border shadow-sm p-6", hero .. intro .. chapter_nav(0));
|
||||
return ok_html(shell("writeonce — learn the language", body));
|
||||
-- hero: tagline + the two CTAs (the go.dev shape, no JS anywhere)
|
||||
let h1 = el("h1", "text-4xl font-bold mb-4", "One language. One runtime.<br>One database. One binary.");
|
||||
let sub = el("p", "text-lg text-gray-700 leading-relaxed mb-6", "writeonce is a language whose compiler, runtime, web server and " .. "database ship as a single never-stopping Linux binary. Ownership-" .. "checked memory, inferred GC where ownership cannot reach, actors " .. "on every core — and the page you are reading is served by it.");
|
||||
let ctas = el("div", "flex items-center justify-center gap-4", btn_link("/ch/hello", "Get started", true) .. btn_link("https://github.com/shoneyj", "View source", false));
|
||||
let hero = el("div", "text-center py-16", h1 .. sub .. ctas);
|
||||
|
||||
-- code showcase: a real flavor of the language
|
||||
let show_head = el("h2", "text-2xl font-bold mb-2 text-center", "An actor per chat room, rows in the built-in database");
|
||||
let show_cap = el("p", "text-sm text-gray-500 text-center mb-4", "No broker, no ORM, no async keyword — ownership moves the message, the WAL makes the row durable.");
|
||||
let showcase = el("div", "mx-auto max-w-3xl mb-8", show_head .. show_cap .. home_snippet());
|
||||
|
||||
-- why-cards (2x2 grid, collapses on small screens)
|
||||
let cards = card("One binary", "woc build emits a self-contained executable: VM, your bytecode, the database engine. Deploys are a file copy; the runtime swaps code in place.");
|
||||
cards = cards .. card("Memory safety, no tax", "Rust-shaped ownership checked at compile time; where ownership cannot express the shape, the compiler infers GC — per shard, no global pause.");
|
||||
cards = cards .. card("The database is built in", "Every class is a table. Inserts are WAL-logged before they acknowledge; restart replays. No server to operate, no connection string.");
|
||||
cards = cards .. card("Actors on every core", "spawn returns an address, send moves ownership. Fibers park on io_uring instead of blocking threads — no async/await, ever.");
|
||||
let grid = el("div", "grid grid-cols-2 gap-6 mb-8", cards);
|
||||
|
||||
-- chapters (the gate's anchor string lives here)
|
||||
let learn = el("h2", "text-2xl font-bold mb-4", "Learn writeonce");
|
||||
let learn_p = el("p", "leading-relaxed mb-4", "The tutorial is written in the language and stored in its tables — work through the chapters in order:");
|
||||
let chapters = el("div", "bg-white rounded-lg border shadow-sm p-6 mb-8", learn .. learn_p .. chapter_nav(0));
|
||||
|
||||
return ok_html(shell_wide("writeonce — learn the language", hero .. showcase .. grid .. chapters));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -71,6 +71,29 @@ pub fn form_post(action: Text, inner: Text) -> Text {
|
|||
return "<form method=\"POST\" action=\"${esc(action)}\" class=\"flex flex-col gap-2\">${inner}</form>";
|
||||
}
|
||||
|
||||
-- A sticky top navigation bar: brand on the left, a prebuilt row of
|
||||
-- links on the right. The bar spans the viewport; the inner row shares
|
||||
-- the page's container widths.
|
||||
pub fn nav_bar(brand_href: Text, brand: Text, right: Text) -> Text {
|
||||
let b = link(brand_href, "text-xl font-bold text-gray-900 no-underline", brand);
|
||||
let row = el("div", "mx-auto max-w-5xl px-4 flex items-center justify-between", b .. el("div", "flex items-center gap-4", right));
|
||||
return el("nav", "nav", row);
|
||||
}
|
||||
|
||||
-- A titled card for feature grids.
|
||||
pub fn card(title: Text, body: Text) -> Text {
|
||||
let t = el("h3", "text-lg font-bold mb-2", title);
|
||||
return el("div", "bg-white rounded-lg border shadow-sm p-6", t .. el("p", "leading-relaxed text-gray-700", body));
|
||||
}
|
||||
|
||||
-- A link styled as a button; primary = filled, otherwise outline.
|
||||
pub fn btn_link(href: Text, label: Text, primary: Bool) -> Text {
|
||||
if primary {
|
||||
return link(href, "btn no-underline", label);
|
||||
}
|
||||
return link(href, "btn-outline no-underline", label);
|
||||
}
|
||||
|
||||
-- The utility sheet. Tailwind's names, one hand-written static sheet —
|
||||
-- only the utilities this ecosystem's pages actually use; growing it is
|
||||
-- adding a line, not adopting a toolchain. `.code-block`, `.field` and
|
||||
|
|
@ -107,6 +130,19 @@ pub fn tw_css() -> Text {
|
|||
c = c .. "padding:.5rem;font-family:ui-monospace,monospace;font-size:.875rem}";
|
||||
c = c .. ".btn{background:#2563eb;color:#fff;border:0;border-radius:.25rem;";
|
||||
c = c .. "padding:.5rem 1rem;font-weight:700;cursor:pointer}";
|
||||
c = c .. "a.btn{color:#fff;display:inline-block}a.btn:hover{text-decoration:none;background:#1d4ed8}";
|
||||
c = c .. ".btn-outline{display:inline-block;border:1px solid #2563eb;color:#2563eb;";
|
||||
c = c .. "border-radius:.25rem;padding:.5rem 1rem;font-weight:700}";
|
||||
c = c .. "a.btn-outline:hover{text-decoration:none;background:#eff6ff}";
|
||||
c = c .. ".nav{position:sticky;top:0;background:#fff;border-bottom:1px solid #e5e7eb;";
|
||||
c = c .. "padding:.75rem 0;z-index:10}";
|
||||
c = c .. ".footer{border-top:1px solid #e5e7eb;color:#6b7280;font-size:.875rem;";
|
||||
c = c .. "padding:2rem 1rem;text-align:center;margin-top:4rem}";
|
||||
c = c .. ".max-w-5xl{max-width:64rem}.text-4xl{font-size:2.25rem;line-height:1.2}";
|
||||
c = c .. ".py-16{padding-top:4rem;padding-bottom:4rem}.text-center{text-align:center}";
|
||||
c = c .. ".mb-6{margin-bottom:1.5rem}.gap-6{gap:1.5rem}.justify-center{justify-content:center}";
|
||||
c = c .. ".grid{display:grid}.grid-cols-2{grid-template-columns:repeat(2,1fr)}";
|
||||
c = c .. "@media(max-width:640px){.grid-cols-2{grid-template-columns:1fr}}";
|
||||
return c;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue