Select the types of activity you want to include in your feed.
Add hero image support with <xeblog-hero>
Also lightens the JavaScript load and shifts ad impressions to only when people from Reddit and Hacker News visit. I may have this include Twitter in the future.
···99# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
10101111[dependencies]
1212-axum = "0.5"
1212+axum = { version = "0.5", features = ["headers"] }
1313axum-macros = "0.2"
1414axum-extra = "0.3"
1515color-eyre = "0.6"
···2828lazy_static = "1.4"
2929log = "0.4"
3030lol_html = "0.3"
3131+maud = { version = "0.23.0", features = ["axum"] }
3132mime = "0.3.0"
3233prometheus = { version = "0.13", default-features = false, features = ["process"] }
3334rand = "0"
3535+regex = "1"
3436reqwest = { version = "0.11", features = ["json"] }
3537serde_dhall = "0.11.1"
3638serde = { version = "1", features = ["derive"] }
+101
blog/site-update-hero-images.markdown
···11+---
22+title: "Site Update: Hero Images"
33+date: 2022-06-08
44+---
55+66+For a while I've been wondering how I can add dramatic flair to my website with
77+so-called "hero images". These images are tools that let you describe the mood a
88+website wants to evoke. I've been unsure how to best implement these on my
99+website for a while, but with the advent of MidJourney and other image
1010+generation APIs/algorithms I think I have found a way to create these without
1111+too much effort on my part and the results are pretty fantastic:
1212+1313+<xeblog-hero file="secret-to-life" prompt="the secret to life, the universe and everything, concept art"></xeblog-hero>
1414+1515+I have generated a bunch of other images that I'm going to use for my other
1616+posts. I'll give out a desktop wallpaper sized version of each of these images
1717+on my [Patreon](https://patreon.com/cadey).
1818+1919+Under the hood this is powered by
2020+[lol_html](https://github.com/cloudflare/lol-html) and
2121+[Maud](https://maud.lambda.xyz/). The magic is mostly contained in a function
2222+that generates a `<figure>` HTML element (which I just learned exists today). I
2323+use a function that looks like this for generating the `<xeblog-hero>` snippets:
2424+2525+```rust
2626+pub fn xeblog_hero(file: String, prompt: Option<String>) -> Markup {
2727+ html! {
2828+ figure.hero style="margin:0" {
2929+ picture style="margin:0" {
3030+ source type="image/avif" srcset={"https://cdn.xeiaso.net/file/christine-static/hero/" (file) ".avif"};
3131+ source type="image/webp" srcset={"https://cdn.xeiaso.net/file/christine-static/hero/" (file) ".webp"};
3232+ img style="padding:0" alt={"hero image " (file)} src={"https://cdn.xeiaso.net/file/christine-static/hero/" (file) "-smol.png"};
3333+ }
3434+ figcaption { "Image generated by MidJourney" @if let Some(prompt) = prompt { " -- " (prompt) } }
3535+ }
3636+ }
3737+}
3838+```
3939+4040+I have it wired up with lol_html like this:
4141+4242+```rust
4343+lol_html::element!("xeblog-hero", |el| {
4444+ let file = el.get_attribute("file").expect("wanted xeblog-hero to contain file");
4545+ el.replace(&crate::tmpl::xeblog_hero(file, el.get_attribute("prompt")).0, ContentType::Html);
4646+ Ok(())
4747+})
4848+```
4949+5050+The result is that I can declare hero images with HTML fragments like this:
5151+5252+```html
5353+<xeblog-hero file="miku-dark-souls" prompt="hatsune miku, elden ring, dark souls, concept art, crowbar"></xeblog-hero>
5454+```
5555+5656+And I get this:
5757+5858+<xeblog-hero file="miku-dark-souls" prompt="hatsune miku, elden ring, dark souls, concept art, crowbar"></xeblog-hero>
5959+6060+<xeblog-conv name="Mara" mood="hacker">This is powered by the
6161+[`<figure>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure)
6262+tag, which is a new discovery to us. This is probably one of the most useful
6363+tags we never knew about and removed the need to write a bunch of annoying CSS
6464+and HTML.</xeblog-conv>
6565+6666+The webp and AVIF versions of the hero images have a higher resolution version
6767+so that it looks nicer on retina screens. However, the png versions of these are
6868+locked to a resolution of 800x356 pixels because I was unable to crush them
6969+below a size of half a megabyte at full resolution. Realistically, this should
7070+only affect older browsers on slower hardware, so I don't expect this to have
7171+too much impact on most users.
7272+7373+<xeblog-conv name="Cadey" mood="coffee">If you don't want to see these hero
7474+images, you can remove them with a userstyle like this:
7575+```css
7676+figure.hero {
7777+ display: none;
7878+}
7979+```
8080+</xeblog-conv>
8181+8282+I'm likely going to convert over most of my website templates to use Maud. I'm
8383+very happy with it and I think it is incredibly useful to express your HTML in
8484+Rust instead of something that has to be compiled to Rust. In practice it
8585+reminds me of the Nim library [emerald](http://flyx.github.io/emerald/), which
8686+lets you write HTML using Nim functions similar to how you use Maud.
8787+8888+Here's a few more examples of hero images I have generated:
8989+9090+<xeblog-hero file="the-forbidden-shape" prompt="the forbidden shape"></xeblog-hero>
9191+9292+<xeblog-hero file="great-wave-cyberpunk" prompt="the great wave off of kanagawa, cyberpunk, hanzi inscription"></xeblog-hero>
9393+9494+Normally I will only have one image per post and it will usually be after the
9595+introduction paragraph. The prompt will usually be related to the article topic,
9696+but sometimes I will take artistic liberty. If you have suggestions for prompts,
9797+please [contact me](/contact) with those ideas.
9898+9999+I hope these updates on how I've been messing with my site are interesting. I'm
100100+trying to capture the spirit of how I'm implementing these changes as well as
101101+details of how everything fits together.
···11+use super::xeblog_conv;
22+use crate::post::Post;
33+use chrono::prelude::*;
44+use lazy_static::lazy_static;
55+use maud::{html, Markup};
66+use regex::Regex;
77+88+lazy_static! {
99+ static ref HN: Regex = Regex::new(r#"^https?://news.ycombinator.com"#).unwrap();
1010+ static ref REDDIT: Regex = Regex::new(r#"^https?://((.+).)?reddit.com"#).unwrap();
1111+}
1212+1313+pub fn referer(referer: Option<String>) -> Markup {
1414+ if referer.is_none() {
1515+ return html! {};
1616+ }
1717+1818+ let referer = referer.unwrap();
1919+2020+ let nag = html! {
2121+ script r#async src="https://media.ethicalads.io/media/client/ethicalads.min.js" { "" }
2222+ div.adaptive data-ea-publisher="christinewebsite" data-ea-type="image" data-ea-style="stickybox" {
2323+ .warning {
2424+ (xeblog_conv(
2525+ "Cadey".into(),
2626+ "coffee".into(),
2727+ html! {
2828+ "Hello! Thank you for visiting my website. You seem to be visiting from a news aggregator and have ads disabled. These ads help pay for running the website and are done by "
2929+ a href="https://www.ethicalads.io/" { "Ethical Ads" }
3030+ ". I do not receive detailed analytics on the ads and from what I understand neither does Ethical Ads. If you don't want to disable your ad blocker, please consider donating on "
3131+ a href="https://patreon.com/cadey" { "Patreon" }
3232+ ". It helps fund the website's hosting bills and pay for the expensive technical editor that I use for my longer articles. Thanks and be well!"
3333+ },
3434+ ))
3535+ }
3636+ }
3737+ };
3838+3939+ if HN.is_match(&referer) {
4040+ return nag;
4141+ }
4242+4343+ if REDDIT.is_match(&referer) {
4444+ return nag;
4545+ }
4646+4747+ html! {}
4848+}
4949+5050+pub fn prerelease(post: &Post) -> Markup {
5151+ if Utc::today().num_days_from_ce() < post.date.num_days_from_ce() {
5252+ html! {
5353+ .warning {
5454+ (xeblog_conv("Mara".into(), "hacker".into(), html!{
5555+ "Hey, this post is set to go live on "
5656+ (format!("{}", post.detri()))
5757+ " UTC. Right now you are reading a pre-publication version of this post. Please do not share this on social media. This post will automatically go live for everyone on the intended publication date. If you want access to these posts, please join the "
5858+ a href="https://patreon.com/cadey" { "Patreon" }
5959+ ". It helps me afford the copyeditor that I contract for the technical content I write."
6060+ }))
6161+ }
6262+ }
6363+ } else {
6464+ html! {}
6565+ }
6666+}
+5-17
templates/blogpost.rs.html
···11-@use super::{header_html, footer_html, mara};
22-@use crate::post::Post;
33-@use chrono::prelude::*;
11+@use super::{header_html, footer_html};
22+@use crate::{post::Post, tmpl::nag};
4355-@(post: Post, body: impl ToHtml)
44+@(post: Post, body: impl ToHtml, referer: Option<String>)
6576@:header_html(Some(&post.front_matter.title.clone()), None)
87···2625} else {
2726 <link rel="canonical" href="@post.front_matter.redirect_to.as_ref().unwrap()" />
2827}
2929-3030-<script type="module" src="/static/js/conversation.js"></script>
31283229<script type="application/ld+json">
3330 @{
···5956 </script>
6057}
61586262-<div id="refererNotice"></div>
6363-<script type="module" src="/static/js/hnwarn.js"></script>
5959+@Html(nag::referer(referer).0)
64606561<h1>@post.front_matter.title</h1>
66626767-@if Utc::today().num_days_from_ce() < post.date.num_days_from_ce() {
6868-<div class="warning">
6969- @:mara("hacker", "Mara", Html(format!(r#"Hey, this post is set to go live to the public on {} UTC. Right now you are reading a pre-publication version of this post. Please do not share this on social media. This post will automatically go live for everyone on the intended publication date. If you want access to these posts, please join the <a href="https://patreon.com/cadey">Patreon</a>. It helps me afford the copyeditor that I contract for the technical content I write."#, post.detri())))
7070-</div>
7171-} else {
7272-<script async src="https://media.ethicalads.io/media/client/ethicalads.min.js"></script>
7373-}
6363+@Html(nag::prerelease(&post).0)
74647565<small>A @post.read_time_estimate_minutes minute read.</small>
76667767@body
7878-7979-<div class="adaptive" data-ea-publisher="christinewebsite" data-ea-type="image" data-ea-style="stickybox"></div>
80688169<hr />
8270