-- layout/app.wo — the app shell: one component wrapping every page with -- the shared header and footer. Unlike the shop template, this site -- INLINES its stylesheet (writeonce-view's `page()` does that), so the shell -- fills writeonce-view's own `Layout` rather than writing its own document. -- -- The only thing that varies between pages is the container width, so -- that is the one extra slot — and the two widths are named once, here, -- instead of as class strings scattered through the controllers. use porch/http use view pub class AppShell { title: Text container: Text content: Text fn render() -> Text { let l = Layout { title: self.title, head: head_links(), nav: header(), content: el("div", self.container, self.content), footer: footer() }; return l.render(); } } -- Chapter pages keep a reading width. pub fn reading_shell(title: Text, content: Text) -> AppShell { return AppShell { title: title, container: "mx-auto max-w-3xl px-4 py-8", content: content }; } -- The homepage uses the wider container (the go.dev shape). pub fn wide_shell(title: Text, content: Text) -> AppShell { return AppShell { title: title, container: "mx-auto max-w-5xl px-4", content: content }; } -- Every non-200 HTML answer goes through here, so an error page is a -- real page: same chrome, same shell, just a different status. pub fn html_error(status: Int, title: Text, msg: Text) -> Resp { let content = el("h1", "text-2xl font-bold mb-4", title) .. el("p", "", msg) .. el("p", "", link("/", "", "Back to the chapters")); let shell = reading_shell("writeonce — ${title}", content); let h: map = {}; h["content-type"] = "text/html; charset=utf-8"; return Resp { status: status, headers: h, body: shell.render() }; }