use anyhow::{Context, Result};
use maud::{html, Markup, DOCTYPE};
use minify_html::{minify, Cfg};
pub trait Page: Sized {
fn render_content(&self) -> Markup;
fn page_title() -> String;
fn render_head() -> Markup {
html!(
head {
(DOCTYPE)
meta charset="utf-8";
meta name="viewport" content="width=device-width,initial-scale=1";
link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=";
title { (Self::page_title()) }
}
)
}
fn logo_svg(height_px: f32) -> Markup {
html!(
svg xmlns="http://www.w3.org/2000/svg" width=(height_px * 0.707_070_7) height=(height_px) viewBox="0 0 210 297" {
path d="m143.17 118.01 19.018 30.905-33.758-.951zM66.41 118.01 47.39 148.915l33.758-.951z" style="opacity:.5;fill:#000;fill-opacity:1;stroke:#000;stroke-width:5.76727" transform="matrix(1.081 0 0 1.081 -10.214 -40)"{}
path d="M193.13 62.963s.92 3.455 0 4.74c-3.708 5.173-13.105 2.168-18.081 6.136-24.12 19.233-36.92 50.23-48.505 78.822-10.285 25.384-17.792 80.217-17.792 80.217h-6.274s-9.022-55.018-19.557-80.566c-11.655-28.264-24.145-58.718-47.478-78.473-4.225-3.577-12.324-1.556-15.432-6.136-.887-1.308 0-4.74 0-4.74" aria-label="V" style="font-size:251.905px;font-family:"Liberation Serif";-inkscape-font-specification:"Liberation Serif";opacity:1;fill:none;fill-opacity:1;stroke:#000;stroke-width:4.62538498;stroke-dasharray:none" transform="matrix(1.081 0 0 1.081 -10.214 -40)"{}
path d="M35.723 73.988h135.413v75.733H35.723v86.553h135.413" style="opacity:1;fill:none;fill-opacity:1;stroke:#000;stroke-width:4.62538498;stroke-dasharray:none" transform="matrix(1.081 0 0 1.081 -10.214 -40)"{}
path d="M35.723 87.977v54.416" style="opacity:.5;fill:none;stroke:#000;stroke-width:3.95992" transform="matrix(1.081 0 0 1.081 -10.214 -40)"{}
path d="M171.136 159.95v65.573" style="opacity:.5;fill:none;stroke:#000;stroke-width:4.34701" transform="matrix(1.081 0 0 1.081 -10.214 -40)"{}
path d="M83.645 241.56c-5.936 9.817-6.62 12.1-6.62 12.1-3.007 10.213-2.685 9.89-2.685 9.89M129.52 241.56c5.935 9.817 6.62 12.1 6.62 12.1 3.007 10.213 2.684 9.89 2.684 9.89M73.891 269.662l13.698 13.584zm-.8.343-16.208 11.643zm.344.228-2.17 16.551zM139.273 269.662l-13.698 13.584zm.799.343 16.209 11.643zm-.343.228 2.17 16.551z" style="opacity:.5;fill:none;fill-opacity:1;stroke:#000;stroke-width:3.91616" transform="matrix(1.081 0 0 1.081 -10.214 -40)"{}
}
)
}
fn render_navigation() -> Markup {
html!(
div class="navbar" {
header {
a href="/index.html" {
(Self::logo_svg(50.))
}
h1 {
"V2 Tales";
}
}
nav {
a href="/index.html" {
"HOME";
}
a href="/stories.html" {
"STORIES";
}
}
}
)
}
fn main_style() -> Markup {
html!("""
* {
padding: 0;
margin: 0;
}
h1 {
padding: 0.3rem;
letter-spacing: 0.8rem;
font-size: 1.5rem;
color: #2F3C57;
display: flex;
justify-content: center;
align-items: center;
gap: 2rem;
}
body {
margin: 2rem 1rem;
background-color: #ffc994;
text-align: center;
}
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 3rem;
}
header {
display: flex;
align-items: center;
a {
margin: 0 1rem;
}
}
nav {
display: flex;
justify-content: space-around;
width: 50%;
a {
text-decoration: none;
color: #2F3C57;
&:hover {
text-decoration: underline;
}
}
}
@media (max-width: 800px) {
nav a:first-child {
display: none;
}
header h1 {
display: none;
}
}
"""
)
}
fn content_style() -> Markup {
html!("")
}
fn style() -> Markup {
html!(
style {
(Self::main_style())
(Self::content_style())
}
)
}
fn render_page(&self) -> Markup {
html!((Self::render_head())(Self::style())(
Self::render_navigation()
)(Self::render_content(self)))
}
fn write_page(&mut self) -> Result<()> {
self.prepare_content()?;
let page_html = self.render_page().into_string();
let mut cfg = Cfg::new();
cfg.minify_css = true;
let minified_html = minify(page_html.as_bytes(), &cfg);
let html_path = format!(
"dist/{}.html",
Self::alternative_htmlfilename().unwrap_or(Self::page_title().to_lowercase())
);
std::fs::write(&html_path, minified_html)
.with_context(|| format!("Failed to write {}", html_path))?;
Ok(())
}
// use this to validate and prepare the content for the page, e.g. files
fn prepare_content(&mut self) -> Result<()> {
Ok(())
}
// without ".html"
fn alternative_htmlfilename() -> Option {
None
}
}