diff --git a/Justfile b/Justfile index 7cc2557..e9915ae 100644 --- a/Justfile +++ b/Justfile @@ -1,7 +1,7 @@ port := "8080" serve: - cargo watch -- cargo run -- serve --port {{port}} + RUST_BACKTRACE=1 cargo watch -- cargo run -- serve --port {{port}} fix: cargo run -- fix-all --apply diff --git a/content/about.tree b/content/about/v2.tree similarity index 100% rename from content/about.tree rename to content/about/v2.tree diff --git a/content/index.dj b/content/index.dj new file mode 100644 index 0000000..2fdb873 --- /dev/null +++ b/content/index.dj @@ -0,0 +1,37 @@ +title = "riki's house" +include_feed = { name = "new", title = "Blog" } + ++++ + +My name's *riki moe*, or *リキ萌*! + +I'm a _he/him_-type cat [furry][page:philosophy/furry] doing various things with computers. + +I work on game optimization tools at [[CD PROJEKT RED](https://cdprojektred.com) :rarog:]{.nowrap} but that's just the tip of the iceberg! +After hours, I program [websites](/), [compilers][def:stitchkit/repo], [audio][def:dawd3/repo], and other fun things. + +Such as [*video games.*][page:games] +Like, I probably wouldn't be in the industry if I didn't like them.\ +My personal favourites are [:nap: [DELTARUNE](https://deltarune.com)]{.nowrap}, [:bean: [Animal Well](https://www.animalwell.net/)]{.nowrap}, [:fox: [TUNIC](https://tunicgame.com/)]{.nowrap}, [:hueh: [A Hat in Time](https://hatintime.com)]{.nowrap}, and [:propane: [Noita](https://noitagame.com/)]{.nowrap}. +But also many many more, because I'm really indecisive. + +Or [*music.*][page:music] +I [listen to a lot of it.][def:social/bandcamp] +And I mean, [_a lot_.][def:social/listenbrainz] +A metric fuck tonne.\ +I'm a huge fan of electronic genres, but also jazz and (alternative) rock from time to time.\ +I listen to [_Songs About My Cats_](https://venetiansnares.bandcamp.com/album/songs-about-my-cats) while coding.\ +My favourite artists are [C418](https://c418.bandcamp.com/album/excursions), [The Flashbulb](https://theflashbulb.bandcamp.com/album/kirlian-selections), [Aphex Twin](https://aphextwin.bandcamp.com/album/drukqs), [Squarepusher](https://squarepusher.bandcamp.com/album/ultravisitor), and [False Noise](https://upscalehq.bandcamp.com/album/floral-strobe). + +I kinda also [make music sometimes](https://daknus.bandcamp.com) when I feel like it. + +I also drew a bit of dawing (...do a bit of drawing), as evidenced by the floofee on this page. + +If all that sounds like an interesting bunch of words... + +- feel free to email me: `hi` at this domain!! +- or add me on Discord---the nickname's *rikimoe*. + +I like to think I'm pretty amicable in person but I'm uh, also really socially awkward...!\ +~Please excuse any social awkwardness that may ensue from you contacting me.\ +Or me contacting you.~ diff --git a/content/index.tree b/content/index.tree index 7f437f2..5e7245f 100644 --- a/content/index.tree +++ b/content/index.tree @@ -73,10 +73,6 @@ visibility = "Private" - I'd like to make some new friends! if you wanna meet me, email `hi` at this domain. -% id = "about" - content.link = "about" -+ ## [``{=html}][page:kuroneko]{.secret}me - % id = "programming" content.link = "programming" + ## ``{=html}programming diff --git a/src/config.rs b/src/config.rs index 48fe361..c55cba2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -152,6 +152,8 @@ impl Config { } pub fn page_url(&self, page: &str) -> String { + // We don't want .dj appearing in URLs, though it exists as a disambiguator in [page:] links. + let page = page.strip_suffix(".dj").unwrap_or(page); format!("{}/{}", self.site, page) } diff --git a/src/generate.rs b/src/generate.rs index 7ea0bce..3caa73e 100644 --- a/src/generate.rs +++ b/src/generate.rs @@ -1,5 +1,6 @@ mod atom; mod dir_helper; +mod doc; mod include_static_helper; mod simple_template; mod tree; @@ -7,6 +8,7 @@ mod tree; use std::{ops::ControlFlow, sync::Arc}; use atom::FeedDir; +use chrono::{DateTime, Utc}; use dir_helper::DirHelper; use handlebars::{handlebars_helper, Handlebars}; use include_static_helper::IncludeStaticHelper; @@ -18,13 +20,14 @@ use crate::{ dirs::Dirs, fun::seasons::Season, generate::{ + doc::DocDir, simple_template::SimpleTemplateDir, tree::{DirIndex, TreehouseDir}, }, sources::Sources, vfs::{ - self, layered_dir, AnchoredAtExt, Cd, Content, ContentCache, Dir, DynDir, Entries, - HtmlCanonicalize, MemDir, Overlay, ToDynDir, VPath, VPathBuf, + self, layered_dir, AnchoredAtExt, Cd, Content, ContentCache, Dir, DynDir, HtmlCanonicalize, + MemDir, ToDynDir, VPath, VPathBuf, }, }; @@ -54,8 +57,10 @@ fn create_handlebars(site: &str, static_: DynDir) -> Handlebars<'static> { let mut handlebars = Handlebars::new(); handlebars_helper!(cat: |a: String, b: String| a + &b); + handlebars_helper!(iso_date: |d: DateTime| d.format("%F").to_string()); handlebars.register_helper("cat", Box::new(cat)); + handlebars.register_helper("iso_date", Box::new(iso_date)); handlebars.register_helper("asset", Box::new(DirHelper::new(site, static_.clone()))); handlebars.register_helper( "include_static", @@ -103,7 +108,13 @@ pub fn target(dirs: Arc, sources: Arc) -> DynDir { let dir_index = DirIndex::new(sources.treehouse.files_by_tree_path.keys().map(|x| &**x)); let treehouse_dir = layered_dir(&[ - TreehouseDir::new(dirs, sources.clone(), handlebars.clone(), dir_index).to_dyn(), + TreehouseDir::new(dirs.clone(), sources.clone(), handlebars.clone(), dir_index).to_dyn(), + DocDir { + sources: sources.clone(), + dirs, + handlebars: handlebars.clone(), + } + .to_dyn(), SimpleTemplateDir::new(sources.clone(), handlebars.clone()).to_dyn(), ]); diff --git a/src/generate/atom.rs b/src/generate/atom.rs index f41bb9b..1c042eb 100644 --- a/src/generate/atom.rs +++ b/src/generate/atom.rs @@ -11,7 +11,7 @@ use crate::{ html::djot::{self, resolve_link}, sources::Sources, state::FileId, - tree::SemaBranchId, + tree::{feed, SemaBranchId}, vfs::{self, Content, Dir, Entries, VPath, VPathBuf}, }; @@ -155,7 +155,7 @@ fn extract_entries(sources: &Sources, dirs: &Dirs, file_id: FileId) -> Vec Vec, - link: Option, -} - -fn parse_entry( - sources: &Sources, - dirs: &Dirs, - file_id: FileId, - parser: jotdown::Parser, -) -> ParsedEntry { - let mut parser = parser.into_offset_iter(); - while let Some((event, span)) = parser.next() { - if let jotdown::Event::Start(jotdown::Container::Heading { .. }, _attrs) = &event { - let mut events = vec![(event, span)]; - for (event, span) in parser.by_ref() { - // To my knowledge headings cannot nest, so it's okay not keeping a stack here. - let is_heading = matches!( - event, - jotdown::Event::End(jotdown::Container::Heading { .. }) - ); - events.push((event, span)); - if is_heading { - break; - } - } - - let title_events: Vec<_> = events - .iter() - .filter(|(event, _)| { - !matches!( - event, - // A little repetitive, but I don't mind. - // The point of this is not to include extra

and in the link text, - // but preserve other formatting such as bold, italic, code, etc. - jotdown::Event::Start( - jotdown::Container::Link(_, _) | jotdown::Container::Heading { .. }, - _ - ) | jotdown::Event::End( - jotdown::Container::Link(_, _) | jotdown::Container::Heading { .. } - ) - ) - }) - .cloned() - .collect(); - let mut title = String::new(); - let _render_diagnostics = djot::Renderer { - config: &sources.config, - dirs, - treehouse: &sources.treehouse, - file_id, - - // How. Just, stop. - page_id: "liquidex-you-reeeeeal-dummy".into(), - } - .render(&title_events, &mut title); - - let link = events.iter().find_map(|(event, _)| { - if let jotdown::Event::Start(jotdown::Container::Link(link, link_type), _) = event { - Some(link_url(sources, dirs, link, *link_type)) - } else { - None - } - }); - - return ParsedEntry { - title: (!title.is_empty()).then_some(title), - link, - }; - } - } - - ParsedEntry { - title: None, - link: None, - } -} - -fn link_url(sources: &Sources, dirs: &Dirs, url: &str, link_type: jotdown::LinkType) -> String { - if let jotdown::LinkType::Span(jotdown::SpanLinkType::Unresolved) = link_type { - if let Some(url) = resolve_link(&sources.config, &sources.treehouse, dirs, url) { - return url; - } - } - url.to_owned() -} - /// Extremely simple HTML renderer without the treehouse's fancy branch folding and linking features. fn branches_to_html_simple( s: &mut String, diff --git a/src/generate/doc.rs b/src/generate/doc.rs new file mode 100644 index 0000000..eda596f --- /dev/null +++ b/src/generate/doc.rs @@ -0,0 +1,242 @@ +use std::{ + fmt::{self}, + sync::Arc, +}; + +use anyhow::Context; +use chrono::{DateTime, Utc}; +use handlebars::Handlebars; +use serde::{Deserialize, Serialize}; +use tracing::{error, instrument}; + +use crate::{ + dirs::Dirs, + generate::BaseTemplateData, + html::djot, + sources::Sources, + state::{report_diagnostics, toml_error_to_diagnostic, FileId, TomlError}, + tree::{attributes::Picture, feed}, + vfs::{Content, Dir, Query, VPath}, +}; + +#[derive(Default, Deserialize)] +struct Attributes { + /// Template to use for generating the page. + /// Defaults to `_tree.hbs`. + #[serde(default)] + template: Option, + + /// Title of the page. + /// The only necessary field. + /// Unlike tree pages, doc pages always have titles. + title: String, + + /// ID of picture attached to the page, to be used as a thumbnail. + #[serde(default)] + thumbnail: Option, + + /// Additional scripts to load into to the page. + /// These are relative to the /static/js directory. + #[serde(default)] + scripts: Vec, + + /// Additional styles to load into to the page. + /// These are relative to the /static/css directory. + #[serde(default)] + styles: Vec, + + /// If not `None`, the page will get an additional 'feed' field in template data, containing + /// updates from the news feed of the specified name. + #[serde(default)] + include_feed: Option, +} + +#[derive(Deserialize)] +struct IncludeFeed { + /// The name of the feed (within the treehouse database.) + name: String, + + /// The title of the feed shown on the page. + title: String, +} + +#[derive(Serialize)] +struct Page { + title: String, + thumbnail: Option, + scripts: Vec, + styles: Vec, + tree_path: String, + doc: String, + feed: Option, +} + +#[derive(Serialize)] +struct Thumbnail { + url: String, + alt: Option, +} + +#[derive(Serialize)] +struct Feed { + title: String, + entries: Vec, +} + +#[derive(Serialize)] +struct Entry { + title: String, + url: String, + updated: DateTime, + categories: Vec, +} + +#[derive(Serialize)] +struct PageTemplateData<'a> { + #[serde(flatten)] + base: &'a BaseTemplateData<'a>, + page: Page, +} + +pub struct DocDir { + pub sources: Arc, + pub dirs: Arc, + + pub handlebars: Arc>, +} + +impl DocDir { + #[instrument("DocDir::content", skip(self))] + pub fn content(&self, path: &VPath) -> Option { + if let Some(file_id) = self + .sources + .treehouse + .files_by_doc_path + .get(&path.with_extension("dj")) + { + let source = self.sources.treehouse.source(*file_id).input(); + return Some(Content::new( + "text/html", + self.generate(*file_id, path, source).into_bytes(), + )); + } + + None + } + + fn generate(&self, file_id: FileId, path: &VPath, source: &str) -> String { + let (front_matter, text) = source.split_once("+++").unwrap_or(("", source)); + let attributes: Attributes = + toml_edit::de::from_str(front_matter).unwrap_or_else(|error| { + _ = report_diagnostics( + &self.sources.treehouse, + &[toml_error_to_diagnostic(TomlError { + message: error.message().to_owned(), + span: error.span(), + file_id, + input_range: 0..front_matter.len(), + })], + ); + Attributes::default() + }); + + let events: Vec<_> = jotdown::Parser::new(text).into_offset_iter().collect(); + let mut rendered_markup = String::new(); + let render_diagnostics = djot::Renderer { + config: &self.sources.config, + dirs: &self.dirs, + treehouse: &self.sources.treehouse, + file_id, + page_id: path.to_string(), + } + .render(&events, &mut rendered_markup); + + let template_name = attributes.template.as_deref().unwrap_or("_doc.hbs"); + + let render_result = self + .handlebars + .render( + template_name, + &PageTemplateData { + base: &BaseTemplateData::new(&self.sources), + page: Page { + title: attributes.title, + thumbnail: attributes.thumbnail.map(|pic| Thumbnail { + url: self.sources.config.pic_url(&*self.dirs.pic, &pic.id), + alt: pic.alt, + }), + scripts: attributes.scripts, + styles: attributes.styles, + tree_path: path.to_string(), + doc: rendered_markup, + feed: attributes.include_feed.and_then(|feed| { + Some(Feed { + title: feed.title, + entries: self + .generate_feed(&feed.name) + .inspect_err(|e| { + error!("generating feed for {path} failed: {e}") + }) + .ok()?, + }) + }), + }, + }, + ) + .context("template rendering failed"); + match render_result { + Ok(rendered) => rendered, + Err(error) => format!("{error:#?}"), + } + } + + fn generate_feed(&self, name: &str) -> anyhow::Result> { + let file_id = *self + .sources + .treehouse + .feeds_by_name + .get(name) + .context("no feed with the given name")?; + let roots = &self.sources.treehouse.roots[&file_id]; + + Ok(roots + .branches + .iter() + .flat_map(|&branch_id| { + let branch = self.sources.treehouse.tree.branch(branch_id); + + let text = &self.sources.treehouse.source(file_id).input()[branch.content.clone()]; + let parsed = feed::parse_entry( + &self.sources, + &self.dirs, + file_id, + jotdown::Parser::new(text), + ); + + let updated = branch + .attributes + .timestamp() + .unwrap_or(DateTime::UNIX_EPOCH); // if you see the Unix epoch... oops + + parsed.link.map(|url| Entry { + updated, + url, + title: parsed.title.unwrap_or_else(|| "untitled".into()), + categories: branch.attributes.tags.clone(), + }) + }) + .collect()) + } +} + +impl Dir for DocDir { + fn query(&self, path: &VPath, query: &mut Query) { + query.try_provide(|| self.content(path)); + } +} + +impl fmt::Debug for DocDir { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("DocDir") + } +} diff --git a/src/html/djot.rs b/src/html/djot.rs index 582aae4..8ce3b11 100644 --- a/src/html/djot.rs +++ b/src/html/djot.rs @@ -106,21 +106,6 @@ impl<'a> Writer<'a> { range: Range, out: &mut String, ) -> std::fmt::Result { - if let Event::Start(Container::Footnote { label: _ }, ..) = e { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: Some("djot".into()), - message: "Djot footnotes are not supported".into(), - labels: vec![Label { - style: LabelStyle::Primary, - file_id: self.renderer.file_id, - range: range.clone(), - message: "".into(), - }], - notes: vec![], - }) - } - if matches!(&e, Event::Start(Container::LinkDefinition { .. }, ..)) { self.ignore_next_event = true; return Ok(()); @@ -163,7 +148,7 @@ impl<'a> Writer<'a> { } => { out.push_str(" 1 { - write!(out, r#" start="{}""#, start)?; + write!(out, r#" start="{start}""#)?; } if let Some(ty) = match numbering { Decimal => None, @@ -172,7 +157,7 @@ impl<'a> Writer<'a> { RomanLower => Some('i'), RomanUpper => Some('I'), } { - write!(out, r#" type="{}""#, ty)?; + write!(out, r#" type="{ty}""#)?; } } } @@ -182,7 +167,7 @@ impl<'a> Writer<'a> { } Container::DescriptionList => out.push_str(" out.push_str(" unreachable!(), + Container::Footnote { label } => out.push_str(label), Container::Table => out.push_str(" out.push_str(" {} @@ -193,7 +178,7 @@ impl<'a> Writer<'a> { } out.push_str(" write!(out, " write!(out, " out.push_str(" out.push_str(" out.push_str(" Writer<'a> { .into_iter() .filter(|(a, _)| !(*a == "class" || a.starts_with(':'))) { - write!(out, r#" {}=""#, key)?; + write!(out, r#" {key}=""#)?; value.parts().for_each(|part| write_attr(part, out)); out.push('"'); } @@ -338,7 +323,7 @@ impl<'a> Writer<'a> { Alignment::Center => "center", Alignment::Right => "right", }; - write!(out, r#" style="text-align: {};">"#, a)?; + write!(out, r#" style="text-align: {a};">"#)?; } Container::CodeBlock { language } => { if language.is_empty() { @@ -444,7 +429,7 @@ impl<'a> Writer<'a> { } Container::DescriptionList => out.push_str(""), Container::DescriptionDetails => out.push_str(""), - Container::Footnote { .. } => unreachable!(), + Container::Footnote { label } => out.push_str(label), Container::Table => out.push_str(""), Container::TableRow { .. } => out.push_str(""), Container::Section { .. } => {} @@ -455,7 +440,7 @@ impl<'a> Writer<'a> { } out.push_str("

"); } - Container::Heading { level, .. } => write!(out, "", level)?, + Container::Heading { level, .. } => write!(out, "")?, Container::TableCell { head: false, .. } => out.push_str(""), Container::TableCell { head: true, .. } => out.push_str(""), Container::Caption => out.push_str(""), @@ -537,19 +522,8 @@ impl<'a> Writer<'a> { Raw::Html => out.push_str(s), Raw::Other => {} }, - Event::FootnoteReference(_label) => { - self.diagnostics.push(Diagnostic { - severity: Severity::Error, - code: Some("djot".into()), - message: "Djot footnotes are unsupported".into(), - labels: vec![Label { - style: LabelStyle::Primary, - file_id: self.renderer.file_id, - range, - message: "".into(), - }], - notes: vec![], - }); + Event::FootnoteReference(label) => { + out.push_str(label); } Event::Symbol(sym) => { if let Some(vpath) = self.renderer.config.emoji.get(sym.as_ref()) { @@ -624,7 +598,7 @@ impl<'a> Writer<'a> { } out.push_str(" Some("""), _ => None, } - .map_or(false, |s| { + .is_some_and(|s| { ent = s; true }) diff --git a/src/sources.rs b/src/sources.rs index 56feac4..9e0938a 100644 --- a/src/sources.rs +++ b/src/sources.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, ops::ControlFlow}; use anyhow::{anyhow, Context}; use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; -use tracing::{info_span, instrument}; +use tracing::{error, info_span, instrument}; use crate::{ config::Config, @@ -66,14 +66,19 @@ fn load_trees(config: &Config, dirs: &Dirs) -> anyhow::Result { let mut parsed_trees = HashMap::new(); let mut paths = vec![]; + let mut doc_paths = vec![]; vfs::walk_dir_rec(&*dirs.content, VPath::ROOT, &mut |path| { - if path.extension() == Some("tree") { - paths.push(path.to_owned()); + match path.extension() { + Some("tree") => paths.push(path.to_owned()), + Some("dj") => doc_paths.push(path.to_owned()), + _ => (), } ControlFlow::Continue(()) }); + // Trees + // NOTE: Sources are filled in later; they can be left out until a call to report_diagnostics. let file_ids: Vec<_> = paths .iter() @@ -132,5 +137,18 @@ fn load_trees(config: &Config, dirs: &Dirs) -> anyhow::Result { report_diagnostics(&treehouse, &diagnostics)?; + // Docs + + for path in doc_paths { + if let Some(input) = + vfs::query::(&dirs.content, &path).and_then(|c| c.string().ok()) + { + let file_id = treehouse.add_file(path.clone(), Source::Other(input)); + treehouse.files_by_doc_path.insert(path, file_id); + } else { + error!("doc {path} does not exist in content directory even though it was enumerated via walk_dir_rec"); + } + } + Ok(treehouse) } diff --git a/src/state.rs b/src/state.rs index 9a592b9..ab98670 100644 --- a/src/state.rs +++ b/src/state.rs @@ -66,7 +66,8 @@ pub struct FileId(usize); /// Treehouse compilation context. pub struct Treehouse { pub files: Vec, - pub files_by_tree_path: HashMap, + pub files_by_tree_path: HashMap, // trees only + pub files_by_doc_path: HashMap, // docs only pub feeds_by_name: HashMap, pub tree: SemaTree, @@ -83,6 +84,7 @@ impl Treehouse { Self { files: vec![], files_by_tree_path: HashMap::new(), + files_by_doc_path: HashMap::new(), feeds_by_name: HashMap::new(), tree: SemaTree::default(), diff --git a/src/tree.rs b/src/tree.rs index b922e32..c5a62e1 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -1,5 +1,6 @@ pub mod ast; pub mod attributes; +pub mod feed; pub mod mini_template; pub mod pull; diff --git a/src/tree/attributes.rs b/src/tree/attributes.rs index 60fed8b..c18fa3e 100644 --- a/src/tree/attributes.rs +++ b/src/tree/attributes.rs @@ -23,10 +23,6 @@ pub struct RootAttributes { #[serde(default = "default_icon")] pub icon: String, - /// Summary of the generated .html page. - #[serde(default)] - pub description: Option, - /// ID of picture attached to the page, to be used as a thumbnail. #[serde(default)] pub thumbnail: Option, @@ -50,7 +46,7 @@ pub struct RootAttributes { #[serde(default)] pub timestamps: Option, - /// When specified, this page will have a corresponding Atom feed under `rss/{feed}.xml`. + /// When specified, this page will have a corresponding Atom feed under `feed/{feed}.atom`. /// /// In feeds, top-level branches are expected to have a single heading containing the post title. /// Their children are turned into the post description diff --git a/src/tree/feed.rs b/src/tree/feed.rs new file mode 100644 index 0000000..b8c0d77 --- /dev/null +++ b/src/tree/feed.rs @@ -0,0 +1,94 @@ +use crate::{ + dirs::Dirs, + html::djot::{self, resolve_link}, + sources::Sources, + state::FileId, +}; + +#[derive(Debug, Clone)] +pub struct ParsedEntry { + pub title: Option, + pub link: Option, +} + +pub fn parse_entry( + sources: &Sources, + dirs: &Dirs, + file_id: FileId, + parser: jotdown::Parser, +) -> ParsedEntry { + let mut parser = parser.into_offset_iter(); + while let Some((event, span)) = parser.next() { + if let jotdown::Event::Start(jotdown::Container::Heading { .. }, _attrs) = &event { + let mut events = vec![(event, span)]; + for (event, span) in parser.by_ref() { + // To my knowledge headings cannot nest, so it's okay not keeping a stack here. + let is_heading = matches!( + event, + jotdown::Event::End(jotdown::Container::Heading { .. }) + ); + events.push((event, span)); + if is_heading { + break; + } + } + + let title_events: Vec<_> = events + .iter() + .filter(|(event, _)| { + !matches!( + event, + // A little repetitive, but I don't mind. + // The point of this is not to include extra

and in the link text, + // but preserve other formatting such as bold, italic, code, etc. + jotdown::Event::Start( + jotdown::Container::Link(_, _) | jotdown::Container::Heading { .. }, + _ + ) | jotdown::Event::End( + jotdown::Container::Link(_, _) | jotdown::Container::Heading { .. } + ) + ) + }) + .cloned() + .collect(); + let mut title = String::new(); + let _render_diagnostics = djot::Renderer { + config: &sources.config, + dirs, + treehouse: &sources.treehouse, + file_id, + + // How. Just, stop. + page_id: "liquidex-you-reeeeeal-dummy".into(), + } + .render(&title_events, &mut title); + + let link = events.iter().find_map(|(event, _)| { + if let jotdown::Event::Start(jotdown::Container::Link(link, link_type), _) = event { + Some(link_url(sources, dirs, link, *link_type)) + } else { + None + } + }); + + return ParsedEntry { + title: (!title.is_empty()).then_some(title), + link, + }; + } + } + + ParsedEntry { + title: None, + link: None, + } +} + +fn link_url(sources: &Sources, dirs: &Dirs, url: &str, link_type: jotdown::LinkType) -> String { + if let jotdown::LinkType::Span(jotdown::SpanLinkType::Unresolved) = link_type { + if let Some(url) = resolve_link(&sources.config, &sources.treehouse, dirs, url) { + return url; + } + } + url.to_owned() +} diff --git a/static/character/riki/sitting.png b/static/character/riki/sitting.png new file mode 100644 index 0000000..316229e Binary files /dev/null and b/static/character/riki/sitting.png differ diff --git a/static/css/base.css b/static/css/base.css index 0ef71d0..7f32d49 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -1,4 +1,4 @@ -/* Color scheme. */ +/* Color scheme */ :root { --accent-red: #fb4c9e; @@ -36,6 +36,18 @@ } } +/* Animations */ + +:root { + --transition-duration: 0.15s; +} + +@media (prefers-reduced-motion: reduce) { + :root { + --transition-duration: 0; + } +} + /* Reset things to more sensible sizing rules */ * { diff --git a/static/css/doc.css b/static/css/doc.css new file mode 100644 index 0000000..2832972 --- /dev/null +++ b/static/css/doc.css @@ -0,0 +1,84 @@ +main.doc { + --doc-text-width: 80ch; + + display: flex; + flex-direction: row; + align-items: start; + + & .vertical-center { + min-height: 100vh; + flex-grow: 1; + + display: flex; + flex-direction: column; + + align-items: center; + justify-content: center; + } + + & .doc-text { + padding: 1.6rem; + max-width: var(--doc-text-width); + + line-height: 1.6; + + /* I was thinking a bunch about whether documents should be justified, and it honestly + causes more awkwardness than it solves. Web pages aren't quite books, unfortunately. + + A cool feature that would help is text-wrap: pretty; but only Safari implements a nice + text layout algorithm for it. (Chrome prevents short last lines, Firefox doesn't + implement it at all) */ + + /* text-align: justify; + hyphens: auto; */ + + & p { + padding-top: 0.5lh; + padding-bottom: 0.5lh; + } + + & h2 { + margin: 0; + padding-top: 1lh; + padding-bottom: 0.5lh; + } + + & ul, + & ol { + /* Is there a better way to add spacing to the marker, other than adding whitespace? */ + list-style: "– "; + margin-top: 0; + margin-bottom: 0; + padding-bottom: 0.5lh; + } + } + + & section.feed { + max-width: 40ch; + padding: 0.8rem; + padding-top: 3.2rem; + } +} + +@media (max-width: 1500px) { + main.doc { + flex-direction: column; + align-items: center; + + & .vertical-center { + min-height: 0; + } + + & footer { + padding: 0.8rem; + } + + & section.feed { + max-width: var(--doc-text-width); + + margin-top: 2.4em; + padding: 1.6rem; + border-top: 1px solid var(--border-1); + } + } +} diff --git a/static/css/main.css b/static/css/main.css index 2b4b5e0..df1456d 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -1,73 +1,46 @@ -/* Lay out the main containers. */ +/* Main layout */ body { - --top-min-spacing: 40px; + --main-min-size: 100vh; margin: 0; display: grid; grid-template-columns: - [left] minmax( - 0, - clamp(136px, calc(100vw - (1920px - 360px - 160px)), 160px) - ) - [center] minmax(0, auto) - [right] minmax(0, calc(100vw - (1920px - 360px))); + [left] 1fr + [right] auto; grid-template-rows: - [top] minmax( - clamp( - var(--top-min-spacing), - calc(100vw - (1920px - 360px - 160px)), - 128px - ), - min-content - ) - [title] minmax(9.6rem, min-content) - [main] 1fr - [bottom] min-content; + [nav] auto + [main] minmax(var(--main-min-size), auto) + [virtual] 100vh; } html { - /* Try to always leave a bunch of empty space at the bottom, but don't overdo it. - It's kind of awkward when you scroll to the bottom and your page just turns blank. */ - --virtual-space-ratio: 1.75; - - height: calc(100% * var(--virtual-space-ratio)); - /* Leave a bunch of space at the top when scrolling to elements. I'm honestly not sure why this is needed on and not the scrolled-to element... */ scroll-padding-top: 10vh; } -body { - min-height: calc(100% / var(--virtual-space-ratio)); +.sidebar-sticky { + grid-column: right; + grid-row: main; } -.noscript { - grid-row: top; - grid-column: center; -} +aside.sidebar { + position: sticky; + top: 0px; -#nav-logo { - grid-row: title; - grid-column: left; + max-width: 50rem; + height: 100vh; + padding: 0.8rem; - align-self: center; - justify-self: end; -} - -section.page-header { - grid-row: title; - grid-column: center; - - align-self: center; + display: flex; } main { + grid-column: left; grid-row: main; - grid-column: center / center; - - margin-right: 0.8rem; + min-width: 0; } footer { @@ -75,27 +48,25 @@ footer { grid-column: center / center; } -@media (max-width: 1200px) { - main { - grid-column: left / -1; - } +/* Narrower layout: sidebar is pushed to the top */ - footer { - grid-column: 1 / -1; - } -} - -@media (max-width: 450px) { +@media (max-width: 1280px) { body { - --top-min-spacing: 0px; + --main-min-size: 0; } - section.page-header { - grid-column: 1 / -1; + .sidebar-sticky { + grid-column: left; + grid-row: nav; + + display: flex; + justify-content: center; } - nav#nav-logo { - display: none; + aside.sidebar { + position: relative; + height: auto; + padding: 0; } } @@ -119,16 +90,6 @@ body { /* Set up typography */ -@font-face { - font-family: "RecVar"; - /* NOTE: I put the hash in here manually instead of adding the complexity of piping CSS through - Handlebars because I don't really think it's worth it for this single asset. - Other assets are referenced rarely enough that caching probably isn't gonna make too much of - an impact. - It's unlikely I'll ever update the font anyways, so eh, whatever. */ - src: url("../font/Recursive_VF_1.085.woff2?v=b3-41236e2f"); -} - body, pre, code, @@ -192,7 +153,7 @@ input { h1 { --recursive-wght: 900; - font-size: 5.6rem; + font-size: 4.8rem; font-feature-settings: var(--recursive-simplified-r) 0; } @@ -246,6 +207,12 @@ h6 { text-wrap: balance; } +/* Other classes for controlling typography */ + +.nowrap { + white-space: nowrap; +} + /* Lay out elements a bit more compactly */ p, @@ -320,21 +287,21 @@ th-literate-program { overflow: auto; } -/* Also don't let images get out of hand */ +/* Images */ img { + /* Prevent images from causing horizontal scrolling */ max-width: 100%; + height: auto; } -/* Also regarding images - make them look a bit more pretty by default */ - img.pic { border-radius: 0.6rem; margin: 0.8rem 0; } -/* Image hints for tweaking rendering */ img { + /* Hints for tweaking rendering */ &[src*="+pixel"] { image-rendering: pixelated; border-radius: 0; @@ -378,8 +345,6 @@ a:visited { color: var(--link-color-visited); } -/* Allow for some secret links */ - a.secret { color: var(--text-color); text-decoration: none; @@ -460,32 +425,224 @@ hr { color: #6c2380; } -/* Navigation button */ +/* Feeds */ -#nav-logo { - width: min-content; - height: min-content; +section.feed { + display: flex; + flex-direction: column; + + /* Titles */ + + & a, + & a:visited { + color: var(--text-color); + } + + & a:visited { + color: color-mix( + in srgb, + var(--background-color), + var(--text-color) 60% + ); + } + + & h1 { + --recursive-wght: 800; + font-size: 125%; + padding-top: 1.2rem; + padding-bottom: 1.2rem; + } + + & h2 { + --recursive-wght: 600; + font-size: 100%; + padding: 0; + } + + /* Articles */ + + & article { + display: flex; + flex-direction: column; + + padding-bottom: 1.2rem; + + line-height: 1.4; + + & .info { + display: flex; + flex-direction: row; + flex-wrap: wrap; + font-size: 87.5%; + + & > *:not(:first-child)::before { + content: "·"; + padding: 0 0.4rem; + } + } + + & .categories { + display: flex; + flex-direction: row; + flex-wrap: wrap; + + list-style: none; + margin: 0; + padding: 0; + + & > *::before { + content: "#"; + } + + & > *:not(:first-child)::before { + padding-left: 0.4rem; + } + } + } } -#nav-logo .logo { - /* NOTE: Measurements in px for pixel perfection */ - width: 120px; - height: 120px; +/* Page sidebar */ - display: block; - opacity: 100%; - color: var(--text-color); +aside.sidebar { + overflow: clip; + + & > a { + display: block; + height: min-content; + margin-top: auto; + } +} + +header.floof { + margin-top: auto; + + position: relative; + + & > img { + display: block; + min-width: 0; + object-fit: cover; + object-position: 33% 0; + } + + & > h1 { + position: absolute; + top: 3rem; + left: 3rem; + + display: flex; + flex-direction: column; + line-height: 1; + width: min-content; + + --recursive-wght: 900; + font-size: 5.6rem; + text-align: right; + + transform: skew(-5deg, -5deg); + + & .rikis { + width: max-content; + background-color: var(--text-color); + color: var(--background-color); + padding: 0.1em; + + --shadow-color: var(--accent-pink); + box-shadow: + 0.5px 0.5px 0 var(--shadow-color), + 1px 1px 0 var(--shadow-color), + 1.5px 1.5px 0 var(--shadow-color), + 2px 2px 0 var(--shadow-color), + 2.5px 2.5px 0 var(--shadow-color), + 3px 3px 0 var(--shadow-color), + 3.5px 3.5px 0 var(--shadow-color), + 4px 4px 0 var(--shadow-color); + + /* + import math + + print("box-shadow:") + x = 0 + max_x = 16 + while x < max_x: + print(f"{x}px {x}px {math.pow(x / max_x, 2) * 16}px rgba(from var(--shadow-color) r g b / {math.pow(1 - x / max_x, 3)}),") + x += 0.5 + */ + /* prettier-ignore */ + box-shadow: + 0px 0px 0.0px rgba(from var(--shadow-color) r g b / 1.0), + 0.5px 0.5px 0.015625px rgba(from var(--shadow-color) r g b / 0.909149169921875), + 1.0px 1.0px 0.0625px rgba(from var(--shadow-color) r g b / 0.823974609375), + 1.5px 1.5px 0.140625px rgba(from var(--shadow-color) r g b / 0.744293212890625), + 2.0px 2.0px 0.25px rgba(from var(--shadow-color) r g b / 0.669921875), + 2.5px 2.5px 0.390625px rgba(from var(--shadow-color) r g b / 0.600677490234375), + 3.0px 3.0px 0.5625px rgba(from var(--shadow-color) r g b / 0.536376953125), + 3.5px 3.5px 0.765625px rgba(from var(--shadow-color) r g b / 0.476837158203125), + 4.0px 4.0px 1.0px rgba(from var(--shadow-color) r g b / 0.421875), + 4.5px 4.5px 1.265625px rgba(from var(--shadow-color) r g b / 0.371307373046875), + 5.0px 5.0px 1.5625px rgba(from var(--shadow-color) r g b / 0.324951171875), + 5.5px 5.5px 1.890625px rgba(from var(--shadow-color) r g b / 0.282623291015625), + 6.0px 6.0px 2.25px rgba(from var(--shadow-color) r g b / 0.244140625), + 6.5px 6.5px 2.640625px rgba(from var(--shadow-color) r g b / 0.209320068359375), + 7.0px 7.0px 3.0625px rgba(from var(--shadow-color) r g b / 0.177978515625), + 7.5px 7.5px 3.515625px rgba(from var(--shadow-color) r g b / 0.149932861328125), + 8.0px 8.0px 4.0px rgba(from var(--shadow-color) r g b / 0.125), + 8.5px 8.5px 4.515625px rgba(from var(--shadow-color) r g b / 0.102996826171875), + 9.0px 9.0px 5.0625px rgba(from var(--shadow-color) r g b / 0.083740234375), + 9.5px 9.5px 5.640625px rgba(from var(--shadow-color) r g b / 0.067047119140625), + 10.0px 10.0px 6.25px rgba(from var(--shadow-color) r g b / 0.052734375), + 10.5px 10.5px 6.890625px rgba(from var(--shadow-color) r g b / 0.040618896484375), + 11.0px 11.0px 7.5625px rgba(from var(--shadow-color) r g b / 0.030517578125), + 11.5px 11.5px 8.265625px rgba(from var(--shadow-color) r g b / 0.022247314453125), + 12.0px 12.0px 9.0px rgba(from var(--shadow-color) r g b / 0.015625), + 12.5px 12.5px 9.765625px rgba(from var(--shadow-color) r g b / 0.010467529296875), + 13.0px 13.0px 10.5625px rgba(from var(--shadow-color) r g b / 0.006591796875), + 13.5px 13.5px 11.390625px rgba(from var(--shadow-color) r g b / 0.003814697265625), + 14.0px 14.0px 12.25px rgba(from var(--shadow-color) r g b / 0.001953125), + 14.5px 14.5px 13.140625px rgba(from var(--shadow-color) r g b / 0.000823974609375), + 15.0px 15.0px 14.0625px rgba(from var(--shadow-color) r g b / 0.000244140625), + 15.5px 15.5px 15.015625px rgba(from var(--shadow-color) r g b / 3.0517578125e-05) + ; + } + + & .fluffy-little-house { + display: flex; + flex-direction: column; + + background-color: var(--background-color); + width: min-content; + align-self: end; + + padding-left: 0.8rem; + padding-right: 0.8rem; + padding-top: 0.4rem; + + z-index: -1; + + color: var(--text-color); + + & .adjectives { + --recursive-wght: 800; + font-size: 1.6rem; + padding-top: 0.6rem; + } + + & .house { + margin-top: -0.2em; + font-size: 3.6rem; + } + } + } } /* Navigation header (contains page title & breadcrumbs) */ h1.page-title { - --recursive-wght: 850; + --recursive-wght: 900; - margin-top: 0.32rem; - margin-bottom: 0.32rem; - margin-left: 3.6rem; - font-size: 4rem; + line-height: 1.2; + padding-top: 3lh; + padding-bottom: 0.5lh; & a { color: var(--text-color); @@ -506,7 +663,18 @@ h1.page-title { } } -/* Style badges */ +@media (max-width: 1280px) { + h1.page-title { + padding-top: 0.25lh; + } +} + +@media (max-width: 700px) { + h1.page-title { + font-size: 4rem; + } +} + span.badge { --recursive-wght: 800; --recursive-mono: 1; @@ -533,11 +701,9 @@ span.badge { /* Style the footer */ footer { - padding-left: 1.6rem; - padding-right: 1.6rem; - - margin-top: 6.4rem; - padding-bottom: 6.4rem; + width: 100%; + max-width: 90ch; + padding: 1.6rem 0.8rem; display: flex; flex-direction: row; @@ -610,9 +776,9 @@ dialog[open] { /* Style emojis to be readable */ img[data-cast~="emoji"] { - max-width: 1.5em; - max-height: 1.5em; - vertical-align: bottom; + max-width: 1.3125em; + max-height: 1.3125em; + vertical-align: text-bottom; object-fit: contain; } diff --git a/static/css/page/index.css b/static/css/page/index.css index ac95a5a..a6efd39 100644 --- a/static/css/page/index.css +++ b/static/css/page/index.css @@ -1,98 +1,3 @@ -h1.page-title { - --recursive-wght: 900; - font-size: 5.6rem; - - display: flex; - flex-direction: column; - line-height: 1; - width: min-content; - - transform: skew(-5deg, -5deg); - - & .rikis { - width: max-content; - background-color: var(--text-color); - color: var(--background-color); - padding: 0.1em; - - --shadow-color: var(--accent-pink); - box-shadow: - 0.5px 0.5px 0 var(--shadow-color), - 1px 1px 0 var(--shadow-color), - 1.5px 1.5px 0 var(--shadow-color), - 2px 2px 0 var(--shadow-color), - 2.5px 2.5px 0 var(--shadow-color), - 3px 3px 0 var(--shadow-color), - 3.5px 3.5px 0 var(--shadow-color), - 4px 4px 0 var(--shadow-color); - - /* - import math - - print("box-shadow:") - x = 0 - max_x = 16 - while x < max_x: - print(f"{x}px {x}px {math.pow(x / max_x, 2) * 16}px rgba(from var(--shadow-color) r g b / {math.pow(1 - x / max_x, 3)}),") - x += 0.5 - */ - /* prettier-ignore */ - box-shadow: - 0px 0px 0.0px rgba(from var(--shadow-color) r g b / 1.0), - 0.5px 0.5px 0.015625px rgba(from var(--shadow-color) r g b / 0.909149169921875), - 1.0px 1.0px 0.0625px rgba(from var(--shadow-color) r g b / 0.823974609375), - 1.5px 1.5px 0.140625px rgba(from var(--shadow-color) r g b / 0.744293212890625), - 2.0px 2.0px 0.25px rgba(from var(--shadow-color) r g b / 0.669921875), - 2.5px 2.5px 0.390625px rgba(from var(--shadow-color) r g b / 0.600677490234375), - 3.0px 3.0px 0.5625px rgba(from var(--shadow-color) r g b / 0.536376953125), - 3.5px 3.5px 0.765625px rgba(from var(--shadow-color) r g b / 0.476837158203125), - 4.0px 4.0px 1.0px rgba(from var(--shadow-color) r g b / 0.421875), - 4.5px 4.5px 1.265625px rgba(from var(--shadow-color) r g b / 0.371307373046875), - 5.0px 5.0px 1.5625px rgba(from var(--shadow-color) r g b / 0.324951171875), - 5.5px 5.5px 1.890625px rgba(from var(--shadow-color) r g b / 0.282623291015625), - 6.0px 6.0px 2.25px rgba(from var(--shadow-color) r g b / 0.244140625), - 6.5px 6.5px 2.640625px rgba(from var(--shadow-color) r g b / 0.209320068359375), - 7.0px 7.0px 3.0625px rgba(from var(--shadow-color) r g b / 0.177978515625), - 7.5px 7.5px 3.515625px rgba(from var(--shadow-color) r g b / 0.149932861328125), - 8.0px 8.0px 4.0px rgba(from var(--shadow-color) r g b / 0.125), - 8.5px 8.5px 4.515625px rgba(from var(--shadow-color) r g b / 0.102996826171875), - 9.0px 9.0px 5.0625px rgba(from var(--shadow-color) r g b / 0.083740234375), - 9.5px 9.5px 5.640625px rgba(from var(--shadow-color) r g b / 0.067047119140625), - 10.0px 10.0px 6.25px rgba(from var(--shadow-color) r g b / 0.052734375), - 10.5px 10.5px 6.890625px rgba(from var(--shadow-color) r g b / 0.040618896484375), - 11.0px 11.0px 7.5625px rgba(from var(--shadow-color) r g b / 0.030517578125), - 11.5px 11.5px 8.265625px rgba(from var(--shadow-color) r g b / 0.022247314453125), - 12.0px 12.0px 9.0px rgba(from var(--shadow-color) r g b / 0.015625), - 12.5px 12.5px 9.765625px rgba(from var(--shadow-color) r g b / 0.010467529296875), - 13.0px 13.0px 10.5625px rgba(from var(--shadow-color) r g b / 0.006591796875), - 13.5px 13.5px 11.390625px rgba(from var(--shadow-color) r g b / 0.003814697265625), - 14.0px 14.0px 12.25px rgba(from var(--shadow-color) r g b / 0.001953125), - 14.5px 14.5px 13.140625px rgba(from var(--shadow-color) r g b / 0.000823974609375), - 15.0px 15.0px 14.0625px rgba(from var(--shadow-color) r g b / 0.000244140625), - 15.5px 15.5px 15.015625px rgba(from var(--shadow-color) r g b / 3.0517578125e-05) - ; - } - - & .adjectives { - --recursive-wght: 800; - font-size: 2rem; - vertical-align: 50%; - } - - & .house { - width: max-content; - font-size: 4rem; - padding-left: 1em; - padding-top: 0.1em; - } -} - -@media (hover: none) { - h1.page-title a { - text-decoration: none; - } -} - @media (max-width: 450px) { body { --top-min-spacing: 40px; diff --git a/static/css/tree.css b/static/css/tree.css index 6aaed5e..ae73ba2 100644 --- a/static/css/tree.css +++ b/static/css/tree.css @@ -2,7 +2,6 @@ :root { --tree-indent-width: 3.2rem; - --transition-duration: 0.15s; --button-bar-icon-size: 2.8rem; } @@ -82,6 +81,21 @@ .tree { --tree-indent-guide-dim: transparent; --tree-indent-guide-highlighted: transparent; + + display: flex; + flex-direction: column; + + align-self: start; + align-items: center; + + & > article { + padding: 1.6rem; + width: 100%; + } + + & > footer { + max-width: none; + } } .tree:has(.branch-container:hover) { @@ -116,8 +130,9 @@ } /* Top level should not have an indent or a border. */ -.tree > ul { +.tree article > ul { padding-left: 0; + margin-left: 0; border-left: none; } diff --git a/template/_doc.hbs b/template/_doc.hbs index 132c397..2c68496 100644 --- a/template/_doc.hbs +++ b/template/_doc.hbs @@ -4,28 +4,25 @@ {{> components/_head.hbs }} + + + + {{#each page.styles}} + + {{/each}} + + - - - {{> components/_noscript.hbs }} + {{> components/_sidebar.hbs }} - {{> components/_nav.hbs }} - {{> components/_header.hbs }} - - {{!-- - NOTE: ~ because components/_tree.hbs must not include any extra indentation, because it may - contain pre elements which shouldn't be indented. - --}} - {{~> components/_tree.hbs }} - - {{!-- For all pages except the one linked from the footer, include the footer icon. --}} - {{#if (ne page.tree_path "treehouse")}} - {{> components/_footer.hbs }} - {{/if}} + {{~> components/_doc.hbs }} @@ -33,3 +30,4 @@ +{{~> components/_jar.hbs }} diff --git a/template/_tree.hbs b/template/_tree.hbs index f395225..233f511 100644 --- a/template/_tree.hbs +++ b/template/_tree.hbs @@ -4,31 +4,30 @@ {{> components/_head.hbs }} + + + + {{#each page.styles}} + + {{/each}} + + - - - {{> components/_noscript.hbs }} + {{> components/_sidebar.hbs }} - {{> components/_nav.hbs }} - {{> components/_header.hbs }} - - {{!-- - NOTE: ~ because components/_tree.hbs must not include any extra indentation, because it may - contain pre elements which shouldn't be indented. - --}} {{~> components/_tree.hbs }} - {{!-- For all pages except the one linked from the footer, include the footer icon. --}} - {{#if (ne page.tree_path "treehouse")}} - {{> components/_footer.hbs }} - {{/if}} - + +{{~> components/_jar.hbs }} diff --git a/template/components/_doc.hbs b/template/components/_doc.hbs new file mode 100644 index 0000000..0afa630 --- /dev/null +++ b/template/components/_doc.hbs @@ -0,0 +1,17 @@ +
+
+ + + {{> components/_footer.hbs }} +
+ + {{> components/_feed.hbs }} +
diff --git a/template/components/_feed.hbs b/template/components/_feed.hbs new file mode 100644 index 0000000..780ef75 --- /dev/null +++ b/template/components/_feed.hbs @@ -0,0 +1,20 @@ +{{#if page.feed}} +
+

{{ page.feed.title }}

+ + {{#each page.feed.entries}} +
+

{{{ title }}}

+
+ +
    + {{#each categories as |category|}} +
  • {{ category }}
  • + {{/each}} +
+
+
+ {{/each}} +
+{{/if}} + diff --git a/template/components/_footer.hbs b/template/components/_footer.hbs index e216374..064b07e 100644 --- a/template/components/_footer.hbs +++ b/template/components/_footer.hbs @@ -9,6 +9,8 @@ + {{!-- For all pages except the one linked from the footer, include the footer icon. --}} + {{#if (ne page.tree_path "treehouse")}}
@@ -58,4 +60,5 @@
+ {{/if}} diff --git a/template/components/_head.hbs b/template/components/_head.hbs index cf19b2e..bbed9c8 100644 --- a/template/components/_head.hbs +++ b/template/components/_head.hbs @@ -4,12 +4,15 @@ - + - {{!-- Import maps currently don't support the src="" attribute. Unless we come up with something @@ -38,14 +41,14 @@ clever to do while browser vendors figure that out, we'll just have to do a cach }; diff --git a/template/components/_jar.hbs b/template/components/_jar.hbs new file mode 100644 index 0000000..48941e1 --- /dev/null +++ b/template/components/_jar.hbs @@ -0,0 +1,16 @@ + + diff --git a/template/components/_nav.hbs b/template/components/_nav.hbs deleted file mode 100644 index 7ce797d..0000000 --- a/template/components/_nav.hbs +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/template/components/_sidebar.hbs b/template/components/_sidebar.hbs new file mode 100644 index 0000000..ae1dff5 --- /dev/null +++ b/template/components/_sidebar.hbs @@ -0,0 +1,17 @@ + + diff --git a/template/components/_tree.hbs b/template/components/_tree.hbs index cf3379d..c9368f1 100644 --- a/template/components/_tree.hbs +++ b/template/components/_tree.hbs @@ -1,18 +1,13 @@
- {{!-- Append page styles and scripts into the main content, such that they can be inlined - into linked branches when those are loaded in. Putting them in the page's head would make - extracting them way more painful than it needs to be. --}} +
+ {{#if (ne page.tree_path 'index')}} +
+

{{ page.title }}

+
+ {{/if}} - {{#each page.styles}} - - {{/each}} + {{{~ page.tree }}} +
- - - {{{ page.tree }}} + {{> components/_footer.hbs }}
diff --git a/treehouse.toml b/treehouse.toml index 34ec8f3..8881c23 100644 --- a/treehouse.toml +++ b/treehouse.toml @@ -12,7 +12,7 @@ commit_base_url = "https://src.liquidev.net/liquidex/treehouse/src/commit" [user] title = "riki's house" author = "riki" -description = "a fluffy ragdoll's fluffy house = —w— =" +description = "a pink ragdoll's fluffy house = —w— =" canonical_url = "https://riki.house" # URI prefix to use for entry IDs in feeds. @@ -26,6 +26,7 @@ feed_id_prefix = "https://liquidex.house" "social/github" = "https://github.com/liquidev" "social/soundcloud" = "https://soundcloud.com/daknus" "social/listenbrainz" = "https://listenbrainz.org/user/liquidev/" +"social/bandcamp" = "https://bandcamp.com/rikimoe" # treehouse management facilities "treehouse/issues" = "https://src.liquidev.net/riki/treehouse/issues"